Closes#224 — the Media3Engine half. The two FFmpeg engines landed in ad2a75d (#236) and d293646 (#237); with this, asking a running export to stop is covered on all three.
Why the assertion is the output file here, and was not for FFmpeg
The FFmpeg side could not use the file: invokeOnCancellation unlinks it, and on POSIX ffmpeg keeps writing to the unlinked inode, so the path stays gone whether or not the cancel landed. It asserts the session's return code instead.
Media3Engine deletes nothing — the partial is ConversionWorker's to clean up — so the file is the evidence.
A cancelled export reports itself two ways, and both mean interrupted:
no video track, or
MediaExtractor refusing the file outright with IOException: Failed to instantiate extractor, because there is no moov atom.
The first version treated only the null as success, and the exception failed the test — which is how that was measured. Only a playable file counts as a miss.
The wait before reading is several times the export's own length, so a cancel that did not land has certainly finished by then: the failure direction is "the file became playable", never "we did not wait long enough".
Retries, and one extra guard
Retried for the reason the other two engines measured — a 3 s 320×240 export outruns a naive cancel on a loaded runner (#240). An export that never wrote a file at all is recorded as inconclusive rather than allowed to pass as a cancellation, which matters on the API 37 image where the decoder is what fails.
Baseline
It carries @FailsOnEmulatorApi37, so FAILS_ON_EMULATOR_API37_BASELINE moves 3 → 4 in this diff, as that file requires.
That file also claimed removing the marker would grow the gating leg "by two" — wrong since the third marker landed, and recorded as E4 in docs/e2e-read-findings.md. It now names the constant instead of restating it, so it cannot drift again.
Verification — local API 34 emulator
tests=68 failures=0 errors=0 skipped=3
Mutation — remove transformer.cancel():
AssertionError: never interrupted a running export in 5 attempts, so either every export
finished first or cancellation does not reach the transformer:
[attempt 0 produced a playable video/hevc, attempt 1 produced a playable video/hevc,
attempt 2 produced a playable video/hevc, attempt 3 produced a playable video/hevc,
attempt 4 produced a playable video/hevc]
Every attempt completes. The retry does not soften it.
Closes #224 — the `Media3Engine` half. The two FFmpeg engines landed in `ad2a75d` (#236) and `d293646` (#237); with this, asking a running export to stop is covered on all three.
## Why the assertion is the output file here, and was not for FFmpeg
The FFmpeg side **could not** use the file: `invokeOnCancellation` unlinks it, and on POSIX ffmpeg keeps writing to the unlinked inode, so the path stays gone whether or not the cancel landed. It asserts the session's return code instead.
`Media3Engine` deletes nothing — the partial is `ConversionWorker`'s to clean up — so the file *is* the evidence.
A cancelled export reports itself **two** ways, and both mean interrupted:
- no video track, or
- `MediaExtractor` refusing the file outright with `IOException: Failed to instantiate extractor`, because there is no moov atom.
The first version treated only the null as success, and the exception failed the test — which is how that was measured. Only a *playable* file counts as a miss.
The wait before reading is several times the export's own length, so a cancel that did not land has certainly finished by then: the failure direction is "the file became playable", never "we did not wait long enough".
## Retries, and one extra guard
Retried for the reason the other two engines measured — a 3 s 320×240 export outruns a naive cancel on a loaded runner (#240). An export that never wrote a file at all is recorded as **inconclusive** rather than allowed to pass as a cancellation, which matters on the API 37 image where the decoder is what fails.
## Baseline
It carries `@FailsOnEmulatorApi37`, so `FAILS_ON_EMULATOR_API37_BASELINE` moves **3 → 4** in this diff, as that file requires.
That file also claimed removing the marker would grow the gating leg *"by two"* — wrong since the third marker landed, and recorded as **E4** in `docs/e2e-read-findings.md`. It now names the constant instead of restating it, so it cannot drift again.
## Verification — local API 34 emulator
```
tests=68 failures=0 errors=0 skipped=3
```
Mutation — remove `transformer.cancel()`:
```
AssertionError: never interrupted a running export in 5 attempts, so either every export
finished first or cancellation does not reach the transformer:
[attempt 0 produced a playable video/hevc, attempt 1 produced a playable video/hevc,
attempt 2 produced a playable video/hevc, attempt 3 produced a playable video/hevc,
attempt 4 produced a playable video/hevc]
```
Every attempt completes. The retry does not soften it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #224 — the
Media3Enginehalf. The two FFmpeg engines landed inad2a75d(#236) andd293646(#237); with this, asking a running export to stop is covered on all three.Why the assertion is the output file here, and was not for FFmpeg
The FFmpeg side could not use the file:
invokeOnCancellationunlinks it, and on POSIX ffmpeg keeps writing to the unlinked inode, so the path stays gone whether or not the cancel landed. It asserts the session's return code instead.Media3Enginedeletes nothing — the partial isConversionWorker's to clean up — so the file is the evidence.A cancelled export reports itself two ways, and both mean interrupted:
MediaExtractorrefusing the file outright withIOException: Failed to instantiate extractor, because there is no moov atom.The first version treated only the null as success, and the exception failed the test — which is how that was measured. Only a playable file counts as a miss.
The wait before reading is several times the export's own length, so a cancel that did not land has certainly finished by then: the failure direction is "the file became playable", never "we did not wait long enough".
Retries, and one extra guard
Retried for the reason the other two engines measured — a 3 s 320×240 export outruns a naive cancel on a loaded runner (#240). An export that never wrote a file at all is recorded as inconclusive rather than allowed to pass as a cancellation, which matters on the API 37 image where the decoder is what fails.
Baseline
It carries
@FailsOnEmulatorApi37, soFAILS_ON_EMULATOR_API37_BASELINEmoves 3 → 4 in this diff, as that file requires.That file also claimed removing the marker would grow the gating leg "by two" — wrong since the third marker landed, and recorded as E4 in
docs/e2e-read-findings.md. It now names the constant instead of restating it, so it cannot drift again.Verification — local API 34 emulator
Mutation — remove
transformer.cancel():Every attempt completes. The retry does not soften it.
🤖 Generated with Claude Code