Skip to content

Make media metadata probing safe: dedicated worker, ffprobe timeout, CTag + codec fixes - #319

Merged
got3nks merged 3 commits into
amule-org:masterfrom
got3nks:fix/mediaprobe-isolate-timeout
Jul 6, 2026
Merged

Make media metadata probing safe: dedicated worker, ffprobe timeout, CTag + codec fixes#319
got3nks merged 3 commits into
amule-org:masterfrom
got3nks:fix/mediaprobe-isolate-timeout

Conversation

@got3nks

@got3nks got3nks commented Jul 6, 2026

Copy link
Copy Markdown

Problem

ffprobe media-metadata extraction (#280) runs on the shared CThreadScheduler — the single worker that also runs hashing and download completion (CHashingTask / CCompletionTask). ffprobe is invoked synchronously via wxExecute and can block for a long time, or hang outright on a headless daemon. When it does, it blocks the scheduler, so every finished download stays stuck at PS_COMPLETING and shutdown stalls behind it.

Fix

Move probing onto a dedicated CMediaProbeThread (modelled on CPartFileHashThread), so a slow or hung probe can only ever delay other probes — never completions or hashing.

Bound and make each probe killable. The ffprobe invocation is now a native posix_spawn / CreateProcess child instead of wxExecute: the synchronous wxExecute path is uncancellable, and its async path ties termination and pipe draining to the main-thread event loop, which is unsafe to poll from a worker thread (a use-after-free). The child is waited on with a 30s wall-clock timeout plus a cancel flag that EndThread flips at shutdown; on either, the child is killed and the join returns promptly. The kill takes the whole process tree — a POSIX process group (kill(-pid)) on Unix, a Job Object on Windows — so a wrapper-style ffprobe (snap, flatpak) is fully reaped rather than orphaned.

Probe tracing moves to a dedicated Media Probe (logMediaProbe) debug category.

Codec selection: prefer the video track (then audio)

The probe reported the first stream's codec, which for a container whose first stream is a subtitle or data track — common in mkv, where a leading subrip track advertised "subrip" as the file's codec. It now queries codec_type alongside codec_name and selects the first video track's codec, falling back to the first audio track's; subtitle / data streams never win.

Pre-existing CTag::operator= double-free

Exercising the re-attach path surfaced a latent heap-corruption bug that predates 3.0.1 and is independent of the media-probe change. CTag keeps an owned pointer in a union that is only valid for string / hash / blob tags; operator= freed that pointer keyed on the right-hand side's type, so assigning a string tag onto a slot currently holding an int freed a garbage pointer (the int's bits reinterpreted). std::vector<CTag>::erase hits this whenever it shifts a string tag over a non-string one — exactly the media tag set [length, bitrate, codec] when AddTagUnique re-attaches it. Fixed to free by the tag's current type (mirroring the destructor) then copy from the source (mirroring the copy constructor).

Commits

  • Fix CTag::operator= freeing the wrong union member — the pre-existing double-free above.
  • Isolate media probing on a dedicated bounded worker thread — thread isolation, native bounded/killable spawn (timeout + cancel + tree kill), logMediaProbe category.
  • Prefer the video track's codec (then audio) in media probing — codec selection.

Test plan

Eight deterministic checks, run on each platform:

  1. Probe → extract → attach — a shared media file yields MediaProbe: extracted … codec=… then Media metadata: … -> (tags attached), under the Media Probe debug category.
  2. Persisted, once-only — after a restart with known.met kept, the file logs skip (already has metadata); the tags survive in known.met.
  3. Runtime isolation — with FFProbePath pointed at a program that hangs, a probe blocks but EC round-trips stay responsive and completions proceed.
  4. Timeout — the hung probe is killed at ~30s with ffprobe timed out / cancelled; the daemon stays alive.
  5. Shutdown — with a probe hung, a graceful shutdown returns promptly (≈1–3s) instead of wedging on the join.
  6. Tree kill — a wrapper-style hung ffprobe that forks a child is fully reaped on shutdown/timeout, no orphan (POSIX process group / Windows Job Object).
  7. Re-attach — a file shared under two paths (same hash) re-attaches its media tags without the CTag crash.
  8. Codec selection — a subtitle-first mkv reports the video codec (h264), not subrip.
Platform Build 1 2 3 4 5 6 7 8
macOS (ARM64) Debug
Linux (Ubuntu ARM64) Debug
Windows 11 (ARM64) Debug

got3nks added 3 commits July 6, 2026 10:00
CTag holds an owned pointer (wxString* / CMD4Hash* / unsigned char*) in a
union that is only valid for string / hash / blob / bsob tags. operator=
freed that pointer keyed on the *right-hand side*'s type, so assigning a
string tag onto a slot that currently holds an int freed a garbage pointer
(the int's bits reinterpreted), corrupting the heap. std::vector<CTag>::erase()
hits this whenever it shifts a string tag over a non-string one -- observed as
a double free in a wxString destructor when a media tag set ([int length,
int bitrate, string codec]) is re-attached via AddTagUnique.

Free based on THIS tag's current type first (mirroring the destructor), then
copy from rhs (mirroring the copy constructor). Pre-existing since well before
3.0.1; independent of the media-probe change that exposes it.
ffprobe media-metadata extraction (amule-project#280) ran on the shared CThreadScheduler
alongside hashing and download completion. A slow or hung ffprobe -- readily
reproducible on a headless daemon -- blocked the scheduler, wedging every
download at PS_COMPLETING indefinitely and stalling shutdown.

Move probing onto a dedicated CMediaProbeThread (mirrors CPartFileHashThread),
so a stuck probe can only ever delay other probes, never completions or hashing.

Bound and make each probe killable: the ffprobe invocation is now a native
posix_spawn / CreateProcess child rather than wxExecute. wxExecute's synchronous
path is uncancellable, and its async path couples termination and pipe draining
to the main-thread event loop -- polling it from the worker is a use-after-free.
The child is waited on with a 30s wall-clock timeout and a cancel flag that
EndThread flips at shutdown; on either it is killed (by process group on POSIX,
so wrapper-style ffprobe from snap/flatpak is fully reaped) and the join returns
promptly.

Add a dedicated "Media Probe" (logMediaProbe) debug category; probe tracing
lives there rather than at normal log level.
The probe reported the first stream's codec_name, which for a container whose
first stream is a subtitle or data track (common in mkv: a leading subrip
track) advertised e.g. "subrip" as the file's codec. Query codec_type
alongside codec_name and select the first video track's codec, falling back to
the first audio track's; subtitle / data streams never win.
@got3nks got3nks changed the title Isolate media probing on a dedicated bounded worker thread Make media metadata probing safe: dedicated worker, ffprobe timeout, CTag + codec fixes Jul 6, 2026
@got3nks
got3nks merged commit c57fcb3 into amule-org:master Jul 6, 2026
13 checks passed
@got3nks
got3nks deleted the fix/mediaprobe-isolate-timeout branch July 6, 2026 09:21
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