Skip to content

Fail closed on partial FFmpeg worker startup - #143

Draft
Blackspirits wants to merge 1 commit into
audit/ffmpeg-close-cleanup-generation-4eddfrom
audit/ffmpeg-partial-worker-start-6cfd
Draft

Blackspirits wants to merge 1 commit into
audit/ffmpeg-close-cleanup-generation-4eddfrom
audit/ffmpeg-partial-worker-start-6cfd

Conversation

@Blackspirits

@Blackspirits Blackspirits commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Purpose

Dependent startup-lifetime follow-up to #142 ("Fence FFmpeg close cleanup by load generation").

#140 gives Session teardown exact worker accounting and deferred reclamation, and #142 makes owner cleanup generation-safe. This tranche closes the remaining partial-startup boundary: Session.Start publishes/starts four workers sequentially, so a later Thread.Start failure can occur after earlier workers are already alive.

Current upstream SubtitleEdit/subtitleedit main was rechecked at d6917286e355976da953d0ee22208b4bf9461699; no relevant drift or competing upstream PR was found.

Finding fixed

The Session is published to the owner before Session.Start().

Before this change, worker startup was:

  • demux;
  • presenter;
  • video when present;
  • audio when present.

If a later Thread.Start() throws (for example under severe resource pressure) after earlier workers started, Start() escaped without closing those workers/resources. LoadFile() only handled ObjectDisposedException, so the failed Session could remain published with a partial worker set and the load Task could fault.

Fix

Worker startup is now all-or-nothing:

  • Start() keeps the existing lifecycle lock across the whole startup sequence;
  • any startup failure calls the same fail-closed teardown used by Dispose before the exception leaves the Session;
  • the existing Dispose body is factored into DisposeLocked(), with its ordering unchanged;
  • the owner catches non-concurrent startup failures, logs them, removes that Session only by CAS, and performs an idempotent Dispose;
  • owner-visible decoder/frame cleanup uses Fence FFmpeg close cleanup by load generation #142's generation fence, so a failed older startup cannot erase a replacement load;
  • _fileName is cleared only while the failed generation is still current;
  • the existing ObjectDisposedException path remains reserved for concurrent CloseFile winning startup.

#140's worker accounting means a Thread.Start that itself fails has already rolled back its active-worker count; workers that did start are closed/joined or retained fail-closed under the same timeout/reclamation rules.

Validation boundary

A deterministic process-level Thread.Start failure cannot be injected without adding a production-only thread-creation test hook. This tranche deliberately avoids that testability distortion.

Acceptance therefore relies on:

Scope / branch state

  • dependency/base PR: Fence FFmpeg close cleanup by load generation #142
  • base HEAD: 6cfde218dfd95e43e7e12077178e95b6969a57c4
  • HEAD: 1ad14ddc05f7852ec830ffed9d88bf5913a76334
  • 1 commit
  • 1 file
  • +87 / -53
  • current upstream main checked: d6917286e355976da953d0ee22208b4bf9461699
  • audit draft only; no merge/promotion intended

AI assistance: ChatGPT was used for adversarial partial-thread-start modelling, lifecycle-lock analysis, rollback ownership review and concurrent replacement cleanup review.

Final CI

Authoritative run: SubtitleEdit#282 on 1ad14ddc05f7852ec830ffed9d88bf5913a76334

  • SeConv: 488 passed / 2 skipped / 0 failed
  • LibUiLogic: 905 passed / 0 skipped / 0 failed
  • LibSE: 2017 passed / 0 skipped / 0 failed
  • UI: 5241 passed / 9 skipped / 0 failed
  • build: 0 errors / 7 pre-existing warnings
  • retry step skipped
  • failure-artifact upload skipped

Final adversarial review was performed on this exact HEAD after CI; no blocker found.

@Blackspirits Blackspirits left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final adversarial review on 1ad14ddc05f7852ec830ffed9d88bf5913a76334 after CI SubtitleEdit#282: no blocker found. The DisposeLocked extraction preserves teardown ordering; partial worker startup closes already-started workers under the same lifecycle lock; failed Thread.Start accounting is rolled back by #140; the owner removes a failed Session only by CAS and #142 prevents failed/stale startup cleanup from erasing a replacement load. No merge/promotion performed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant