Compare commits

..

No commits in common. "4d2f11a96df8d3e6dcffd5bd7fd54a8343849e81" and "7be2d2e14839a79bff6142f759e5d6263862e778" have entirely different histories.

3 changed files with 9 additions and 60 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.7.141", "version": "1.7.140",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -142,30 +142,7 @@ function setDebridLinkKeyCooldownState(
clearDebridLinkKeyCooldownState(keyId); clearDebridLinkKeyCooldownState(keyId);
return; return;
} }
// Cooldown set: max-wins. When 8 parallel items hit floodDetected on the debridLinkKeyCooldowns.set(keyId, Date.now() + Math.max(1000, Math.floor(cooldownMs)));
// same key, each computes its own retry-after and calls setDebridLinkKey
// CooldownState. Without max-wins, the LAST setter could shorten the
// cooldown (e.g. one item got a 1h Retry-After header, another got the
// default 2 min — without max-wins the 2 min would overwrite the 1h).
// Quota and rate_limit categories take priority over generic temporary
// cooldowns regardless of duration to preserve the more-specific signal.
const newUntil = Date.now() + Math.max(1000, Math.floor(cooldownMs));
const existingUntil = Number(debridLinkKeyCooldowns.get(keyId) || 0);
const existingDetail = debridLinkKeyCooldownDetails.get(keyId);
const newIsStrongCategory = category === "rate_limit" || category === "quota" || category === "invalid";
const existingIsStrongCategory = existingDetail
? (existingDetail.category === "rate_limit" || existingDetail.category === "quota" || existingDetail.category === "invalid")
: false;
// Keep existing if it's still active and either lasts longer or has a stronger category
if (existingUntil > Date.now()) {
if (existingUntil >= newUntil && (!newIsStrongCategory || existingIsStrongCategory)) {
return;
}
if (existingIsStrongCategory && !newIsStrongCategory) {
return;
}
}
debridLinkKeyCooldowns.set(keyId, newUntil);
debridLinkKeyCooldownDetails.set(keyId, { message, category }); debridLinkKeyCooldownDetails.set(keyId, { message, category });
setDebridLinkKeyRuntimeStatus(keyId, mapDebridLinkCooldownCategoryToRuntimeState(category), message); setDebridLinkKeyRuntimeStatus(keyId, mapDebridLinkCooldownCategoryToRuntimeState(category), message);
} }
@ -2705,32 +2682,13 @@ class DebridLinkClient {
} }
if (isRetryableErrorText(errorText) || /debrid-link.*(json|html)/i.test(errorText)) { if (isRetryableErrorText(errorText) || /debrid-link.*(json|html)/i.test(errorText)) {
// Distinguish a single transient transport error (timeout, network blip,
// ECONNRESET) from a real API/server problem. Single timeouts shouldn't
// park a key for 2 full minutes — that just delays parallel work for
// no reason. Use a short 15s cooldown for transport, full 2min only
// for things that look like server-side faults (5xx HTML pages, etc).
const isTransport = /timeout|network|fetch failed|aborted|econnreset|enotfound|etimedout|socket/i.test(errorText)
&& !(error instanceof DebridLinkApiError);
return { return {
fatal: false, fatal: false,
cooldownMs: isTransport ? 15_000 : DEBRID_LINK_KEY_COOLDOWN_MS, cooldownMs: DEBRID_LINK_KEY_COOLDOWN_MS,
message: errorText || "temporärer Transportfehler" message: errorText || "temporärer Transportfehler"
}; };
} }
// HTTP 200 with success:false but no recognizable error code: don't kill
// the item permanently. Treat as a temporary blip — same key can be tried
// again after a short cooldown, or another key picked up.
if (errorText && /success.*false|kein.*json|empty.*response/i.test(errorText)) {
return {
fatal: false,
cooldownMs: 30_000,
message: errorText,
category: "temporary"
};
}
return { return {
fatal: true, fatal: true,
cooldownMs: 0, cooldownMs: 0,

View File

@ -8687,21 +8687,12 @@ export class DownloadManager extends EventEmitter {
active.unrestrictRetries += 1; active.unrestrictRetries += 1;
item.retries += 1; item.retries += 1;
const failureProvider = this.getProviderFailureKeyForItem(item); const failureProvider = this.getProviderFailureKeyForItem(item);
// Debrid-Link manages its own per-key cooldowns in debrid.ts. The this.recordProviderFailure(failureProvider);
// provider-wide circuit breaker would double-block all Debrid-Link if (isProviderBusyUnrestrictError(errorText) || isTemporaryUnrestrictError(errorText)) {
// keys when only one key (or a transient transport hiccup) failed. const busyCooldownMs = isTemporaryUnrestrictError(errorText)
// Skip recordProviderFailure / applyProviderBusyBackoff entirely ? Math.min(180000, 20000 + Number(active.unrestrictRetries || 0) * 10000)
// for any Debrid-Link-flavoured error message, not just the : Math.min(60000, 12000 + Number(active.unrestrictRetries || 0) * 3000);
// debrid_link_cooldown sentinel that's caught above. this.applyProviderBusyBackoff(failureProvider, busyCooldownMs);
const isDebridLinkError = /debrid-link|debrid_link/i.test(errorText) || failureProvider === "debridlink";
if (!isDebridLinkError) {
this.recordProviderFailure(failureProvider);
if (isProviderBusyUnrestrictError(errorText) || isTemporaryUnrestrictError(errorText)) {
const busyCooldownMs = isTemporaryUnrestrictError(errorText)
? Math.min(180000, 20000 + Number(active.unrestrictRetries || 0) * 10000)
: Math.min(60000, 12000 + Number(active.unrestrictRetries || 0) * 3000);
this.applyProviderBusyBackoff(failureProvider, busyCooldownMs);
}
} }
// Escalating backoff: 5s, 7.5s, 11s, 17s, 25s, 38s, ... up to 120s // Escalating backoff: 5s, 7.5s, 11s, 17s, 25s, 38s, ... up to 120s
let unrestrictDelayMs = Math.min(120000, Math.floor(5000 * Math.pow(1.5, active.unrestrictRetries - 1))); let unrestrictDelayMs = Math.min(120000, Math.floor(5000 * Math.pow(1.5, active.unrestrictRetries - 1)));