Skip to content

[实现] 按实际会话边界切分 afterMonotonic 判据,消除 -j4 基线假红 #116

Closed

Description

@wt159

承接

#115 决策票的决议已按既有一次性批量授权采用 AI 推荐,本票是其实现票(GitHub 原生 blocked_by 已连接)。本票只解决 -j4 基线假红,不修 SIGSEGV、不重做 #113。

目标:消除 sdk_interaction_tests / PublicPlayer Interactive Paths / test_player_integration.cpp:1408 的 CHECK(afterMonotonic) 低概率假红,让 ctest -j4 同轮数对照有干净基线。

改动清单(严格最小化)

唯一落地文件:sdk/test/unit/test_player_integration.cpp(用例 position callbacks stay monotonic and a legal seek still rewinds,:1333-1409)。

把「新会话起点」从固定快照索引 beforeCount 改为实际回退点:

const std::vector<uint64_t> after = callbacks.positionValues();
// seek 是异步 WorkQueue 任务;first 快照到 seek 生效之间旧会话仍会追加 position。
// 回退点不一定落在 beforeCount,必须按序列内容定位,不能按快照下标猜。
size_t rewindAt = after.size();
for (size_t i = beforeCount; i < after.size(); ++i) {
    if (after[i] < before) { rewindAt = i; break; }
}
bool afterMonotonic = true;
for (size_t i = rewindAt + 1; i < after.size(); ++i) {
    if (after[i] < after[i - 1]) {
        afterMonotonic = false;
    }
}

rewound 的 waitFor 已保证序列中存在 < before 的值,且 positionValues() 只增不减,故 rewindAt < after.size() 成立;REQUIRE(rewound) 语义不动。

为什么这不是放宽判据:原判据把「after[beforeCount] 之后所有相邻对」都当新会话,于是旧会话尾部 before+Δ → 合法回退值 100 被误判成回退;新判据只把回退点之后当新会话,仍逐对检查单调。若真回退发生在新会话内部(H_B:100 → 更小值),rewindAt 之后那对仍会被抓到 —— 检测力不降反清晰。两条契约(合法回退必须发生、新会话内单调不回退)与断言条数均不变。

阶段 1:实证(必须先做,不得跳过)

  1. 在用例里临时加诊断输出(打印 beforeCount / before / 完整 after 序列 / 实际回退点下标),只写 stdout(ctest 捕获),不写任何固定名字的共享文件(-j4 撞名假红的教训)。
  2. 用 ctest --test-dir build/sdk/test -j4 --output-on-failure 反复跑全套直到复现一次(历史命中率约 1/10 轮,每轮约 58–62s;单用例串行复现率低,因为并发负载才拉宽窗口)。
  3. 依据抓到的序列判定:
    • H_A 成立:失败序列形如 ... before, before+Δ1, before+Δ2, 100, 100+δ ...,回退点在 beforeCount + k (k≥1) → 按上面的改动清单修复。
    • H_B 成立:出现「回退值之后还有更小值」或多次回退 → 属生产 reportPosition / resetFloor 路径问题,本票停下并另开新票,不改生产代码。
  4. 诊断代码 git checkout HEAD -- <file> 还原,零残留。

阶段 2:变异验证(可红可绿)

  • 构造「新会话内真回退」输入:临时让 seek 之后再触发一次 resetFloor 上报(例如 _setPosition() 后追加一次 reportPosition(较小值, ..., true, ...)),新判据必须红;或直接在判据侧注入一条含真回退的 after 序列验证循环能抓到。
  • 字节一致还原生产文件后必须绿。取证期间对生产文件的临时改动允许,但还原后 git diff 必须为空。
  • 记录红/绿两条命令与输出,作为「未放宽判据」的证据。

阶段 3:稳定性闸门(沿用 #112 决议 4,一票否决)

  • 分支与 master 同轮数 ctest -j4 ≥10 轮,同机、同 build 目录、同样 -j4;分支假红率不得高于基线,且本用例在分支上 0 假红。
  • 切 master 对照前先 git checkout master 并重编译 UnitTests;切回分支同样重编译,避免拿错二进制。
  • 分支假红率高于基线即撤回全部改动并关闭 PR,不合并、不放宽任何判据 / 超时 / 基线 —— 与 PR fix: stop() 清空未消费 seek 槽并锁定状态契约(#112 / #113) #114 的处理完全一致。
  • 两轴 code-review;PR 阶段 Linux sdk-coverage + Windows sdk-windows 双绿(只认 gh run view <id> --json status,conclusion,attempt)。

明确不做

  • 不改任何生产代码作为落地改动(sdk/MusicPlayer.cpp、AudioStreamDecoder.*、公开 API、七个 void 控制入口)。
  • 不新增测试专用钩子 / 注入点(避免再引入 [实现] stop() 清空未消费 seek 槽并锁定状态契约 #113 的钩子闸门);不新增 add_test 分组。
  • 不删任何 CHECK / REQUIRE,不新增 skipCase 条件;不改任何数值容差、waitFor 3000ms、stepMs=20、seek 目标、采样门槛、TIMEOUT、快集 / 全量集划分、14 个 sdk_* 目标、坏输入 55 例 / 396 断言基线、CI 配置。
  • 不改 afterMonotonic 的断言条数(该 suite 目前 111 断言 / 19 用例)。
  • 不修 42488656322 的 SIGSEGV(根因仍未定位:无复现、无栈、CI=Linux / 本机=Windows 不可外推);H2 未改、H3/H4 未验。
  • 不重做 [实现] stop() 清空未消费 seek 槽并锁定状态契约 #113、不合并或 cherry-pick ca3543fef;重做须另开新票重走全套闸门。
  • AGENTS.md 的「反复出现」三条门槛不删、不放宽。

验收

  • 阶段 1 抓到失败序列并判定 H_A / 排除 H_B;诊断代码零残留。
  • 阶段 2 变异验证精确红 → 字节一致还原 → 绿;生产文件 git diff 为空。
  • 阶段 3 分支与 master 各 ≥10 轮同轮数 -j4,分支假红率不高于基线且本用例 0 假红。
  • 两轴 code-review 通过;PR 阶段双平台 CI 双绿。
  • 文档回填:AGENTS.md NOTES(「按固定快照索引切会话」反模式 + 本假红根因)、doc/player-state-contract.md §10.4、当日日志与长期记忆;如实保留「SIGSEGV 根因仍未定位」以及「本 effort 只解决基线假红」。
  • 票正文不改,resolution 留完整依据(实证序列、变异验证命令、逐轮对照数据、撤回或放行的判定)。

Activity

  1. added 2 commits that reference this issue on Nov 9, 2026
  2. wt159 commented on Nov 9, 2026

    @wt159
    OwnerAuthor

    Resolution:实现完成、全部验收通过,随 PR #117 合入 master

    结果:PR #117 merge commit b482b900e。实现提交 a07d19f3d(implementer worktree)→ 209782555(merger --no-ff 合入集成分支)。唯一落地代码文件 sdk/test/unit/test_player_integration.cpp,7 insertions / 1 deletion;另有 AGENTS.md +1 行的 #115 决议档。票正文未改。

    阶段 1 实证(H_A 成立 / H_B 排除)

    诊断版本在 afterMonotonic 为 false 时才 printf 到 stdout(不写任何固定名字共享文件),全套 ctest --test-dir build/sdk/test -j4 --output-on-failure 第 7 轮复现,日志 build/flake-diag/round_7.log:

    [FLAKE-DIAG] beforeCount=24 before=535 afterSize=27
    v[19]=442  v[20]=465  v[21]=488  v[22]=512  v[23]=535 (= before, first.back())
    v[24]=558   <- 旧会话在 first 快照之后追加 1 个值 (k=1)
    v[25]=100   <- seek(100) 合法回退,序列中唯一 DESCENT
    v[26]=123   <- 新会话,递增
    

    rewindAt 搜索从 beforeCount 起,v[19..22] 虽 < before 但在快照范围内,不会被误定位。sdk_interaction_tests 12.51s 失败、test cases: 19 / 18 passed / 1 failed、assertions: 111 / 110 passed / 1 failed,失败点 :1408(诊断版因插入 16 行显示为 :1424)CHECK( afterMonotonic )。H_A 精确成立;H_B 排除(只有一次回退,回退后 123 单调递增,没有「回退值之后还有更小值」或多次回退,无 resetFloor 二次上报迹象)→ 不需另开生产票。诊断 git checkout HEAD -- 还原,merger 复核 FLAKE-DIAG grep=0。

    阶段 2 变异验证(可红可绿)

    步骤 命令 输出
    修复版基线 cd sdk/test && UnitTests.exe -tc="position callbacks stay monotonic and a legal seek still rewinds" test cases: 1 / 1 passed,assertions: 6 / 6 passed,Status: SUCCESS!,rc=0
    注入真回退 临时把 after 改为非 const 并 after.push_back(50)(新会话 123 → 50)后重编译 :1415 ERROR: CHECK( afterMonotonic ) is NOT correct!,assertions: 6 / 5 passed / 1 failed,Status: FAILURE!,rc=1
    字节一致还原 git checkout HEAD -- sdk/test/unit/test_player_integration.cpp git diff 为空;重编译后单用例 6/6 SUCCESS rc=0;全套 ctest -j4 14/14 全绿 59.38s

    生产文件全程未改(sdk/MusicPlayer.cpp 等 git diff 恒为空)。证明新判据仍能抓到新会话内真回退,检测力未降。

    阶段 3 稳定性闸门(放行)

    同机、同 build 目录、同样 -j4,每轮全套 14 目标,脚本 build/flake_gate.sh:

    • 集成分支(修复版)10/10 全绿、0 次 afterMonotonic 假红,逐轮 60,59,58,60,59,56,58,59,60,58 s。
    • master 915c2bc40(旧判据)9/10,第 5 轮 sdk_interaction_tests 13.73s 失败,逐轮 58,59,58,57,58,55,58,57,57,59 s;失败日志 build/flake-gate/master_round_5.log::1408 CHECK( afterMonotonic ) is NOT correct!、111 断言 110 通过 1 失败,与阶段 1 及 fix: stop() 清空未消费 seek 槽并锁定状态契约(#112 / #113) #114 两次失败完全同形态。
    • 切回集成分支重编译后单用例 6/6 SUCCESS、源码工作区无改动。

    判定:分支 0/10 ≤ 基线 1/10 且本用例 0 假红 → 放行,不撤回、不放宽任何判据 / 超时 / 基线。10 对 10 样本小,不宣称统计显著;证据强度来自确定性失败序列 + 红绿变异 + 同轮数 master 自然复现。

    两轴 code-review

    • Standards:无硬性违规(纯 CRLF 无 BOM、WebKit/4 空格、中文注释、不写共享固定路径、rewindAt 只读本地快照未落判据主循环额外写、无钩子、断言与数值未动、诊断零残留)。1 处 Duplicated Code 判断项(定位循环与 waitFor lambda 同形)被「就地判据、不新增 helper/钩子、断言条数不变」的仓库约束抑制,不改。
    • Spec:与票定伪码逐字一致(起点 beforeCount、rewindAt=after.size()、命中即 break、rewindAt+1 检查),无范围蔓延;rewindAt 边界安全(找不到时 rewindAt=size、二循环不执行,但 CHECK(rewound) 会红,不静默通过);两条契约保留。一条非硬缺陷提示留痕:rewindAt 取首个 < before,正确性系于 H_A 实证前提「快照后旧会话追加值恒 >= before」;若该段出现 < before 的旧值会提前定位、把合法回退重判为回退 —— 方向是更严的假红而非漏判,且 monotonic CHECK 原本就不覆盖该追加段。不改代码以免偏离票定伪码与「不放宽」口径。

    验收清单

    • 阶段 1 实证 H_A / 排除 H_B,诊断零残留。
    • 阶段 2 变异精确红 → 字节一致还原 → 绿,生产文件 diff 为空。
    • 阶段 3 分支 / master 各 10 轮同轮数 -j4,分支 0 假红。
    • 两轴 code-review;PR 阶段 Linux 42603640630 + Windows 42603640466 双绿 attempt=1(只认 gh run view --json status,conclusion,attempt)。
    • 文档回填:AGENTS.md NOTES 反模式条 + 补「只解决基线假红 / SIGSEGV 根因仍未定位」、doc/player-state-contract.md §10.4、当日日志与长期记忆。
    • 票正文不改,本 resolution 留完整依据;明确不做清单逐项遵守(无生产代码 / 无钩子 / 无新 add_test / 不删 CHECK / 不改数值与 TIMEOUT / 不修 SIGSEGV / 不重做 [实现] stop() 清空未消费 seek 槽并锁定状态契约 #113 / 不合并 ca3543fef / 门槛不删不放宽)。
  3. added a commit that references this issue on Nov 9, 2026
  4. wt159 commented on Nov 9, 2026

    @wt159
    OwnerAuthor

    补充(不改 resolution):合并后 push run 42607915236 attempt 1 的两个失败目标都在 test_player_integration.cpp,故「失败目标不在改动文件」不成立,按 AGENTS.md 登记的替代取证处理:tree 对比 209782555 vs b482b900e 为空、doctest suite 过滤器确认与本次改动用例零交集、同代码 PR run 42603640630 已绿、一次 rerun --failed 全绿。未放宽任何判据 / 超时 / 基线;详细数据见 PR #117 结论 comment。本票验收结论不变。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agent信息完备,agent 可直接开工

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions