mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-01 04:43: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
+14
-18
@@ -378,7 +378,7 @@ export class Output<
|
||||
/** @internal */
|
||||
_muxer: Muxer;
|
||||
/** @internal */
|
||||
_targets = new Set<Target>();
|
||||
_unfinalizedTargets = new Set<Target>();
|
||||
/** @internal */
|
||||
_rootWriterPromise: Promise<Writer> | null = null;
|
||||
/** @internal */
|
||||
@@ -439,12 +439,7 @@ export class Output<
|
||||
throw new TypeError('options.target must be a Target or a PathedTarget.');
|
||||
}
|
||||
if (options.target instanceof Target) {
|
||||
if (options.target._output) {
|
||||
throw new Error('Target is already used for another output.');
|
||||
}
|
||||
|
||||
options.target._output = this;
|
||||
this._targets.add(options.target);
|
||||
this._rememberTarget(options.target);
|
||||
}
|
||||
if (
|
||||
options.initTarget !== undefined
|
||||
@@ -466,8 +461,7 @@ export class Output<
|
||||
|
||||
this._initTarget = options.initTarget ?? null;
|
||||
if (this._initTarget instanceof Target) {
|
||||
this._initTarget._output = this;
|
||||
this._targets.add(this._initTarget);
|
||||
this._rememberTarget(this._initTarget);
|
||||
}
|
||||
|
||||
this._muxer = options.format._createMuxer(this);
|
||||
@@ -498,18 +492,23 @@ export class Output<
|
||||
assert(this._target instanceof PathedTarget);
|
||||
|
||||
const target = await this._getTargetValidated(request);
|
||||
target._output = this;
|
||||
this._emit('target', { target, request, isRoot: request.isRoot });
|
||||
|
||||
if (this.state === 'canceled') {
|
||||
await target._close();
|
||||
} else {
|
||||
this._targets.add(target);
|
||||
this._rememberTarget(target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_rememberTarget(target: Target) {
|
||||
this._unfinalizedTargets.add(target);
|
||||
target.on('finalized', () => this._unfinalizedTargets.delete(target), { once: true });
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async _getInitTarget(): Promise<T> {
|
||||
assert(this._initTarget !== null);
|
||||
@@ -519,12 +518,11 @@ export class Output<
|
||||
}
|
||||
|
||||
const target = await this._initTarget();
|
||||
target._output = this;
|
||||
|
||||
if (this.state === 'canceled') {
|
||||
await target._close();
|
||||
} else {
|
||||
this._targets.add(target);
|
||||
this._rememberTarget(target);
|
||||
}
|
||||
|
||||
return target;
|
||||
@@ -558,13 +556,11 @@ export class Output<
|
||||
const result = this._getTargetValidated(request);
|
||||
|
||||
const handleResult = (target: T) => {
|
||||
target._output = this;
|
||||
|
||||
if (this.state === 'canceled') {
|
||||
// Promise thrown away here, but no way to surface it to the user really
|
||||
void target._close();
|
||||
} else {
|
||||
this._targets.add(target);
|
||||
this._rememberTarget(target);
|
||||
}
|
||||
|
||||
this._emit('target', { target, request, isRoot: true });
|
||||
@@ -849,8 +845,8 @@ export class Output<
|
||||
const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(true)); // Force close
|
||||
await Promise.all(promises);
|
||||
|
||||
await Promise.all([...this._targets].map(target => target._close()));
|
||||
this._targets.clear();
|
||||
await Promise.all([...this._unfinalizedTargets].map(target => target._close()));
|
||||
this._unfinalizedTargets.clear();
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user