mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-28 03:13:51 +02:00
fix: two long-running HLS transcode issues (#355)
* fix: release targets from Output._targets on finalize Long-running HLS transcodes leak memory. Every finalized BufferTarget stays in _targets until the outer Output closes, pinning its buffer. Writer.finalize() already does this cleanup for writer-based flows; extend it to buffer-finalize paths via the public 'finalized' event. * fix: ReadOrchestrator LRU eviction picks only drained workers assert(pendingSlices.length === 0) fires under heavy concurrent reads (e.g. multi-rendition HLS decode from BlobSource). LRU filter only checked !running; workers with queued slices could be evicted. Add pendingSlices.length === 0 to the filter. * fix: export AppendOnlyStreamTarget from index Missing from the re-export; public docs import it by name. * fix: join HLS init segment path with root + playlist path Init path went through _getTarget bare; segments got joined with rootPath + playlist.path. Playlist-relative URI then can't resolve when the playlist lives in a subdirectory. * Fix targets not being cleaned up, fix paused workers with remaining pending slices, modify doc block, fixed isRoot not being changed on proxied requests --------- Co-authored-by: Vanilagy <[email protected]>
This commit is contained in:
co-authored by
Vanilagy
parent
7acae8dda1
commit
b05cdbe7e0
+31
-6
@@ -558,7 +558,7 @@ export class BlobSource extends Source {
|
||||
}
|
||||
}
|
||||
|
||||
worker.running = false;
|
||||
this._orchestrator.signalWorkerStoppedRunning(worker);
|
||||
|
||||
if (worker.aborted) {
|
||||
// MDN: "Calling this method signals a loss of interest in the stream by a consumer."
|
||||
@@ -853,7 +853,7 @@ export class UrlSource extends PathedSource {
|
||||
while (true) {
|
||||
if (worker.currentPos >= worker.targetPos || worker.aborted) {
|
||||
abortController.abort();
|
||||
worker.running = false;
|
||||
this._orchestrator.signalWorkerStoppedRunning(worker);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1201,7 +1201,7 @@ export class StreamSource extends Source {
|
||||
}
|
||||
}
|
||||
|
||||
worker.running = false;
|
||||
this._orchestrator.signalWorkerStoppedRunning(worker);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -1608,6 +1608,8 @@ class ReadOrchestrator {
|
||||
minReadPosition: number,
|
||||
maxReadPosition: number,
|
||||
): MaybePromise<ReadResult | null> {
|
||||
assert(!this.disposed);
|
||||
|
||||
const prefetchRange = this.options.prefetchProfile(innerStart, innerEnd, this.workers);
|
||||
const outerStart = Math.max(prefetchRange.start, minReadPosition);
|
||||
const outerEnd = Math.min(prefetchRange.end, this.fileSize ?? Infinity, maxReadPosition);
|
||||
@@ -1868,7 +1870,11 @@ class ReadOrchestrator {
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
const worker = this.workers[i]!;
|
||||
|
||||
if (!worker.running && (!oldestWorker || worker.age < oldestWorker.age)) {
|
||||
if (
|
||||
!worker.running
|
||||
&& worker.pendingSlices.length === 0
|
||||
&& (!oldestWorker || worker.age < oldestWorker.age)
|
||||
) {
|
||||
oldestIndex = i;
|
||||
oldestWorker = worker;
|
||||
}
|
||||
@@ -1952,13 +1958,18 @@ class ReadOrchestrator {
|
||||
// Here we merge everything into one "megaworker" that spans the entire file. We assume the passed-in worker
|
||||
// is already configured to be a megaworker.
|
||||
|
||||
const uniqueSlices = new Set(worker.pendingSlices);
|
||||
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
const otherWorker = this.workers[i]!;
|
||||
if (otherWorker === worker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
worker.pendingSlices.push(...otherWorker.pendingSlices);
|
||||
for (const slice of otherWorker.pendingSlices) {
|
||||
uniqueSlices.add(slice);
|
||||
}
|
||||
|
||||
otherWorker.aborted = true;
|
||||
otherWorker.pendingSlices.length = 0;
|
||||
this.workers.splice(i, 1);
|
||||
@@ -1967,9 +1978,13 @@ class ReadOrchestrator {
|
||||
|
||||
for (let i = 0; i < this.queuedReads.length; i++) {
|
||||
const queuedRead = this.queuedReads[i]!;
|
||||
worker.pendingSlices.push(...queuedRead.pendingSlices);
|
||||
|
||||
for (const slice of queuedRead.pendingSlices) {
|
||||
uniqueSlices.add(slice);
|
||||
}
|
||||
}
|
||||
|
||||
worker.pendingSlices = [...uniqueSlices];
|
||||
this.queuedReads.length = 0;
|
||||
}
|
||||
|
||||
@@ -2101,6 +2116,16 @@ class ReadOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
signalWorkerStoppedRunning(worker: ReadWorker) {
|
||||
worker.running = false;
|
||||
|
||||
// When a worker stops running, that means it has hit its targetPos. It might still have pendingSlices assigned,
|
||||
// but this is because those pending slices cover data that other workers are assigned to fill. Since targetPos
|
||||
// has been reached, we can confidently say that this worker has completed its share of work on the pending
|
||||
// slices and must no longer care about them.
|
||||
worker.pendingSlices.length = 0;
|
||||
}
|
||||
|
||||
/** Called when a worker reaches the end of the underlying data and must be cleaned up. */
|
||||
onWorkerFinished(worker: ReadWorker) {
|
||||
const index = this.workers.indexOf(worker);
|
||||
|
||||
Reference in New Issue
Block a user