Skip to content

Fix Android PAGView release race.#3561

Open
leiyue123 wants to merge 2 commits into
mainfrom
bugfix/thunderllei_android_race
Open

Fix Android PAGView release race.#3561
leiyue123 wants to merge 2 commits into
mainfrom
bugfix/thunderllei_android_race

Conversation

@leiyue123

Copy link
Copy Markdown
Collaborator

变更说明

  • 修复 PAGView detach 后异步动画回调仍进入 PAGPlayer.setProgress 的竞态,避免释放后继续访问 native。
  • 将 PAGView 的 update/flush 与 detach 释放串行化,确保释放 pagSurface 前当前帧更新完成。
  • 加固 Android JNI wrapper 的 nativeContext 读取和释放流程,避免 release/finalize 与 native 调用并发导致 wrapper mutex 被销毁后继续加锁。
  • 修正 PAGSurface finalize 后 nativeSurface 字段残留无效指针的问题。

关联 Issue

验证

  • git diff --cached --check
  • PAGView.java lints 无新增问题
  • 本机缺少 Java Runtime,未执行 Android Gradle 编译。

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.33%. Comparing base (322c33e) to head (1553506).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #3561    +/-   ##
========================================
  Coverage   81.32%   81.33%            
========================================
  Files         652      652            
  Lines       77024    77024            
  Branches    21747    21873   +126     
========================================
+ Hits        62640    62645     +5     
+ Misses       9953     9947     -6     
- Partials     4431     4432     +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

if (jPlayer != nullptr) {
jPlayer->clear();
}
clearPAGPlayer(env, thiz);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativeRelease 改用了 clearPAGPlayer,但下方 nativeFinalize 仍是 setPAGPlayer(env, thiz, nullptr)。而 Animator/Surface 的 finalize 都统一走了 clear 逻辑,三个文件处理方式不一致。虽然 setPAGPlayer(nullptr) 与 clearPAGPlayer 行为等价、无 bug,但建议把 nativeFinalize 也改为 clearPAGPlayer(env, thiz),让 setPAGPlayer 只保留 nativeSetup 的“设新值”语义,统一风格、便于维护。

namespace pag {
static jfieldID PAGPlayer_nativeContext;
}
static std::mutex PAGPlayer_contextLocker = {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里(以及 JPAGAnimator.cpp、JPAGSurface.cpp)新增使用了 std::mutex,但均未显式 #include ,目前依赖 pag.h 间接包含才能编译。按 IWYU 原则,建议在这三个 .cpp 中显式添加 #include ,避免未来上游头文件调整包含链时被动编译失败。非阻塞项。

{
std::lock_guard<std::mutex> autoLock(PAGSurface_contextLocker);
old = reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface));
env->SetLongField(thiz, PAGSurface_nativeSurface, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativeFinalize 这里把加锁+置0+delete 的逻辑内联了一遍,与上方刚新增的 clearPAGSurface 函数体逐行完全相同。建议直接调用 clearPAGSurface(env, thiz)(与 nativeRelease 一致),消除同文件内的重复逻辑,后续调整锁策略时也不易漏改一处。

tempAnimator = std::move(animator);
tempListener = std::move(listener);
}
// Call cancel outside the lock to avoid deadlock

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不建议删除这行 “Call cancel outside the lock to avoid deadlock” 注释。下方代码刻意采用“锁内 std::move 出局部变量、出锁后再 cancel()”的写法,正是为避免 cancel() 同步回调 listener 时重入 locker 造成死锁。删掉注释后,这段非直觉的写法失去了唯一的解释,后来者很可能误以为可简化为锁内直接 cancel 而重新引入死锁。项目规范也要求 workaround 需加注释说明原因,建议恢复。

}
isVisible = visible;
if (isVisible) {
boolean visible = attached && isShown();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里把 isShown() 特意放在前后两个 synchronized(PAGView.this) 块之间、而非合并为一个锁块,应该是刻意设计——isShown() 是 Android View 框架方法,持锁调用可能触发重入/回调导致死锁,所以必须在锁外调用。但代码无注释说明,读者容易把这两个锁误当作可合并的冗余,从而把 isShown() 挪进锁内引入死锁。建议加一行注释说明 isShown() 必须锁外调用的原因。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android平台多次发生NE问题

3 participants