mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-27 12:55:20 -07:00
fix(anilist): dedupe failures during retry cooldown and block dead-lette
- Ignore markFailure calls while an item is still within its retry backoff window - Prevent enqueue from re-adding keys already in the dead-letter queue
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: anilist
|
||||||
|
|
||||||
|
- Prevent repeated missing-token checks from rapidly exhausting AniList retry attempts or duplicating dead-letter entries for the same episode.
|
||||||
@@ -71,7 +71,7 @@ test('anilist update queue applies retry backoff and dead-letter', () => {
|
|||||||
assert.equal((pendingPayload.pending[0]?.nextAttemptAt ?? now) - now, 30_000);
|
assert.equal((pendingPayload.pending[0]?.nextAttemptAt ?? now) - now, 30_000);
|
||||||
|
|
||||||
for (let attempt = 2; attempt <= 8; attempt += 1) {
|
for (let attempt = 2; attempt <= 8; attempt += 1) {
|
||||||
queue.markFailure('k2', `fail-${attempt}`, now);
|
queue.markFailure('k2', `fail-${attempt}`, now + attempt * 6 * 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = queue.getSnapshot(Number.MAX_SAFE_INTEGER);
|
const snapshot = queue.getSnapshot(Number.MAX_SAFE_INTEGER);
|
||||||
@@ -83,6 +83,52 @@ test('anilist update queue applies retry backoff and dead-letter', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('anilist update queue ignores duplicate failures while retry is cooling down', () => {
|
||||||
|
const queueFile = createTempQueueFile();
|
||||||
|
const loggerState = createLogger();
|
||||||
|
const queue = createAnilistUpdateQueue(queueFile, loggerState.logger);
|
||||||
|
|
||||||
|
const now = 1_700_000 * 1_000_000;
|
||||||
|
queue.enqueue('k2', 'Backoff Demo', 2);
|
||||||
|
queue.markFailure('k2', 'fail-1', now);
|
||||||
|
|
||||||
|
for (let attempt = 2; attempt <= 12; attempt += 1) {
|
||||||
|
queue.markFailure('k2', `duplicate-${attempt}`, now + attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.parse(fs.readFileSync(queueFile, 'utf-8')) as {
|
||||||
|
pending: Array<{ attemptCount: number; lastError: string | null }>;
|
||||||
|
deadLetter: Array<unknown>;
|
||||||
|
};
|
||||||
|
assert.equal(payload.pending[0]?.attemptCount, 1);
|
||||||
|
assert.equal(payload.pending[0]?.lastError, 'fail-1');
|
||||||
|
assert.deepEqual(queue.getSnapshot(Number.MAX_SAFE_INTEGER), {
|
||||||
|
pending: 1,
|
||||||
|
ready: 1,
|
||||||
|
deadLetter: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('anilist update queue does not re-enqueue dead-lettered keys', () => {
|
||||||
|
const queueFile = createTempQueueFile();
|
||||||
|
const loggerState = createLogger();
|
||||||
|
const queue = createAnilistUpdateQueue(queueFile, loggerState.logger);
|
||||||
|
|
||||||
|
const now = 1_700_000 * 1_000_000;
|
||||||
|
queue.enqueue('k4', 'Dead Letter Demo', 4);
|
||||||
|
for (let attempt = 1; attempt <= 8; attempt += 1) {
|
||||||
|
queue.markFailure('k4', `fail-${attempt}`, now + attempt * 6 * 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.enqueue('k4', 'Dead Letter Demo', 4);
|
||||||
|
|
||||||
|
assert.deepEqual(queue.getSnapshot(Number.MAX_SAFE_INTEGER), {
|
||||||
|
pending: 0,
|
||||||
|
ready: 0,
|
||||||
|
deadLetter: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('anilist update queue persists and reloads from disk', () => {
|
test('anilist update queue persists and reloads from disk', () => {
|
||||||
const queueFile = createTempQueueFile();
|
const queueFile = createTempQueueFile();
|
||||||
const loggerState = createLogger();
|
const loggerState = createLogger();
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ export function createAnilistUpdateQueue(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
enqueue(key: string, title: string, episode: number, season: number | null = null): void {
|
enqueue(key: string, title: string, episode: number, season: number | null = null): void {
|
||||||
const existing = pending.find((item) => item.key === key);
|
const existing =
|
||||||
|
pending.find((item) => item.key === key) || deadLetter.find((item) => item.key === key);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,6 +148,9 @@ export function createAnilistUpdateQueue(
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (item.attemptCount > 0 && item.nextAttemptAt > nowMs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
item.attemptCount += 1;
|
item.attemptCount += 1;
|
||||||
item.lastError = reason;
|
item.lastError = reason;
|
||||||
if (item.attemptCount >= MAX_ATTEMPTS) {
|
if (item.attemptCount >= MAX_ATTEMPTS) {
|
||||||
|
|||||||
Reference in New Issue
Block a user