Härtung Download/Rotation (Audit-Batch 1): Scheduler-Freeze, Cooldown-Respekt, Daily-Reset, Kategorisierung
Aus einem adversarisch verifizierten Multi-Agent-Audit (14 confirmed/11 refuted): #1 HIGH Scheduler-Freeze: findNextQueuedItem hatte keinen activeTasks-Guard. Wird ein Item zurueckgesetzt/ueberschrieben, waehrend sein alter Task noch in einem nicht-abbrechbaren await parkt (z.B. Integritaets-Check), liefert findNextQueuedItem dasselbe Item, startItem lehnt es ab ohne activeTasks zu verkleinern → der SYNCHRONE Admission-Loop dreht endlos → Event-Loop friert permanent ein. Fix: `if (this.activeTasks.has(itemId)) continue;`. Repro-Test (ohne Fix haengt sogar der vitest-Timeout — Freeze bewiesen). #2/#3 MED mega_debrid_cooldown:<ms> wurde verworfen: kein Parser (nur das debrid_link-Analogon) → Item lief in den generischen 5s-Exponential-Backoff und fragte cooled Accounts im Sekundentakt erneut an. Neuer parseMegaDebridCooldownRetry (nimmt das frueheste Cooldown-Ende ueber alle Accounts) + Handler VOR transient/generic → Item wartet die echte Cooldown-Zeit. #7/#8 MED Mega per-Account Tages-Usage wurde am Tageswechsel nie zurueckgesetzt (ensureProviderDailyUsageFresh ruecksetzte nur provider/debrid-link), und weil es den Tagesschluessel zuerst weiterstellte, lief auch der Reset in addMegaDebridAccountDailyUsageBytes ins Leere → Accounts blieben den ganzen Tag faelschlich "am Limit" und schrumpften das (neue) Pro-Account-Umwandlungslimit. Fix: megaDebridAccountDailyUsageBytes im Tageswechsel mit zuruecksetzen. #14 LOW classifyAccountFailure: rate_limit-Branch vor quota (quota matchte "limit" in "rate limit" → Fehl-Kategorisierung). 852/852 gruen, tsc unveraendert (6). #4 (empty→until-restart) bewusst deferred.
This commit is contained in:
+9
-9
@@ -2183,6 +2183,15 @@ class MegaDebridClient {
|
||||
return { fatal: true, cooldownMs: 0, message: errorText, category: "skip" };
|
||||
}
|
||||
|
||||
if (/rate.?limit|too.?many|429/i.test(errorText)) {
|
||||
return {
|
||||
fatal: false,
|
||||
cooldownMs: MEGA_DEBRID_ACCOUNT_COOLDOWN_MS,
|
||||
message: `Rate-Limit (${errorText})`,
|
||||
category: "rate_limit"
|
||||
};
|
||||
}
|
||||
|
||||
if (/quota|limit|exceeded|bandwidth/i.test(errorText)) {
|
||||
return {
|
||||
fatal: false,
|
||||
@@ -2202,15 +2211,6 @@ class MegaDebridClient {
|
||||
};
|
||||
}
|
||||
|
||||
if (/rate.?limit|too.?many|429/i.test(errorText)) {
|
||||
return {
|
||||
fatal: false,
|
||||
cooldownMs: MEGA_DEBRID_ACCOUNT_COOLDOWN_MS,
|
||||
message: `Rate-Limit (${errorText})`,
|
||||
category: "rate_limit"
|
||||
};
|
||||
}
|
||||
|
||||
if (/antwort\s+leer|empty\s+response|leere\s+antwort/i.test(errorText)) {
|
||||
return {
|
||||
fatal: false,
|
||||
|
||||
@@ -645,6 +645,20 @@ function parseDebridLinkCooldownRetry(errorText: string): { delayMs: number; det
|
||||
return { delayMs, detail };
|
||||
}
|
||||
|
||||
export function parseMegaDebridCooldownRetry(errorText: string): { delayMs: number; detail: string } | null {
|
||||
const text = String(errorText || "");
|
||||
const matches = [...text.matchAll(/mega_debrid_cooldown:(\d+)/gi)];
|
||||
if (matches.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const delays = matches.map((m) => Number(m[1])).filter((n) => Number.isFinite(n) && n > 0);
|
||||
if (delays.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const delayMs = Math.max(1000, Math.min(15 * 60 * 1000, Math.min(...delays)));
|
||||
return { delayMs, detail: text.replace(/mega_debrid_cooldown:\d+:/i, "").trim() };
|
||||
}
|
||||
|
||||
function parseDebridLinkTerminalFailure(errorText: string): { kind: "invalid_all" | "no_active_key"; detail: string } | null {
|
||||
const raw = String(errorText || "");
|
||||
const match = raw.match(/debrid_link_(invalid_all|no_active_key):(.*)$/i);
|
||||
@@ -7764,6 +7778,7 @@ export class DownloadManager extends EventEmitter {
|
||||
this.settings.providerDailyUsageDay = currentDay;
|
||||
this.settings.providerDailyUsageBytes = {};
|
||||
this.settings.debridLinkApiKeyDailyUsageBytes = {};
|
||||
this.settings.megaDebridAccountDailyUsageBytes = {};
|
||||
this.statsCache = null;
|
||||
this.statsCacheAt = 0;
|
||||
if (persist) {
|
||||
@@ -8474,6 +8489,7 @@ export class DownloadManager extends EventEmitter {
|
||||
const retryAfter = this.retryAfterByItem.get(itemId) || 0;
|
||||
if (retryAfter > now) continue;
|
||||
if (item.status !== "queued" && item.status !== "reconnect_wait") continue;
|
||||
if (this.activeTasks.has(itemId)) continue;
|
||||
if (this.delayPacedStartForItem(item, now)) continue;
|
||||
if (this.shouldDelayStartForItem(item)) continue;
|
||||
|
||||
@@ -9349,6 +9365,23 @@ export class DownloadManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
const megaCooldownRetry = parseMegaDebridCooldownRetry(errorText);
|
||||
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||
active.unrestrictRetries += 1;
|
||||
item.retries += 1;
|
||||
logger.warn(`Mega-Debrid Account-Cooldown: item=${item.fileName || item.id}, retry=${active.unrestrictRetries}/${retryDisplayLimit}, delay=${megaCooldownRetry.delayMs}ms, link=${item.url.slice(0, 80)}`);
|
||||
this.queueRetry(
|
||||
item,
|
||||
active,
|
||||
megaCooldownRetry.delayMs,
|
||||
`Mega-Debrid Cooldown, neuer Versuch in ${Math.ceil(megaCooldownRetry.delayMs / 1000)}s`
|
||||
);
|
||||
item.lastError = megaCooldownRetry.detail || errorText;
|
||||
this.persistSoon();
|
||||
this.emitState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMegaDebridTransientResolveFailure(errorText) && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||
active.unrestrictRetries += 1;
|
||||
item.retries += 1;
|
||||
|
||||
Reference in New Issue
Block a user