Cancel a running Media3 export, completing the third engine #243

Merged
JMR-dev merged 1 commits from test/cancelling-a-running-export into main 2026-09-06 07:48:23 +00:00
JMR-dev commented 2026-09-06 07:28:20 +00:00 (Migrated from github.com)

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

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)
Sign in to join this conversation.