9.4 KiB
STEP-101 RenderOrchestrator teardown 生命周期修复 — Codex 任务 v1
0. 任务目的
修复 Particle V18 Android 在正常 Renderer teardown / MapView reset 时发生的 Native SIGABRT。
现场已在 NavSea Android x86_64 设备稳定复现:
Fatal signal 6 (SIGABRT)
thread: RenderThread 11
FORTIFY:
pthread_mutex_lock called on a destroyed mutex
libmaplibre.so
std::__ndk1::mutex::lock()
...
mbgl::android::MapRenderer::resetRenderer()
V17 正常,V18 接入后稳定在启动约 9~11 秒触发。
本任务只修 Particle Native 生命周期;禁止在 NavSea 增加绕路、private wrapper、禁止阻止正常 MapView reset。
未经用户明确要求:不加入、不执行 Jest。
1. 冻结基线
工作树:
/Users/tei/dev/MapLibreParticleLab-step101
分支:
step101-render-orchestrator-teardown
起点必须是:
origin/particle/main = d29e84c1a90daeea104b389d28b000c4f017d08d
即正式 V18 Final HEAD。
V18 已发布,不得覆盖、重发或移动:
maplibre-particle/rn/13.4.1-particle.18- Android
13.4.1-particle.18 - iOS
6.28.0-particle.18
修复通过后由 RDC/ChatGPT 发布新不可变版本 V19。
2. 已知根因证据
V18 STEP-100 新增:
RenderSegmentRevisionBook retainedSegmentRevisions;
mutable std::mutex readinessDiagnosticsMutex;
uint64_t readinessEventSequence;
std::unordered_map<std::string, SourceReadinessEventState> sourceReadinessEvents;
RenderMutationDiagnostics lastReadinessMutation;
多个 RenderSource / Tile observer callback 会进入:
advanceRetainedSourceRevision(...)
recordReadinessMutation(...)
onTileChanged(...)
onTileAction(...)
buildReadinessDiagnostics(...)
retainedSegmentSourceState(...)
并 lock:
readinessDiagnosticsMutex
V18 当前成员声明中:
renderSources
renderLayers
...
retainedSegmentRevisions
...
readinessDiagnosticsMutex
sourceReadinessEvents
...
threadPool
C++ 成员按声明反序析构,因此 readiness state / mutex 会早于 renderSources 销毁。
而 RenderSource 在创建时:
renderSource->setObserver(this);
observer 指向 RenderOrchestrator。
当前 RenderOrchestrator::~RenderOrchestrator() 只:
- contextLost 时标记 layer;
threadPool.waitForEmpty();
没有在 readiness state 仍有效时显式 detach RenderSource observer / 清理 renderSources。
因此 V18 存在明确生命周期危险:
readinessDiagnosticsMutex destroyed
↓
renderSources still destroying/alive
↓
late tile/source callback
↓
RenderOrchestrator::onTile*
↓
lock destroyed mutex
↓
FORTIFY SIGABRT
3. 修复原则
不要只依赖调整成员声明顺序。
本任务应建立显式 teardown contract:
RenderOrchestrator destructor begins
↓
stop / drain deferred scheduler work
↓
detach observer from every RenderSource
↓
destroy/clear renderSources while all orchestrator state is still alive
↓
only then continue member teardown
同时 RenderSource::setObserver(nullptr) 必须安全恢复到 NullObserver。
目标是即使未来成员声明顺序变化,Source callback 也不能在 owner teardown 后访问 RenderOrchestrator。
4. 必做实现
4.1 RenderSource nullptr observer 语义
当前:
void RenderSource::setObserver(RenderSourceObserver* observer_) {
observer = observer_;
}
改成与 Style Source / Layer 一致的安全语义:
observer = observer_ ? observer_ : &nullObserver;
要求:
- constructor 仍默认 NullObserver;
setObserver(nullptr)是合法 detach;- onTileChanged / onTileError / onTileAction 不需要加 null 分支;
- 不吞掉正常有 observer 时的回调。
必须加非 Jest C++ 回归测试验证 nullptr detach 后 callback 不 crash、不调用旧 observer。
4.2 RenderOrchestrator 显式 teardown
在 RenderOrchestrator::~RenderOrchestrator() 中,在 readiness mutex/state 仍确定存活时:
- 保留现有 contextLost layer handling;
- 等待已有 deferred work 完成;
- 对所有
renderSources执行 observer detach; - 显式
renderSources.clear(); - 必要时对 glyph/image observer 也做已有 API 支持的 detach,但不要扩大范围;
- 不允许 teardown callback 再进入 RenderOrchestrator source callback。
顺序必须结合当前 TaggedScheduler / RenderSource / TilePyramid 的真实生命周期检查,而不是机械照抄上述编号。
如果存在“clear renderSources 本身可能产生新的 scheduler work”,需要设计第二次 drain 或更安全顺序,并在报告解释。
4.3 生命周期状态可选保护
如果仅 detach + explicit clear 仍无法证明并发安全,允许增加最小 teardown flag,例如 atomic bool,但只作为二次防线。
禁止用 flag 代替 observer detach。
禁止 catch FORTIFY;destroyed mutex abort 不是 C++ exception。
5. 不得修改
- STEP-100 Source→Segment locality 语义;
- per-segment revision;
- per-segment readiness;
- mixed Build/Reuse/Fallback;
- Fast Path gate;
- singleContinuousSegment;
- Source::isLoaded;
- Style Z-order;
- provider/network;
- NavSea;
- MapRenderer::resetRenderer 正常生命周期;
- Android Activity 为避免 reset 而加 workaround。
6. 必做测试
6.1 Native 非 Jest
至少覆盖:
- RenderSource 默认 NullObserver 可安全接 callback;
- setObserver(real) 正常转发;
- setObserver(nullptr) 后不再调用旧 observer;
- detach 后 onTileChanged / onTileError / onTileAction 安全;
- RenderOrchestrator teardown 有至少一个含 RenderSource 的场景;
- source callback 与 teardown 边界不访问已销毁 readiness state;
- STEP-100 locality 相关 focused tests 不回归。
如果现有测试设施不方便直接构造完整 RenderOrchestrator teardown,允许提取最小可测试 teardown helper,但禁止为了测试重构大范围生产结构。
6.2 Android Test App
在现有 ParticleCoreLayerValidationActivity / Validation App 增加专门 STEP-101 teardown 场景。
场景必须真实触发 Native Renderer teardown,而不是只 clear field。
至少提供:
T1 RESET_ONCE
T2 RESET_LOOP_20
T3 RESET_WITH_DYNAMIC_GEOJSON
T4 RESET_WITH_DIAGNOSTICS
目标:
- recreate/destroy MapView 或等价真实 renderer.reset;
- 每轮等待足够让 source/tile work 进入正常运行;
- T3 同时保持动态 GeoJSON/source mutation;
- T4 在 reset 前调用一次 RendererRuntime diagnostics,模拟 NavSea probe 时序;
- 20 次循环无 SIGABRT / crash / ANR;
- reset 后新 renderer 能继续正常显示 / 获取 diagnostics。
不得通过延迟 reset、禁用 observer、关闭 GeoJSON 来逃避问题。
7. NavSea 现场等价验收
RDC 在 Codex 完成后独立执行。
Android 设备:
192.168.200.139:5555
必须显式 adb -s。
最终至少执行:
- Validation App STEP-101 T1~T4;
- crash buffer 检查;
- 20 次 reset/recreate;
- 安装 V19 进 NavSea 后做真实启动;
- 保留 NavSea 当前
key={map-style-generation-...}正常 reset 触发条件; - 保留/启用一次 RenderSegment diagnostics probe A/B;
- 连续至少 5 次冷启动,每次观察 >30 秒;
- 不允许出现:
pthread_mutex_lock called on a destroyed mutex- SIGABRT RenderThread
MapRenderer::resetRendererteardown crash。
8. STEP-100 回归验收
修复 teardown 后必须确认 V18 的核心 locality 没被破坏。
至少复测 T4 R-C-R:
无关 R0:
buildDelta = 0
invalidationDelta = 0
reuseDelta > 0
动态 R1 正常 build/invalidation。
禁止通过删除 mutex/readiness/revision 功能来修 crash。
9. Codex 执行边界
Codex负责:
- 只读检查真实 V18 代码;
- 实现最小 Native 修复;
- 编写/更新非 Jest C++ tests;
- 更新 Android Validation STEP-101 场景;
- 静态检查;
- 生成报告。
Codex不负责:
- NavSea 修改;
- V19 release;
- tag;
- publish;
- 覆盖 V18;
- Jest。
Mac Native 开发/编译原则保持;不要使用 Linux Native 环境。
10. 完成报告
生成:
docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md
Step 记录:
docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md
报告必须写:
- root cause;
- start HEAD;
- 修改文件;
- destructor teardown 顺序;
- observer detach 语义;
- 为什么不依赖成员声明顺序;
- tests added;
- 实际运行 / NOT RUN;
- STEP-100 locality 是否保持;
- 风险;
- 未发布 V19(Codex 阶段)。
Codex阶段不 push,由 RDC 验收后统一 commit/push。
11. V19 发布门槛
仅当:
- Native compile PASS;
- focused C++ tests PASS;
- Android Validation teardown loop PASS;
- STEP-100 locality regression PASS;
- NavSea V19 真实 5 次冷启动 >30s PASS;
- crash buffer 无 destroyed mutex;
才允许发布:
- RN / Android:
13.4.1-particle.19 - iOS:
6.28.0-particle.19 - tag:
maplibre-particle/rn/13.4.1-particle.19
V18 保持不可变,不覆盖、不删除。
12. 最终原则
正常:
MapView unmount
Style generation replacement
renderer.reset()
Activity recreate
都是合法生命周期。
Particle Native 必须做到:
Renderer teardown 永远不能因为 observer late callback 访问已销毁 mutex/state 而杀掉宿主 App。