Make media metadata probing safe: dedicated worker, ffprobe timeout, CTag + codec fixes - #319
Merged
Merged
Conversation
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.
This was referenced Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ffprobemedia-metadata extraction (#280) runs on the sharedCThreadScheduler— the single worker that also runs hashing and download completion (CHashingTask/CCompletionTask).ffprobeis invoked synchronously viawxExecuteand 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 atPS_COMPLETINGand shutdown stalls behind it.Fix
Move probing onto a dedicated
CMediaProbeThread(modelled onCPartFileHashThread), so a slow or hung probe can only ever delay other probes — never completions or hashing.Bound and make each probe killable. The
ffprobeinvocation is now a nativeposix_spawn/CreateProcesschild instead ofwxExecute: the synchronouswxExecutepath 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 thatEndThreadflips 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-styleffprobe(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
subriptrack advertised "subrip" as the file's codec. It now queriescodec_typealongsidecodec_nameand 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-freeExercising the re-attach path surfaced a latent heap-corruption bug that predates 3.0.1 and is independent of the media-probe change.
CTagkeeps 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>::erasehits this whenever it shifts a string tag over a non-string one — exactly the media tag set[length, bitrate, codec]whenAddTagUniquere-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),logMediaProbecategory.Prefer the video track's codec (then audio) in media probing— codec selection.Test plan
Eight deterministic checks, run on each platform:
MediaProbe: extracted … codec=…thenMedia metadata: … ->(tags attached), under theMedia Probedebug category.known.metkept, the file logsskip (already has metadata); the tags survive inknown.met.FFProbePathpointed at a program that hangs, a probe blocks but EC round-trips stay responsive and completions proceed.ffprobe timed out / cancelled; the daemon stays alive.CTagcrash.h264), notsubrip.