Skip to content

Fence FFmpeg frame publication by seek serial - #133

Draft
Blackspirits wants to merge 2 commits into
audit/ffmpeg-frame-flush-no-reuse-3e4dfrom
audit/ffmpeg-present-serial-fence-cd1b
Draft

Blackspirits wants to merge 2 commits into
audit/ffmpeg-frame-flush-no-reuse-3e4dfrom
audit/ffmpeg-present-serial-fence-cd1b

Conversation

@Blackspirits

@Blackspirits Blackspirits commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Purpose

Dependent FFmpeg presenter follow-up to #132 ("Do not recycle FFmpeg frames removed by seek flush").

This tranche intentionally uses #132 as its base. #132 removes the object-reuse/data race for frames asynchronously removed by seek flush; this PR closes the separate publication race where a normal frame can pass the presenter's early serial check and still be published after a newer seek serial commits.

Current upstream SubtitleEdit/subtitleedit main was rechecked at 0e8b50b27f4084a12cd4a3b0ed01245366f91379; the latest delta is Japanese localization only and does not touch the FFmpeg player or frame queue.

Finding fixed

A frame from the previous serial could be published after a seek committed

PresentLoop() checks the head frame against _currentSerial, but that check happened before ShowFrame().

A successful seek can interleave in the gap:

  1. presenter peeks a serial-N frame and observes _currentSerial == N;
  2. demux completes a seek and commits _currentSerial = N+1 under _seekLock;
  3. before the seek-side frame flush removes the old head, the presenter enters ShowFrame();
  4. ShowFrame() pops the still-present serial-N frame and publishes it to the UI.

The identity check only proves that the same queue object was popped. It does not prove that its serial is still current at publication time.

With #132 in place, the old frame object itself is no longer eligible for asynchronous pool reuse, so the remaining boundary is ordering publication against seek commit.

Fix

ShowFrame() now treats publication as part of the seek-serial transaction:

  • revalidate frame.Serial == _currentSerial under _seekLock;
  • pop/claim exactly that frame while the same lock is held;
  • update restart/paused-position state under that lock;
  • publish the frame snapshot/version before releasing the lock;
  • defer FrameReady until after the lock is released, so UI/user callbacks never execute under the seek lock.

Because PerformSeek() commits _currentSerial under the same lock, the ordering is now explicit:

  • if frame publication wins the lock, it completes before the new serial can commit;
  • if seek commit wins the lock, the stale frame fails the revalidation and cannot be published.

The earlier PresentLoop() serial check remains as a cheap fast-path; correctness no longer depends on its timing.

Regression coverage

A pure helper test pins the publication classification:

  • equal frame/current serial -> eligible;
  • stale frame serial -> rejected.

The lock placement itself is reviewed structurally because the native/session pipeline is not instantiated by the pure test suite.

Scope / branch state

  • dependency/base PR: Do not recycle FFmpeg frames removed by seek flush #132
  • base HEAD: cd1ba8af5d5e0e7f7b576d7da96ccce4cfa26c8b
  • HEAD: 10c80c025d96d3b079e0bf279d05c8265c925071
  • 2 technical commits (source + regression test); connector guardrails blocked re-compaction, so no unsafe history rewrite was attempted
  • 2 files
  • +45 / -12
  • audit draft only; no merge/promotion intended

AI assistance: ChatGPT was used for adversarial seek/presenter interleaving analysis, lock-order review, publication-boundary design, and focused regression coverage.

Final CI

Authoritative run: SubtitleEdit#266 on 10c80c025d96d3b079e0bf279d05c8265c925071

  • SeConv: 488 passed / 2 skipped / 0 failed
  • LibUiLogic: 905 passed / 0 skipped / 0 failed
  • LibSE: 2017 passed / 0 skipped / 0 failed
  • UI: 5233 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 10c80c025d96d3b079e0bf279d05c8265c925071 after authoritative CI SubtitleEdit#266. Rechecked seek-lock ordering, frame claim/publication ordering, queue/current-frame lock interactions, callback deferral outside _seekLock, and stale-serial rejection. No blocker found in this tranche.

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