-
v2.0.0-beta.2 Stable
released this
2026-03-08 19:10:03 +01:00 | 6 commits to main since this releasev2.0.0-beta.2 — Code Review Bugfixes + Separate Beta Identity
Critical Bugfixes (6)
-
Post-processor: double attempts increment — Both
onProgressandonArchiveFailurecallbacks incrementedarchiveState.attempts++, so each extraction failure consumed 2 attempts againstmaxAttempts=3. Effectively reduced retry budget from 3 to 1. Fixed by only incrementing inonArchiveFailure(the authoritative failure path). -
Post-processor: slot leak on abort — When the abort signal fired after
acquireSlot()but before thetry/finallyblock, the early return skippedreleaseSlot(). Each leaked slot permanently reduced extraction parallelism. Fixed by releasing the slot before the early return. -
Scheduler: global watchdog high-water mark never decreases — After a stall event triggered download retries,
bytesAtHeartbeatreset to 0 butlastGlobalProgressBytesstayed at the old peak. SincetotalBytes < lastGlobalProgressByteswas permanently true, the watchdog fired every cycle until downloads exceeded the old peak. Fixed by resetting the high-water mark alongside the timestamp after emittingglobal-stall. -
Pipeline + Download-manager: path traversal in
isPathInsideDir—path.resolve("C:\downloads\pack-evil").startsWith(path.resolve("C:\downloads\pack"))returnedtruebecause no trailing separator was enforced. An attacker-controlled filename could escape the intended directory. Fixed by appendingpath.septo the directory path before thestartsWithcheck. -
Retry-manager: per-kind exhaustion bypassed by shelving — The shelve threshold check (totalFailures ≥ 15) ran before the per-kind exhaustion check (kindCount > maxRetries). Items that exhausted a specific error kind (e.g., 4× NetworkReset with maxRetries=3) would get shelved instead of permanently failed, halving counters and allowing infinite retries. Fixed by checking per-kind exhaustion first.
-
Retry-manager: infinite shelve cycling — No cap on
shelveCount, so items with mixed error kinds could shelve indefinitely (every 15 failures: halve counters → accumulate 15 more → shelve again → forever). AddedMAX_SHELVE_COUNT = 5— after 5 shelve cycles, the item fails permanently.
Important Bugfixes (10)
-
Scheduler: stale retryDelays and providerCooldowns on start() — Previous run's retry delays and provider cooldowns persisted into the next run, causing items to appear delayed or providers to appear cooled down when they shouldn't be. Fixed by clearing both maps in
start(). -
Scheduler: repeated stall-detected events —
checkStalls()re-emittedstall-detectedevery 2 seconds for slots already being aborted (abortReason ≠"none"). Fixed by skipping slots withabortReason !== "none". -
Download-manager:
cleanupAfterExtractionwrong directory —removeDownloadLinkArtifacts()was called withpkg.extractDir(extraction output) instead ofpkg.outputDir(download directory where .lnk files actually are). Link artifacts were never cleaned up. -
Download-manager:
normalizeSessionStatusesmissing "extracting" — On app restart, packages stuck in "extracting" status weren't reset to "queued". They appeared stuck in the UI with no way to restart them. -
Download-manager:
stop()leaves stale activeTasks entries —this.activeTasksmap was never cleared on stop, leaving ghost references to aborted slots. Fixed by addingthis.activeTasks.clear()after aborting all slots. -
Download-manager:
cachedDirectUrlsmemory leak — After a successful download, the direct URL was deleted then immediately re-inserted with the same item ID key. Since completed items never reuse cached URLs, the map grew unbounded. Removed the re-insertion. -
Stream-writer: duplicate truncation code — The same
fs.promises.truncate(effectiveTargetPath, written)block appeared twice in the error path (lines 542-544 and 548-549). Removed the first duplicate. -
Stream-writer: 5-minute drain wait in finally after error —
alignedFlush(true)in thefinallyblock calledwaitDrain()even whenbodyErrorwas already set, potentially blocking for up to 5 minutes on a stuck stream. Fixed by skippingalignedFlushwhen an error already exists. -
Stream-writer: speed limiter stale elapsed after sleep — After the speed limiter's
await sleep(), theelapsedvariable still held the pre-sleep value. The window reset check (elapsed >= 1) used stale data, preventing the speed window from resetting and causing progressively longer sleeps. Fixed by re-readingnowMs()after sleep. -
Error-classifier: missing HTTP 401 and 410 classification — HTTP 401 (Unauthorized) fell through to
Unknowninstead ofForbidden. HTTP 410 (Gone) fell through toUnknowninstead ofNotFound. Added explicit cases for both status codes.
Separate Beta Identity
- appId changed to
com.sucukdeluxe.realdebrid-beta - productName changed to
Real-Debrid-Downloader Beta - package name changed to
real-debrid-downloader-beta - Installs to separate directory (
%APPDATA%/Real-Debrid-Downloader Beta/) - Stable and Beta can run side by side without interfering
Testing
- 216 unit tests passing (1 new test added for per-kind exhaustion priority)
- Existing tests updated to match new shelve/kind-exhaustion priority order and HTTP 401 classification
- Build compiles cleanly (tsup + vite)
-