diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 5ec2be9..dd006eb 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -3570,14 +3570,16 @@ export class DownloadManager extends EventEmitter { this.emit("state", this.getSnapshot()); return; } - // Too soon — schedule deferred forced emit - if (!this.stateEmitTimer) { - this.stateEmitTimer = setTimeout(() => { - this.stateEmitTimer = null; - this.lastStateEmitAt = nowMs(); - this.emit("state", this.getSnapshot()); - }, MIN_FORCE_GAP_MS - sinceLastEmit); + // Too soon — replace any pending timer with a shorter forced-emit timer + if (this.stateEmitTimer) { + clearTimeout(this.stateEmitTimer); + this.stateEmitTimer = null; } + this.stateEmitTimer = setTimeout(() => { + this.stateEmitTimer = null; + this.lastStateEmitAt = nowMs(); + this.emit("state", this.getSnapshot()); + }, MIN_FORCE_GAP_MS - sinceLastEmit); return; } if (this.stateEmitTimer) { @@ -6400,7 +6402,7 @@ export class DownloadManager extends EventEmitter { packageId, hybridMode: true, maxParallel: this.settings.maxParallelExtract || 2, - extractCpuPriority: this.settings.extractCpuPriority, + extractCpuPriority: "high", onProgress: (progress) => { if (progress.phase === "preparing") { pkg.postProcessLabel = progress.archiveName || "Vorbereiten..."; @@ -6762,8 +6764,8 @@ export class DownloadManager extends EventEmitter { packageId, skipPostCleanup: true, maxParallel: this.settings.maxParallelExtract || 2, - // All downloads finished — use highest configured priority so extraction - // isn't starved. "high" maps to BELOW_NORMAL instead of the default IDLE. + // All downloads finished — use NORMAL OS priority so extraction runs at + // full speed (matching manual 7-Zip/WinRAR speed). extractCpuPriority: "high", onProgress: (progress) => { if (progress.phase === "preparing") { diff --git a/src/main/extractor.ts b/src/main/extractor.ts index da46a27..501c3e9 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -600,8 +600,8 @@ function extractCpuBudgetFromPriority(priority?: string): number { function extractOsPriority(priority?: string): number { switch (priority) { - case "high": return os.constants.priority.PRIORITY_BELOW_NORMAL; - default: return os.constants.priority.PRIORITY_LOW; + case "high": return os.constants.priority.PRIORITY_NORMAL; + default: return os.constants.priority.PRIORITY_BELOW_NORMAL; } } @@ -615,10 +615,15 @@ function extractCpuBudgetPercent(priority?: string): number { function extractorThreadSwitch(hybridMode = false, priority?: string): string { if (hybridMode) { - // 2 threads during hybrid extraction (download + extract simultaneously). - // JDownloader 2 uses in-process 7-Zip-JBinding which naturally limits throughput - // to ~16 MB/s write. 2 UnRAR threads produce similar controlled disk load. - return "-mt2"; + // Use half the CPU budget during hybrid extraction to leave headroom for + // concurrent downloads. Falls back to at least 2 threads. + const envValue = Number(process.env.RD_EXTRACT_THREADS ?? NaN); + if (Number.isFinite(envValue) && envValue >= 1 && envValue <= 32) { + return `-mt${Math.floor(envValue)}`; + } + const cpuCount = Math.max(1, os.cpus().length || 1); + const hybridThreads = Math.max(2, Math.min(8, Math.floor(cpuCount / 2))); + return `-mt${hybridThreads}`; } const envValue = Number(process.env.RD_EXTRACT_THREADS ?? NaN); if (Number.isFinite(envValue) && envValue >= 1 && envValue <= 32) { @@ -640,8 +645,8 @@ function lowerExtractProcessPriority(childPid: number | undefined, cpuPriority?: return; } try { - // Lowers CPU scheduling priority so extraction doesn't starve other processes. - // high → BELOW_NORMAL, middle/low → IDLE. I/O priority stays Normal (like JDownloader 2). + // Sets CPU scheduling priority for the extraction process. + // high → NORMAL (full speed), default → BELOW_NORMAL. I/O priority stays Normal. os.setPriority(pid, extractOsPriority(cpuPriority)); } catch { // ignore: priority lowering is best-effort