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:
@@ -6721,6 +6721,88 @@ describe("download manager", () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
});
|
||||
|
||||
it("resets Mega-Debrid per-account daily usage at the day boundary (not just provider/debrid-link)", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||
tempDirs.push(root);
|
||||
const acctId = getMegaDebridAccountId("mega-user");
|
||||
|
||||
const manager = new DownloadManager(
|
||||
{
|
||||
...defaultSettings(),
|
||||
megaCredentials: "mega-user:mega-pass",
|
||||
providerDailyUsageDay: "2000-01-01",
|
||||
providerDailyUsageBytes: { megadebrid: 9_000_000_000 } as Record<string, number>,
|
||||
megaDebridAccountDailyUsageBytes: { [acctId]: 9_000_000_000 } as Record<string, number>,
|
||||
megaDebridAccountDailyLimitBytes: { [acctId]: 1_000_000_000 } as Record<string, number>
|
||||
},
|
||||
emptySession(),
|
||||
createStoragePaths(path.join(root, "state")),
|
||||
{}
|
||||
);
|
||||
|
||||
const snap = manager.getSnapshot();
|
||||
expect(snap.settings.providerDailyUsageDay).not.toBe("2000-01-01");
|
||||
expect(snap.settings.megaDebridAccountDailyUsageBytes || {}).toEqual({});
|
||||
expect(snap.settings.providerDailyUsageBytes || {}).toEqual({});
|
||||
});
|
||||
|
||||
it("does not freeze the scheduler when a reset item's old task is parked in a non-abort-observing await", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||
tempDirs.push(root);
|
||||
|
||||
let convCalls = 0;
|
||||
const manager = new DownloadManager(
|
||||
{
|
||||
...defaultSettings(),
|
||||
megaCredentials: "mega-user:mega-pass",
|
||||
megaDebridWebEnabled: true,
|
||||
megaDebridApiEnabled: false,
|
||||
megaDebridPreferApi: false,
|
||||
providerOrder: [],
|
||||
providerPrimary: "megadebrid",
|
||||
providerSecondary: "none",
|
||||
providerTertiary: "none",
|
||||
outputDir: path.join(root, "downloads"),
|
||||
extractDir: path.join(root, "extract"),
|
||||
autoExtract: false,
|
||||
autoReconnect: false,
|
||||
enableIntegrityCheck: false,
|
||||
maxParallel: 4
|
||||
},
|
||||
emptySession(),
|
||||
createStoragePaths(path.join(root, "state")),
|
||||
{
|
||||
megaWebUnrestrict: vi.fn(async (): Promise<UnrestrictedLink | null> => {
|
||||
convCalls += 1;
|
||||
return await new Promise<UnrestrictedLink | null>(() => { /* never settles, ignores abort */ });
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
manager.addPackages([{
|
||||
name: "freeze-repro",
|
||||
links: [
|
||||
"https://rapidgator.net/file/freeze-1.part1.rar.html",
|
||||
"https://rapidgator.net/file/freeze-2.part2.rar.html",
|
||||
"https://rapidgator.net/file/freeze-3.part3.rar.html"
|
||||
]
|
||||
}]);
|
||||
|
||||
await manager.start();
|
||||
await waitFor(() => convCalls === 1, 10000);
|
||||
|
||||
const validatingItem = Object.values(manager.getSnapshot().session.items).find((i) => i.status === "validating");
|
||||
expect(validatingItem).toBeTruthy();
|
||||
|
||||
manager.resetItems([validatingItem!.id]);
|
||||
|
||||
await waitFor(() => convCalls === 2, 5000);
|
||||
expect(convCalls).toBe(2);
|
||||
|
||||
manager.stop();
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
}, 20000);
|
||||
|
||||
it("serializes Mega-Debrid API conversions to one per account (no single-token hammering)", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||
tempDirs.push(root);
|
||||
|
||||
Reference in New Issue
Block a user