Fix extraction speed and UI label updates

- Change OS priority from IDLE/BELOW_NORMAL to NORMAL/BELOW_NORMAL so
  extraction runs at full speed (matching manual 7-Zip/WinRAR performance)
- Use "high" priority in both hybrid and full extraction paths
- Increase hybrid extraction threads from hardcoded 2 to dynamic
  calculation (half CPU count, min 2, max 8)
- Fix emitState forced emit being silently dropped when a non-forced
  timer was already pending — forced emits now always replace pending
  timers to ensure immediate UI feedback during extraction transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-05 05:28:42 +01:00
parent 375ec36781
commit 1cda391dfe
2 changed files with 25 additions and 18 deletions

View File

@ -3570,14 +3570,16 @@ export class DownloadManager extends EventEmitter {
this.emit("state", this.getSnapshot()); this.emit("state", this.getSnapshot());
return; return;
} }
// Too soon — schedule deferred forced emit // Too soon — replace any pending timer with a shorter forced-emit timer
if (!this.stateEmitTimer) { if (this.stateEmitTimer) {
clearTimeout(this.stateEmitTimer);
this.stateEmitTimer = null;
}
this.stateEmitTimer = setTimeout(() => { this.stateEmitTimer = setTimeout(() => {
this.stateEmitTimer = null; this.stateEmitTimer = null;
this.lastStateEmitAt = nowMs(); this.lastStateEmitAt = nowMs();
this.emit("state", this.getSnapshot()); this.emit("state", this.getSnapshot());
}, MIN_FORCE_GAP_MS - sinceLastEmit); }, MIN_FORCE_GAP_MS - sinceLastEmit);
}
return; return;
} }
if (this.stateEmitTimer) { if (this.stateEmitTimer) {
@ -6400,7 +6402,7 @@ export class DownloadManager extends EventEmitter {
packageId, packageId,
hybridMode: true, hybridMode: true,
maxParallel: this.settings.maxParallelExtract || 2, maxParallel: this.settings.maxParallelExtract || 2,
extractCpuPriority: this.settings.extractCpuPriority, extractCpuPriority: "high",
onProgress: (progress) => { onProgress: (progress) => {
if (progress.phase === "preparing") { if (progress.phase === "preparing") {
pkg.postProcessLabel = progress.archiveName || "Vorbereiten..."; pkg.postProcessLabel = progress.archiveName || "Vorbereiten...";
@ -6762,8 +6764,8 @@ export class DownloadManager extends EventEmitter {
packageId, packageId,
skipPostCleanup: true, skipPostCleanup: true,
maxParallel: this.settings.maxParallelExtract || 2, maxParallel: this.settings.maxParallelExtract || 2,
// All downloads finished — use highest configured priority so extraction // All downloads finished — use NORMAL OS priority so extraction runs at
// isn't starved. "high" maps to BELOW_NORMAL instead of the default IDLE. // full speed (matching manual 7-Zip/WinRAR speed).
extractCpuPriority: "high", extractCpuPriority: "high",
onProgress: (progress) => { onProgress: (progress) => {
if (progress.phase === "preparing") { if (progress.phase === "preparing") {

View File

@ -600,8 +600,8 @@ function extractCpuBudgetFromPriority(priority?: string): number {
function extractOsPriority(priority?: string): number { function extractOsPriority(priority?: string): number {
switch (priority) { switch (priority) {
case "high": return os.constants.priority.PRIORITY_BELOW_NORMAL; case "high": return os.constants.priority.PRIORITY_NORMAL;
default: return os.constants.priority.PRIORITY_LOW; default: return os.constants.priority.PRIORITY_BELOW_NORMAL;
} }
} }
@ -615,10 +615,15 @@ function extractCpuBudgetPercent(priority?: string): number {
function extractorThreadSwitch(hybridMode = false, priority?: string): string { function extractorThreadSwitch(hybridMode = false, priority?: string): string {
if (hybridMode) { if (hybridMode) {
// 2 threads during hybrid extraction (download + extract simultaneously). // Use half the CPU budget during hybrid extraction to leave headroom for
// JDownloader 2 uses in-process 7-Zip-JBinding which naturally limits throughput // concurrent downloads. Falls back to at least 2 threads.
// to ~16 MB/s write. 2 UnRAR threads produce similar controlled disk load. const envValue = Number(process.env.RD_EXTRACT_THREADS ?? NaN);
return "-mt2"; 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); const envValue = Number(process.env.RD_EXTRACT_THREADS ?? NaN);
if (Number.isFinite(envValue) && envValue >= 1 && envValue <= 32) { if (Number.isFinite(envValue) && envValue >= 1 && envValue <= 32) {
@ -640,8 +645,8 @@ function lowerExtractProcessPriority(childPid: number | undefined, cpuPriority?:
return; return;
} }
try { try {
// Lowers CPU scheduling priority so extraction doesn't starve other processes. // Sets CPU scheduling priority for the extraction process.
// high → BELOW_NORMAL, middle/low → IDLE. I/O priority stays Normal (like JDownloader 2). // high → NORMAL (full speed), default → BELOW_NORMAL. I/O priority stays Normal.
os.setPriority(pid, extractOsPriority(cpuPriority)); os.setPriority(pid, extractOsPriority(cpuPriority));
} catch { } catch {
// ignore: priority lowering is best-effort // ignore: priority lowering is best-effort