Skip to content

Commit db651f1

Browse files
committed
fix(engine): give the ffmpeg children the signal that actually aborts them
The internal AbortController exists so a fatal FX error can cancel in-flight ffmpeg before the finally-block deletes workDir. It reached the entry guard and applyAudioFxChain, and nothing else: the trim, the video extract and the download were all still handed the CALLER's signal. So `internalController .abort()` cancelled nothing, and the `rmSync(workDir, { recursive: true })` immediately after it ran while those children were still writing into the directory. Concretely: track A's FX render fails while track B is mid-trim, with no external cancellation. B's ffmpeg keeps writing `${id}-trimmed.wav` into an unlinked directory until it finishes. The comment above the controller describes a mechanism that was never wired to the processes it names. The download also had no signal at all, though downloadToTemp accepts one. No test: the three changes are which variable is passed to a child process helper, and observing the difference means driving real ffmpeg to the point of cancellation. The old wiring's inertness is provable by reading — the caller's signal is never aborted by this function — and the new wiring is the same three calls with the controller the function already built.
1 parent a56a03d commit db651f1

1 file changed

Lines changed: 9 additions & 3 deletions

File tree

packages/engine/src/services/audioMixer.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,12 @@ export async function processCompositionAudio(
765765
// be able to abort the in-flight ffmpeg runs before the finally-block removes
766766
// workDir out from under them. Chained off the caller's signal so external
767767
// cancellation still behaves as before.
768+
//
769+
// Every child that can outlive a sibling's failure has to be given THIS
770+
// signal, not the caller's: the trim, the video extract and the download all
771+
// took `signal`, so `internalController.abort()` cancelled nothing and the
772+
// `rmSync(workDir)` on the next line ran while their ffmpeg children were
773+
// still writing into it.
768774
const internalController = new AbortController();
769775
const effectiveSignal = internalController.signal;
770776
if (signal) {
@@ -795,7 +801,7 @@ export async function processCompositionAudio(
795801

796802
if (isHttpUrl(srcPath)) {
797803
try {
798-
srcPath = await downloadToTemp(srcPath, workDir);
804+
srcPath = await downloadToTemp(srcPath, workDir, undefined, effectiveSignal);
799805
} catch (err: unknown) {
800806
failures.push(
801807
downloadFailure(err instanceof Error ? err.message : String(err), element.id),
@@ -842,7 +848,7 @@ export async function processCompositionAudio(
842848
startTime: element.mediaStart,
843849
duration: element.end - element.start,
844850
},
845-
signal,
851+
effectiveSignal,
846852
config,
847853
);
848854
if (!extractResult.success) {
@@ -868,7 +874,7 @@ export async function processCompositionAudio(
868874
trimmedPath,
869875
element.mediaStart,
870876
element.end - element.start,
871-
signal,
877+
effectiveSignal,
872878
config,
873879
);
874880
if (!prepResult.success) {

0 commit comments

Comments
 (0)