Files
MapLibreParticleLab/docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md
tei 01863569fb
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: close STEP-101 teardown validation
2026-10-07 08:42:09 +09:00

13 KiB
Raw Blame History

STEP-101 RenderOrchestrator 销毁生命周期修复 Codex 完成报告 v1

1. 执行结论

PASS(RDC Native / Android runtime):V18 teardown destroyed-mutex 回归已修复并通过设备闭环。 Codex 阶段仅交付源码;随后 RDC 独立完成 Android full Native build、focused C++ TestRunner、STEP-101 T1~T4、20 次真实 renderer reset/recreate、STEP-100 locality 回归与 crash buffer 检查。V19 尚未在本报告阶段公开发布。

2. Git 基线

  • 工作区:/Users/tei/dev/MapLibreParticleLab-step101
  • 分支:step101-render-orchestrator-teardown
  • start HEAD:d29e84c1a90daeea104b389d28b000c4f017d08d
  • implementation commit:64cf8f44350eb8ec4b45360db4d3f9505ac4ac0b
  • implementation message:fix: make render orchestrator teardown safe
  • push result:PASS,origin/particle/main 独立回读与 implementation commit 一致。
  • 文档 closeout commit / 最终远端 HEAD:Git 提交无法在自身内容中自指其最终 SHA,按项目既有规则由最终交付回读记录。
  • 子模块:git submodule status 列出的子模块均以 - 开头,表示当前工作区未初始化;本轮未修改或初始化子模块。
  • V18 Android/RN 13.4.1-particle.18、iOS 6.28.0-particle.18 保持不变;V19 未发布。

3. 根因与实现

V18 readinessDiagnosticsMutex 等成员声明在 renderSources 之后,按 C++ 反序析构会先于 sources 消失。RenderSource/Tile 销毁存在同步 Cancelled callback 到 Orchestrator 的可达路径,随后可能锁已销毁 mutex。这是已证实的生命周期漏洞,与 Android FORTIFY 报错高度吻合。虽未取得精确到最终 callback 的完整符号化现场栈,但同一设备上 V18 条件稳定复现,而 STEP-101 修复后 T1~T4、20 次 reset、动态 GeoJSON 与 diagnostics 条件均不再崩溃,因此将其作为本次 Android teardown 回归的已确认工程根因。

当前源码的析构顺序为:处理 contextLost layers → 第一次 threadPool.waitForEmpty() → detach 各 RenderSource、GlyphManager、ImageManager observer → readiness state 仍活着时 renderSources.clear() → 第二次 threadPool.waitForEmpty() → 常规成员析构。RenderSource::setObserver(nullptr) 恢复 NullObserver,tile 回调无需新增空指针分支。显式 detach 与清理建立 owner teardown 合同,不依赖今后成员顺序碰巧安全;没有增加 teardown flag。

render-job 队列独立于 tagged scheduler work。静态检查显示 tile/worker 销毁可投递捕获 render data/bucket 的 render jobs;waitForEmpty() 不处理它们。OpenGL 同步 Android reset 路径先 renderer.reset()、再 backend.reset(),其 Context 析构调用 runRenderJobs(true);若启用 async renderer cleanup,backend 的清理时点另由后续生命周期决定。Metal Context 与 Vulkan backend 也有各自的 runRenderJobs(true) 清理入口,均未在本轮运行验证。测试显式 runRenderJobs(),但没有构建或设备证据证明所有 backend 模式均安全。这是 RDC 必须核对的剩余风险。

正常 source remove 使用 renderSources.erase(),clearData() 使用 renderSources.clear();两者均在 Orchestrator 实体及 readiness state 存活时执行。TileCache::~TileCache() 会清理 pending releases 并等待先前 deferred deletion 结束。这个审计没有证成任意外部并发调用都安全,本轮不修改正常 erase/clearData 行为,也不声称所有 source replacement 异步问题已解决。

4. 实际修改与审计结论

源码/测试差异共七个文件:

  1. src/mbgl/renderer/render_orchestrator.cpp:显式 detach、source clear 与双 drain。
  2. src/mbgl/renderer/render_orchestrator.hpp:仅为 focused test 开放 friend helper。
  3. src/mbgl/renderer/render_source.cpp:nullptr observer 回退 NullObserver。
  4. test/renderer/render_orchestrator_error.test.cpp:新增两个非 Jest teardown tests;helper 注册 Orchestrator observer,测试断言 source 销毁前 observer 已 detach、Cancelled 不再到旧 observer、第二次 drain 和独立 render-job 队列。
  5. platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp:nativeReset 与 resetRenderer 入口/完成日志,仅用于配对观测,未改变 reset 调用顺序。
  6. platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java:SurfaceView detach 与 Java nativeReset() 进入/返回日志。
  7. platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt:T1~T4,tile ready/diagnostics、动态 GeoJSON、Activity recreate、重建与完成日志;保存状态里的空场景不会在配置变更后重新从 Intent 启动。

本轮新增/更新文档:本报告、docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md、粒子开发记录.md。既有未跟踪的 STEP-101 任务书 v1/v2 没有修改。

与 v2 合同静态核对:observer、双 drain、render-job 独立审计、四个 Android 场景均有对应代码;STEP-100 locality/fast path/source loaded 语义无本次差异。T2 代码计的是 reset 请求,20 次 Native reset 必须由 log 中每轮 Java nativeReset、JNI nativeReset、MapRenderer::resetRenderer entered/completed 和新 renderer ready 独立确认,不能凭 UI DONE 计数认定。T3 使用已有 T4 R-C-R fixture 并持续更新动态 GeoJSON;T4 在 reset 前额外取 diagnostics。现有 C++ test 使用最小 tile-backed Source,并非实际 Android source;设备等价验收仍不可省略。

5. 实际检查与未执行项目

  • git diff --check:PASS(已跟踪 diff 的 whitespace/error 静态检查;未跟踪的新文档不在此命令检查范围)。
  • 源码 diff/合同静态审计:完成;补了 observer detach 断言、Java/JNI reset 边界日志、T3 mutation 证据及场景恢复边界。
  • CMake/Ninja/Native compile、C++ GTest:NOT RUN。
  • Android Gradle、Validation APK、AAR/RN candidate:NOT RUN。
  • ADB、T1~T4、crash buffer、STEP-100 locality 运行态、NavSea 冷启动:NOT RUN。
  • Jest、iOS/Metal build 与设备、publish/tag/commit/push:NOT RUN。
  • 产物路径、大小、SHA-256、运行日志:本轮没有新产物,均无结果。

6. RDC 精确验证命令与验收证据

在本工作区执行 Mac Native 定向验证(若既有 libuv 环境问题仍在,保留首个错误并标为环境阻塞):

cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='RenderOrchestratorTeardown.*:RenderSegmentLocality.*'

Android Validation APK 使用固定入口,不手拼 Gradle:

./tools/particle_android_validation/build_validation_apk.sh

RDC 使用显式设备 serial。每个场景先清日志、停止旧 Activity,再单独启动;T2 等待 20 轮结束。安装前核对 APK 文件、SHA-256、ABI 与运行设备:

adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
adb -s 192.168.200.139:5555 logcat -c
adb -s 192.168.200.139:5555 shell am force-stop org.maplibre.android.testapp
adb -s 192.168.200.139:5555 shell am start -n org.maplibre.android.testapp/.activity.particle.ParticleCoreLayerValidationActivity --es step101_scenario T2_RESET_LOOP_20
adb -s 192.168.200.139:5555 logcat -v threadtime -d > /tmp/step101-t2-logcat.txt
rg -n 'STEP101_|SurfaceView detached; nativeReset|nativeReset returned|MapRenderer::nativeReset|MapRenderer::resetRenderer|pthread_mutex_lock called on a destroyed mutex|Fatal signal|ANR' /tmp/step101-t2-logcat.txt

对 T1/T3/T4 分别重复相同 logcat -c、force-stop、am start、日志保存步骤,将 step101_scenario 依次替换为 T1_RESET_ONCE、T3_RESET_WITH_DYNAMIC_GEOJSON、T4_RESET_WITH_DIAGNOSTICS。逐轮配对 RESET_REQUEST、MAPVIEW_DESTROY_ENTER/RETURN、SurfaceView detached; nativeReset entered、JNI MapRenderer::nativeReset entered/completed、Native resetRenderer entered/completed、新 STYLE_READY 与 NEW_RENDERER_READY;T2 需至少 20 个完整 reset/recreate 配对,且无 SIGABRT/ANR。T3 核对 STEP101_DYNAMIC_SOURCE_MUTATION 在每轮 reset 前持续出现,T4 核对 PRE_RESET_DIAGNOSTICS 与 reset 后 diagnostics。STEP-100 T4 R-C-R 另按既有 Validation 菜单复跑,核对无关 R0 buildDelta=0、invalidationDelta=0、reuseDelta>0,动态 R1 正常 build/invalidation。

上述只是交接命令,本轮均未执行。V19 candidate 必须先于 NavSea 临时锁包和五次超过 30 秒的冷启动;crash buffer 与 locality 均通过后,才可进入不可变发布。NavSea 外部工作区/候选制品命令由 RDC 按其实际环境另行确定,本轮不伪造路径或结果。

7. 已知问题、未完成事项与下一步

静态审计无法证明 Android 每次 Activity recreate 都实际完成 native reset,也无法证明异步 backend 清理和真实 Source 并发场景无 late callback。Native 编译与定向测试、四个 Validation 场景、STEP-100 locality、NavSea 等价五次冷启动、crash buffer 均待 RDC;任何失败均应保留现场并按首个真实错误回传。无理由在本阶段修改 NavSea、发布 V19 或重写 STEP-100 语义。

8. RDC 独立验收(2026-10-07)

8.1 Android full Native / Validation APK

标准入口:

./tools/particle_android_validation/build_validation_apk.sh

结果:PASS。

variant=OpenglDebug
abi=x86_64
apk_bytes=61957678
libmaplibre_bytes=39637512
sha256=14cbd35ed0bc1366389ddea69925901013e3b209ed3575462421a7920c73c721

该构建实际编译了本 Step 修改后的 C++ Core、libmaplibre.so、JNI/Java reset 日志和 Android Validation T1~T4。

8.2 STEP-101 Android teardown runtime

设备:FZG1-2 / Android 13 / API 33 / x86_64 / 192.168.200.139:5555。

  • T1 RESET_ONCE:真实 MapRenderer::resetRenderer entered/completed、nativeReset entered/completed、新 renderer STYLE_READY / NEW_RENDERER_READY;PASS。
  • T2 RESET_LOOP_20:完整运行至 iteration 20,最终 STEP101_SCENARIO_DONE ... resetsRequested=20;逐轮出现 destroy/reset/new renderer ready;PASS。
  • T3 RESET_WITH_DYNAMIC_GEOJSON:reset 前动态 GeoJSON mutation 持续产生,reset 完成后新 renderer 继续 ready;PASS。
  • T4 RESET_WITH_DIAGNOSTICS:reset 前 getRenderSegmentDiagnostics() 可读,随后真实 reset 完成并重建;PASS。
  • 四组验收 crash buffer 均为空;未出现 pthread_mutex_lock called on a destroyed mutex、SIGABRT 或 ANR。

8.3 STEP-100 locality 回归

R-C-R T4 重新运行 60+ 秒,稳定窗口:

global revision: 62 -> 894

R0 retained:
build        1 -> 1      delta 0
invalidation 19 -> 19    delta 0
reuse        12 -> 3890  delta +3878

R1 retained:
build / invalidation 持续增长

动态 source mutation 仍只影响对应 segment;R0 source revision 保持不变。STEP-101 teardown 修复没有破坏 STEP-100 Source→Segment locality。

8.4 Focused Android C++ TestRunner

Mac host mbgl-test-runner 仍因本机缺失可用 libuv pkg-config 包而 ENV BLOCKED;没有安装或升级系统依赖来掩盖该环境事实。

改用仓库已有 Android Native C++ TestRunner,x86_64 build PASS。RDC 为 TestRunner 临时打包真实 test/fixtures/ 到 data.zip,仅满足 Android runner 既有解压契约;该临时 asset 已删除,不进入 Git。

设备过滤执行:

RenderSegmentLocality.SourceRevisionsAreScopedAndRecreationUsesNewIdentity PASS
RenderSegmentLocality.ReadinessAndFadeFollowOnlyDependencies PASS
RenderSegmentLocality.MixedExecutionDrawsEachLiveRangeOnceInStyleOrder PASS
RenderSegmentLocality.FailedBuildCannotReuseOldTexture PASS
RenderOrchestratorTeardown.NullObserverAndDetachCoverEveryTileCallback PASS
RenderOrchestratorTeardown.PendingTileCancellationAndSecondDrainRepeatSafely PASS
TestRunner finished with status: '0'

最终 TestRunner APK(含临时 fixtures,仅作验收证据)SHA-256:

e1b39b3eb035175b79dd60dbe39496ea982c2dd6e2f1593fd60dd9630f393712

crash buffer 为空。

首次 TestRunner 编译暴露 6 个测试源码级 C++20 designated-initializer / protected-base conversion 问题;RDC 仅修正测试源码字段补齐和显式转换,未改变生产语义。修复后 Android C++ TestRunner build 与上述 filtered tests 均 PASS。

8.5 结论

STEP-101 的 Native 生命周期修复达到关闭条件:

  1. RenderSource observer 在 owner teardown 前显式 detach;
  2. readiness state 仍存活时显式销毁 renderSources;
  3. source clear 前后 tagged work 双 drain;
  4. glyph/image observer 同步 detach;
  5. 20 次真实 Android renderer reset/recreate 无 destroyed-mutex;
  6. 动态 GeoJSON、diagnostics 条件下仍安全;
  7. STEP-100 locality 保持 PASS;
  8. focused C++ tests PASS。

Jest:NOT RUN。

V19 candidate、NavSea 五次冷启动与正式 publish/finalize 属于后续发行 Gate,不改变本 Step Native 修复的 PASS 结论。