test(PKG-HLS-SDK-01): checkpoint direct HTTP teardown crashes

This commit is contained in:
Harvey Zhao committed 2026-09-14 14:42:12 +08:00
1 parent fe5534ac24
commit 3316b8a867
8 files changed
+1516 -10

No files matched your search

@@ -127,3 +127,10 @@ For unresolved Firefox grouped-stream diagnostics, use
`--observe-workers` uses the same native-worker observer as the integration suite. Each run retains
its own report and trace files. See the HLS-SDK-01 in-progress record: one target crash and a separate
group-switch stall remain unclassified. Passing diagnostic repetitions are not release evidence.
The diagnostic runner records host-side failure/crash phases. Optional
`--capture-before-destroy` saves media/SDK events outside the page before teardown;
it adds an evaluate and may change timing, so it is off by default. Direct ordinary
HTTP without ArtPlayer/plugin or a Worker observer still reproduced crashes with
both reset-first and SDK-first teardown. A passing no-worker control does not
replace worker acceptance. See the 2026-09-14 HTTP teardown checkpoint in refactor.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,70 @@
# PKG-HLS-SDK-01: direct HTTP teardown diagnostic checkpoint
Status remains doing. Baseline fe5534ac2429ac67702ca9d4121b4ab8603cb278.
No production source, dependency, fixture, build output or acceptance assertion
was changed. This checkpoint advances attribution, not completion.
## Controls and results
All five runs use frozen Hls.js1.5.17, Windows Firefox155.0, local ordinary HTTP,
direct native video, no ArtPlayer/plugin, no route interception, no Worker
observer and no SDK logger. Native worker output is not substituted. Browser
process DEBUG logging is enabled and saved for every run. Existing trace capture
and media/SDK event recording remain enabled, so these are not uninstrumented runs.
| Run | Teardown | Worker | Extra pre-destroy snapshot | Pass | Fail |
| --- | --- | --- | --- | --- | --- |
| Original runner | reset first | yes | no | 9 | 1 |
| Original runner control | reset first | no | no | 10 | 0 |
| Phase/snapshot runner | reset first | yes | yes | 5 | 0 |
| Final phase runner | reset first | yes | no | 4 | 1 |
| Final phase runner | SDK first | yes | no | 3 | 2 |
35 diagnostic iterations total:31 pass,4 fail. These are finite controls, not
release-matrix passes or a claim about long-term failure probability. The no-worker
control cannot substitute for required worker acceptance. Snapshot success may
reflect changed timing; it is not evidence of a repair.
The three failures in the final phase runner occur during final-state collection,
after destroy and the immediate post-destroy state call returned. The first failure
predates phase recording and cannot be assigned the same precise stage. Original
errors, trace hashes and logs are retained separately from previous diagnostics.
Both reset-first and SDK-first reproduce failures. Do not change the public core
destroy order or callers' SDK configuration based on the previously passing
SDK-first samples. No grouped-playback timeout occurred in these direct controls;
the independent HLS-PLAYBACK-01 failure remains unclosed.
## Browser evidence limits
The installed Firefox1543 archive's TargetRegistry.js observes
`oop-frameloader-crashed`, emits the target crash event and disposes that target.
The logged stack reaches this observer. The source member hash and excerpt are
recorded with the actual binary archive hash, not inferred from another release.
This supports a browser out-of-process frame-loader crash notification; it is not
a native stack, minidump or proof of the responsible decoder/worker/SDK component.
The browser parent exits normally after explicit teardown. Its exit code0 does
not negate page crashes. The SWGL framebuffer startup warning occurs in both the
failing worker run and passing no-worker control, so it is not a root-cause claim.
## Maintained diagnostic changes
The runner now records host-side operation phases and the phase/time of crash
events. Errors during final state, tracing or context close remain failures even
after operation assertions passed. This introduces no page calls in default mode.
`--capture-before-destroy` optionally saves media, SDK and event state to Node
before destroying the page; it defaults off and is explicitly listed in reports.
That extra evaluate may affect timing, which is why its passing control is kept
separate. Existing7-second waits, retries, media, SDK and worker settings are intact.
Script lint and the frozen SDK/worker archive check pass. Package source and all
published build outputs remain unchanged, so no package rebuild is required.
See [evidence](../baselines/hls-http-teardown-validation.json) for exact runner,
SDK/media inputs, reports, traces and failure classification.
## Next evidence
HLS-CRASH-01 and HLS-PLAYBACK-01 remain open; task SDK-01 is not complete. Obtain
native Firefox content-process crash diagnostics or a minimized MSE reproduction
and independently investigate the grouped-stream stall. Continue other package
tasks while keeping this gate visible. Reverting this checkpoint removes diagnostic
phase/snapshot metadata only and restores no production behavior.
+1 -1
View File
@@ -548,7 +548,7 @@
- PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md)
- PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md)
- PKG-HLS-04: [记录](changes/2026-09-12-PKG-HLS-04-types.md) [记录](baselines/hls-types-validation.json) [记录](hls-validation.md)
- PKG-HLS-SDK-01: [记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) [记录](baselines/hls-sdk-validation.json) [记录](baselines/hls-sdk-diagnostics.json)
- PKG-HLS-SDK-01: [记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) [记录](baselines/hls-sdk-validation.json) [记录](baselines/hls-sdk-diagnostics.json) [记录](changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md) [记录](baselines/hls-http-teardown-validation.json)
- PKG-DASH-01: [记录](changes/2026-09-12-PKG-DASH-01-contract.md) [记录](baselines/dash-control-contract.md) [记录](baselines/dash-control-release.json)
- PKG-DASH-02: [记录](changes/2026-09-12-PKG-DASH-02-tests.md) [记录](dash-validation.md) [记录](baselines/dash-validation.json)
- PKG-DASH-03: [记录](changes/2026-09-12-PKG-DASH-03-runtime.md) [记录](baselines/dash-runtime.json) [记录](dash-validation.md)
+12
View File
@@ -1,5 +1,17 @@
# 进度与证据
## PKG-HLS-SDK-01 普通HTTP销毁诊断检查点(仍未完成)
直接原生video、Hls1.5.17、Firefox155.0、普通HTTP且无ArtPlayer/插件/Worker观察器,
35次诊断31通过4失败。关闭Worker的10次及可选销毁前快照5次通过不能替代正常验收;
先销毁SDK也失败2/5,因此不能把销毁顺序当作修复。新增主机侧阶段记录,三次有阶段
证据的崩溃发生在destroy已返回后的最终状态读取。当前Firefox调试源码接收到
oop-frameloader-crashed,但没有原生堆栈来确定责任组件;SWGL警告两种控制均存在。
见[检查点](changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md)及[证据](baselines/hls-http-teardown-validation.json)。只改诊断/文档,生产和产物未变。
脚本lint和SDK归档验证通过,原崩溃/切组失败均保留,SDK-01仍doing;253项统计不变。
下一步需要Firefox原生崩溃诊断或更小MSE复现,切组卡住独立排查。VAST初始化选择仍待
用户回复,继续不依赖这些决定的包验收工作;不改核心销毁顺序或放宽worker门槛。
## PKG-VAST-07 旧default调用兼容完成
VAST入口增加default自身别名,恢复旧require(...).default调用并保留现有直接调用。
+5 -2
View File
@@ -2688,10 +2688,13 @@
"test/browser/hls-sdk.spec.js",
"refactor/changes/2026-09-12-PKG-HLS-SDK-01-integration.md",
"refactor/baselines/hls-sdk-diagnostics.json",
"refactor/scripts/hls-sdk-diagnostic.mjs"
"refactor/scripts/hls-sdk-diagnostic.mjs",
"refactor/changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md",
"refactor/baselines/hls-http-teardown-validation.json"
],
"compatibleResolution": "直接原生 video(无 ArtPlayer/插件)和候选核心已有 page crashed;候选核心无观察器也复现。继续区分 SDK、Firefox/Playwright 与媒体销毁,不静默改核心 destroy 顺序或调用方 SDK 配置。",
"closureCriteria": "取得可解释的崩溃诊断或直接 SDK 对照;验证处置和新旧核心/worker/分组销毁矩阵,不能仅以再次通过关闭。"
"closureCriteria": "取得可解释的崩溃诊断或直接 SDK 对照;验证处置和新旧核心/worker/分组销毁矩阵,不能仅以再次通过关闭。",
"workspaceState": "2026-09-14 direct HTTP/no ArtPlayer/plugin/Worker observer:35 diagnostics,31 passed4 failed. Worker reset-first fails1/10 and1/5; no-worker10/10 and optional pre-destroy snapshot5/5 pass. SDK-first also fails2/5. Three phase-recorded crashes occur during final-state collection after destroy returned. Installed Firefox TargetRegistry observer receives oop-frameloader-crashed; no native stack/root cause established. Do not substitute no-worker/snapshot success or change core teardown order; risk remains open."
},
{
"id": "HLS-PLAYBACK-01",
+25 -6
View File
@@ -10,7 +10,7 @@ import { observeWorkers } from '../../test/helpers/worker-observer.js'
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
// Direct SDK control: no ArtPlayer or plugin is loaded, and no worker output is substituted.
export async function diagnose({ version = '1.7.2', iterations = 5, transport = 'http', teardown = 'reset-first', worker = true, host = 'direct', plugin = false, sdkLogs = false, workerObserver = false } = {}) {
export async function diagnose({ version = '1.7.2', iterations = 5, transport = 'http', teardown = 'reset-first', worker = true, host = 'direct', plugin = false, sdkLogs = false, workerObserver = false, captureBeforeDestroy = false } = {}) {
const runnerSHA256 = hash(fs.readFileSync(new URL(import.meta.url)))
const observerSHA256 = hash(fs.readFileSync(new URL('../../test/helpers/worker-observer.js', import.meta.url)))
assert(Number.isInteger(iterations) && iterations > 0 && iterations <= 50)
@@ -66,10 +66,13 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
await context.tracing.start({ screenshots: true, snapshots: true, sources: true })
const page = await context.newPage()
const wait = predicate => expect.poll(() => page.evaluate(predicate), { timeout: 7000 }).toBe(true)
const entry = { iteration, status: 'running', errors: [], requests: [] }
const entry = { iteration, status: 'running', errors: [], requests: [], phase: 'setup', crashes: [] }
results.push(entry)
page.on('pageerror', error => entry.errors.push(error.message))
page.on('crash', () => entry.errors.push('page crashed'))
page.on('crash', () => {
entry.errors.push('page crashed')
entry.crashes.push({ phase: entry.phase, at: new Date().toISOString() })
})
page.on('requestfailed', request => entry.requests.push({ url: request.url(), error: request.failure() }))
if (transport === 'route') {
await page.route('**/*.m3u8', route => serveRoute(route))
@@ -163,9 +166,11 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
},
}
}, { worker, teardown, host, plugin, sdkLogs })
entry.phase = 'initial-playback'
await wait(() => window.direct.video?.readyState >= 3)
await page.evaluate(() => window.direct.video.play())
await wait(() => window.direct.video.currentTime > 0.3)
entry.phase = 'low-group'
await page.evaluate(() => {
window.direct.hls.currentLevel = 0
})
@@ -175,19 +180,28 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
window.direct.hls.audioTrack = 1
})
await wait(() => window.direct.records.some(item => item.at >= window.direct.mark && item.event === 'AUDIO_TRACK_SWITCHED' && item.data.id === 1))
entry.phase = 'high-group'
await page.evaluate(() => {
window.direct.hls.currentLevel = 1
})
await wait(() => window.direct.video.videoHeight === 180 && window.direct.hls.audioTrack === 0)
entry.phase = 'commentary-track'
await page.evaluate(() => {
window.direct.hls.audioTrack = 2
})
await wait(() => window.direct.records.some(item => item.event === 'AUDIO_TRACK_SWITCHED' && item.data.id === 2))
entry.phase = 'return-low-group'
await page.evaluate(() => {
window.direct.hls.currentLevel = 0
})
await wait(() => window.direct.hls.audioTracks.length === 2)
if (captureBeforeDestroy) {
entry.phase = 'capture-before-destroy'
entry.beforeDestroy = await page.evaluate(() => ({ coreLoaded: typeof window.Artplayer, pluginLoaded: typeof window.artplayerPluginHlsControl, media: window.direct.state(), sdk: { version: window.Hls.version, workerEnabled: window.direct.hls.config.enableWorker, level: window.direct.hls.currentLevel, audioTrack: window.direct.hls.audioTrack }, events: window.direct.records }))
}
entry.phase = 'destroy-call'
await page.evaluate(() => window.direct.destroy())
entry.phase = 'after-destroy-state'
await page.evaluate(() => window.direct.state())
assert.deepEqual(entry.errors, [])
entry.status = 'passed'
@@ -198,15 +212,20 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
await page.screenshot({ path: path.join(directory, `${iteration}.png`) }).catch(() => {})
}
finally {
entry.failurePhase = entry.status === 'failed' ? entry.phase : undefined
entry.phase = 'final-state'
entry.state = await page.evaluate(() => ({ coreLoaded: typeof window.Artplayer, pluginLoaded: typeof window.artplayerPluginHlsControl, workers: window.workerEvidence, media: window.direct?.state(), events: window.direct?.records, sdk: { version: window.Hls?.version, level: window.direct?.hls.currentLevel, audioTrack: window.direct?.hls.audioTrack } })).catch(error => ({ error: error.message }))
entry.phase = 'trace-stop'
await context.tracing.stop({ path: path.join(directory, `${iteration}.zip`) }).catch((error) => {
entry.traceError = error.message
})
entry.phase = 'context-close'
await context.close().catch((error) => {
entry.closeError = error.message
})
if (entry.state.error || entry.errors.length || entry.traceError || entry.closeError) {
entry.status = 'failed'
entry.failurePhase ||= entry.crashes[0]?.phase || (entry.state.error ? 'final-state' : entry.traceError ? 'trace-stop' : 'context-close')
entry.failure ||= entry.state.error || entry.traceError || entry.closeError || entry.errors.join('; ')
}
}
@@ -217,14 +236,14 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
await browser?.close()
server.closeAllConnections()
await new Promise(resolve => server.close(resolve))
fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify({ version, iterations, transport, teardown, worker, host, plugin, sdkLogs, workerObserver, runnerSHA256, observerSHA256, browserVersion, release, inputs: Object.fromEntries([...files].map(([file, bytes]) => [file, hash(bytes)])), results }, null, 2)}\n`)
fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify({ version, iterations, transport, teardown, worker, host, plugin, sdkLogs, workerObserver, captureBeforeDestroy, runnerSHA256, observerSHA256, browserVersion, release, inputs: Object.fromEntries([...files].map(([file, bytes]) => [file, hash(bytes)])), results }, null, 2)}\n`)
console.log(`Diagnostic report: ${directory}`)
}
return results
}
const { values } = parseArgs({ options: { 'version': { type: 'string', default: '1.7.2' }, 'iterations': { type: 'string', default: '5' }, 'transport': { type: 'string', default: 'http' }, 'teardown': { type: 'string', default: 'reset-first' }, 'no-worker': { type: 'boolean', default: false }, 'host': { type: 'string', default: 'direct' }, 'plugin': { type: 'boolean', default: false }, 'sdk-logs': { type: 'boolean', default: false }, 'observe-workers': { type: 'boolean', default: false } } })
diagnose({ version: values.version, iterations: Number(values.iterations), transport: values.transport, teardown: values.teardown, worker: !values['no-worker'], host: values.host, plugin: values.plugin, sdkLogs: values['sdk-logs'], workerObserver: values['observe-workers'] }).then((results) => {
const { values } = parseArgs({ options: { 'version': { type: 'string', default: '1.7.2' }, 'iterations': { type: 'string', default: '5' }, 'transport': { type: 'string', default: 'http' }, 'teardown': { type: 'string', default: 'reset-first' }, 'no-worker': { type: 'boolean', default: false }, 'host': { type: 'string', default: 'direct' }, 'plugin': { type: 'boolean', default: false }, 'sdk-logs': { type: 'boolean', default: false }, 'observe-workers': { type: 'boolean', default: false }, 'capture-before-destroy': { type: 'boolean', default: false } } })
diagnose({ version: values.version, iterations: Number(values.iterations), transport: values.transport, teardown: values.teardown, worker: !values['no-worker'], host: values.host, plugin: values.plugin, sdkLogs: values['sdk-logs'], workerObserver: values['observe-workers'], captureBeforeDestroy: values['capture-before-destroy'] }).then((results) => {
process.exitCode = results.some(result => result.status !== 'passed') ? 1 : 0
}).catch((error) => {
console.error(error)
+3 -1
View File
@@ -2100,7 +2100,9 @@
"evidence": [
"changes/2026-09-12-PKG-HLS-SDK-01-integration.md",
"baselines/hls-sdk-validation.json",
"baselines/hls-sdk-diagnostics.json"
"baselines/hls-sdk-diagnostics.json",
"changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md",
"baselines/hls-http-teardown-validation.json"
]
},
{