Match JDownloader 2 extraction behavior: normal I/O, -mt2 hybrid
Some checks are pending
Build and Release / build (push) Waiting to run
Some checks are pending
Build and Release / build (push) Waiting to run
- Remove setWindowsBackgroundIO entirely (JD2 uses normal I/O priority) - Keep only CPU priority IDLE (os.setPriority) - Hybrid threads fixed at -mt2 (matches JD2's ~16 MB/s controlled throughput) - Final extraction uses full thread count (unchanged) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a646d516b
commit
3ee3af03cf
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.41",
|
"version": "1.5.42",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -471,9 +471,10 @@ function extractCpuBudgetPercent(): number {
|
|||||||
|
|
||||||
function extractorThreadSwitch(hybridMode = false): string {
|
function extractorThreadSwitch(hybridMode = false): string {
|
||||||
if (hybridMode) {
|
if (hybridMode) {
|
||||||
const cpuCount = Math.max(1, os.cpus().length || 1);
|
// 2 threads during hybrid extraction (download + extract simultaneously).
|
||||||
const hybridThreads = Math.max(1, Math.min(8, Math.floor(cpuCount / 2)));
|
// JDownloader 2 uses in-process 7-Zip-JBinding which naturally limits throughput
|
||||||
return `-mt${hybridThreads}`;
|
// to ~16 MB/s write. 2 UnRAR threads produce similar controlled disk load.
|
||||||
|
return "-mt2";
|
||||||
}
|
}
|
||||||
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) {
|
||||||
@ -486,25 +487,7 @@ function extractorThreadSwitch(hybridMode = false): string {
|
|||||||
return `-mt${threadCount}`;
|
return `-mt${threadCount}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWindowsBackgroundIO(pid: number): void {
|
function lowerExtractProcessPriority(childPid: number | undefined): void {
|
||||||
if (process.platform !== "win32") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// NtSetInformationProcess: set I/O priority to Very Low (0) and Page priority to Very Low (1).
|
|
||||||
// IDLE_PRIORITY_CLASS (set by os.setPriority) only lowers CPU scheduling priority;
|
|
||||||
// it does NOT lower I/O priority on modern Windows (Vista+). This call does.
|
|
||||||
const script = `$c=@'\nusing System;using System.Runtime.InteropServices;\npublic class P{[DllImport("ntdll.dll")]static extern int NtSetInformationProcess(IntPtr h,int c,ref int d,int s);[DllImport("kernel32.dll")]static extern IntPtr OpenProcess(int a,bool i,int p);[DllImport("kernel32.dll")]static extern bool CloseHandle(IntPtr h);public static void S(int pid){IntPtr h=OpenProcess(0x0600,false,pid);if(h==IntPtr.Zero)return;int v=0;NtSetInformationProcess(h,0x21,ref v,4);v=1;NtSetInformationProcess(h,0x27,ref v,4);CloseHandle(h);}}\n'@\nAdd-Type -TypeDefinition $c;[P]::S(${pid})`;
|
|
||||||
try {
|
|
||||||
spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
||||||
windowsHide: true,
|
|
||||||
stdio: "ignore"
|
|
||||||
}).unref();
|
|
||||||
} catch {
|
|
||||||
// best-effort: powershell may not be available
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lowerExtractProcessPriority(childPid: number | undefined, lowIo = false): void {
|
|
||||||
if (process.platform !== "win32") {
|
if (process.platform !== "win32") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -513,15 +496,12 @@ function lowerExtractProcessPriority(childPid: number | undefined, lowIo = false
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// IDLE_PRIORITY_CLASS: lowers CPU scheduling priority only
|
// IDLE_PRIORITY_CLASS: lowers CPU scheduling priority so extraction
|
||||||
|
// doesn't starve other processes. I/O priority stays Normal (like JDownloader 2).
|
||||||
os.setPriority(pid, os.constants.priority.PRIORITY_LOW);
|
os.setPriority(pid, os.constants.priority.PRIORITY_LOW);
|
||||||
} catch {
|
} catch {
|
||||||
// ignore: priority lowering is best-effort
|
// ignore: priority lowering is best-effort
|
||||||
}
|
}
|
||||||
// Lower I/O + page priority only in hybrid mode (download + extract simultaneously)
|
|
||||||
if (lowIo) {
|
|
||||||
setWindowsBackgroundIO(pid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtractSpawnResult = {
|
type ExtractSpawnResult = {
|
||||||
@ -578,8 +558,7 @@ function runExtractCommand(
|
|||||||
args: string[],
|
args: string[],
|
||||||
onChunk?: (chunk: string) => void,
|
onChunk?: (chunk: string) => void,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
timeoutMs?: number,
|
timeoutMs?: number
|
||||||
hybridMode = false
|
|
||||||
): Promise<ExtractSpawnResult> {
|
): Promise<ExtractSpawnResult> {
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
return Promise.resolve({ ok: false, missingCommand: false, aborted: true, timedOut: false, errorText: "aborted:extract" });
|
return Promise.resolve({ ok: false, missingCommand: false, aborted: true, timedOut: false, errorText: "aborted:extract" });
|
||||||
@ -589,7 +568,7 @@ function runExtractCommand(
|
|||||||
let settled = false;
|
let settled = false;
|
||||||
let output = "";
|
let output = "";
|
||||||
const child = spawn(command, args, { windowsHide: true });
|
const child = spawn(command, args, { windowsHide: true });
|
||||||
lowerExtractProcessPriority(child.pid, hybridMode);
|
lowerExtractProcessPriority(child.pid);
|
||||||
let timeoutId: NodeJS.Timeout | null = null;
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
let timedOutByWatchdog = false;
|
let timedOutByWatchdog = false;
|
||||||
let abortedBySignal = false;
|
let abortedBySignal = false;
|
||||||
@ -846,7 +825,7 @@ async function runExternalExtractInner(
|
|||||||
}
|
}
|
||||||
bestPercent = parsed;
|
bestPercent = parsed;
|
||||||
onArchiveProgress?.(bestPercent);
|
onArchiveProgress?.(bestPercent);
|
||||||
}, signal, timeoutMs, hybridMode);
|
}, signal, timeoutMs);
|
||||||
|
|
||||||
if (!result.ok && usePerformanceFlags && isUnsupportedExtractorSwitchError(result.errorText)) {
|
if (!result.ok && usePerformanceFlags && isUnsupportedExtractorSwitchError(result.errorText)) {
|
||||||
usePerformanceFlags = false;
|
usePerformanceFlags = false;
|
||||||
@ -860,7 +839,7 @@ async function runExternalExtractInner(
|
|||||||
}
|
}
|
||||||
bestPercent = parsed;
|
bestPercent = parsed;
|
||||||
onArchiveProgress?.(bestPercent);
|
onArchiveProgress?.(bestPercent);
|
||||||
}, signal, timeoutMs, hybridMode);
|
}, signal, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user