diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 4576772..65bab73 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -1918,14 +1918,20 @@ export class DownloadManager extends EventEmitter { private packagePostProcessTasks = new Map>(); - private packagePostProcessAbortControllers = new Map(); + private packagePostProcessAbortControllers = new Map(); + + private packagePostProcessRunOwnerByController = new WeakMap(); private packageDeferredPostProcessAbortControllers = new Map(); + private packageDeferredRunOwnerByController = new WeakMap(); + private packageDeferredPostProcessTasks = new Map>>(); private packageHybridPostProcessControllers = new Map>(); + private packageHybridRunOwnerByController = new WeakMap(); + private packageHybridPostProcessTasks = new Map>>(); private packagePostProcessVersions = new Map(); @@ -6805,7 +6811,7 @@ export class DownloadManager extends EventEmitter { this.speedBytesLastWindow = 0; this.speedBytesPerPackage.clear(); this.speedEventsHead = 0; - this.abortPostProcessing("stop"); + this.abortPostProcessing("stop", stoppedRunContext?.id); for (const waiter of this.packagePostProcessWaiters) { waiter.resolve(); } this.packagePostProcessWaiters = []; this.packagePostProcessActive = 0; @@ -8381,9 +8387,13 @@ export class DownloadManager extends EventEmitter { return claimed; } - private abortPostProcessing(reason: string): void { - for (const [packageId, controller] of this.packagePostProcessAbortControllers.entries()) { - if (!controller.signal.aborted) { + private abortPostProcessing(reason: string, runContextId?: string): void { + for (const [packageId, controller] of this.packagePostProcessAbortControllers.entries()) { + const owner = this.packagePostProcessRunOwnerByController.get(controller); + if (runContextId !== undefined && owner !== undefined && owner !== null && owner !== runContextId) { + continue; + } + if (!controller.signal.aborted) { controller.abort(reason); } @@ -8410,16 +8420,24 @@ export class DownloadManager extends EventEmitter { } } } - } - - for (const controller of this.packageDeferredPostProcessAbortControllers.values()) { - if (!controller.signal.aborted) { + } + + for (const controller of this.packageDeferredPostProcessAbortControllers.values()) { + const owner = this.packageDeferredRunOwnerByController.get(controller); + if (runContextId !== undefined && owner !== undefined && owner !== null && owner !== runContextId) { + continue; + } + if (!controller.signal.aborted) { controller.abort(reason); } } - for (const hybridSet of this.packageHybridPostProcessControllers.values()) { - for (const controller of hybridSet) { - if (!controller.signal.aborted) { + for (const hybridSet of this.packageHybridPostProcessControllers.values()) { + for (const controller of hybridSet) { + const owner = this.packageHybridRunOwnerByController.get(controller); + if (runContextId !== undefined && owner !== undefined && owner !== null && owner !== runContextId) { + continue; + } + if (!controller.signal.aborted) { controller.abort(reason); } } @@ -8473,6 +8491,7 @@ export class DownloadManager extends EventEmitter { const abortController = new AbortController(); this.packagePostProcessAbortControllers.set(packageId, abortController); + this.packagePostProcessRunOwnerByController.set(abortController, this.getPackageResultRunOwner(packageId)); const queuedPackage = this.session.packages[packageId]; if (queuedPackage) { queuedPackage.postProcessQueuedAt = queuedPackage.postProcessQueuedAt || nowMs(); @@ -12367,6 +12386,16 @@ export class DownloadManager extends EventEmitter { } } + private getPackageResultRunOwner(packageId: string): string | null { + const generation = this.getPackageResultGeneration(packageId); + for (const context of this.runContexts.values()) { + if (context.packageGenerations.get(packageId) === generation) { + return context.id; + } + } + return null; + } + private queueNotificationEvent(notification: NotificationEvent): void { if (!this.enqueueNotificationCallback || !String(this.settings.notifyUrl || "").trim()) { return; @@ -13389,6 +13418,7 @@ export class DownloadManager extends EventEmitter { if (result.extracted > 0) { this.trackPackagePostProcessResult(packageId); const hybridController = new AbortController(); + this.packageHybridRunOwnerByController.set(hybridController, this.getPackageResultRunOwner(packageId)); let hybridSet = this.packageHybridPostProcessControllers.get(packageId); if (!hybridSet) { hybridSet = new Set(); @@ -14093,8 +14123,9 @@ export class DownloadManager extends EventEmitter { if (replacedController && !replacedController.signal.aborted) { replacedController.abort("deferred_replaced"); } - const deferredController = new AbortController(); - this.packageDeferredPostProcessAbortControllers.set(packageId, deferredController); + const deferredController = new AbortController(); + this.packageDeferredPostProcessAbortControllers.set(packageId, deferredController); + this.packageDeferredRunOwnerByController.set(deferredController, this.getPackageResultRunOwner(packageId)); const deferredVersion = this.getPackagePostProcessVersion(packageId); const shouldAbort = (): boolean => !this.isDeferredPostProcessStillCurrent(packageId, pkg, deferredVersion, deferredController.signal); const throwIfAborted = (): void => this.throwIfDeferredPostProcessAborted(packageId, pkg, deferredVersion, deferredController.signal); @@ -14252,9 +14283,13 @@ export class DownloadManager extends EventEmitter { || reason.includes("package_removed") || reason === "reset" || reason === "cancel" - || reason === "overwrite" - || reason === "skip" - || reason === "package_toggle") { + || reason === "overwrite" + || reason === "skip" + || reason === "package_toggle" + || reason === "stop" + || reason === "shutdown" + || reason === "Error: stop" + || reason === "Error: shutdown") { logger.info(`Deferred Post-Extraction abgebrochen: pkg=${pkg.name}, reason=${reason}`); } else { pkg.cleanupErrorCategory = reason.slice(0, 256) || "cleanup"; diff --git a/tests/notify-hooks.test.ts b/tests/notify-hooks.test.ts index 1bf3433..002c8e7 100644 --- a/tests/notify-hooks.test.ts +++ b/tests/notify-hooks.test.ts @@ -755,6 +755,7 @@ describe("authoritative run completion", () => { await Promise.allSettled(deferredTasks); await flushNotifications(); + expect(packageA.cleanupErrorCategory || "").toBe(""); expect(events.filter((event) => event.type === "package_completed")).toHaveLength(1); expect(events.filter((event) => event.type === "run_completed")).toHaveLength(1); expect(history.map((entry) => entry.name)).toEqual([packageA.name]);