fix(rotation): concurrent jobs now reuse the override instead of failing
When multiple jobs run in parallel on the same hoster and the primary account starts failing, the first job marks it failed + triggers rotation. The second job's retries then also exhaust on the same (already-failed) primary — but the old while-condition `!_failedAccounts.has(...)` short-circuited the whole rotation loop for anything already marked, so the second job went straight to final-error even though a resolved override was sitting right there. Now the loop always checks for an available override; it only skips the mark-failed + emit step if the account was already marked by a concurrent job. Fixed visible symptom: first job rotates A→B, every other job in the same batch that hit A got final-error instead of also switching to B. Also extended fast-fail patterns to include 429 (Too many requests), CSRF-Token / 'Bist du eingeloggt' — both were showing up as the primary failure mode in real uploads and were wasting 5 retries each.
This commit is contained in:
parent
b7336eefb8
commit
63f87a0310
@ -72,14 +72,20 @@ class UploadManager extends EventEmitter {
|
|||||||
/limit (reached|exceeded|überschritten)/i,
|
/limit (reached|exceeded|überschritten)/i,
|
||||||
/rate[- ]?limit/i,
|
/rate[- ]?limit/i,
|
||||||
/too many requests/i,
|
/too many requests/i,
|
||||||
/\b(401|403)\b/,
|
/\b(401|403|429)\b/,
|
||||||
/Falscher (User|Username|Passwort)/i,
|
/Falscher (User|Username|Passwort)/i,
|
||||||
/Incorrect (Login|Password)/i,
|
/Incorrect (Login|Password)/i,
|
||||||
/invalid (credentials|api[- ]?key|token|session)/i,
|
/invalid (credentials|api[- ]?key|token|session)/i,
|
||||||
/(account|user) (banned|suspended|disabled|gesperrt)/i,
|
/(account|user) (banned|suspended|disabled|gesperrt)/i,
|
||||||
/not authorized/i,
|
/not authorized/i,
|
||||||
/forbidden/i,
|
/forbidden/i,
|
||||||
/session (expired|abgelaufen)/i
|
/session (expired|abgelaufen)/i,
|
||||||
|
// Session/CSRF hints — the account's server session went stale, which
|
||||||
|
// no amount of retrying will fix. Re-login happens on the next account.
|
||||||
|
/CSRF[- ]?Token nicht gefunden/i,
|
||||||
|
/CSRF[- ]?token not found/i,
|
||||||
|
/Bist du eingeloggt/i,
|
||||||
|
/not logged in/i
|
||||||
];
|
];
|
||||||
return PATTERNS.some(p => p.test(m));
|
return PATTERNS.some(p => p.test(m));
|
||||||
}
|
}
|
||||||
@ -524,23 +530,34 @@ class UploadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account rotation: mark the current account failed, wait for main to
|
// Account rotation: mark the current account failed (if not already),
|
||||||
// resolve the next fallback, then retry. Loop so A → B → C → ... works
|
// wait for main to resolve the next fallback, then retry. Loops so
|
||||||
// for hosters with 3+ accounts (the old code only did one level: A → B
|
// A → B → C → ... works for hosters with 3+ accounts.
|
||||||
// and stopped, even if C would have worked).
|
//
|
||||||
|
// CRITICAL: we must ALWAYS check for an existing override, even if this
|
||||||
|
// account is already in _failedAccounts (e.g. another concurrent job
|
||||||
|
// already marked it failed). Otherwise the second job falls straight
|
||||||
|
// through to final-error instead of using the already-resolved fallback.
|
||||||
this._rotLog('retries-exhausted', {
|
this._rotLog('retries-exhausted', {
|
||||||
hoster: task.hoster, fileName, accountId: task.accountId,
|
hoster: task.hoster, fileName, accountId: task.accountId,
|
||||||
lastError: lastError ? lastError.message : null
|
lastError: lastError ? lastError.message : null
|
||||||
});
|
});
|
||||||
while (task.accountId && !this._failedAccounts.has(task.hoster + ':' + task.accountId)) {
|
while (task.accountId) {
|
||||||
if (signal.aborted || this.stopAfterActive) break;
|
if (signal.aborted || this.stopAfterActive) break;
|
||||||
this._failedAccounts.set(task.hoster + ':' + task.accountId, true);
|
const alreadyMarked = this._failedAccounts.has(task.hoster + ':' + task.accountId);
|
||||||
this._rotLog('mark-failed', {
|
if (!alreadyMarked) {
|
||||||
hoster: task.hoster, fileName, accountId: task.accountId,
|
this._failedAccounts.set(task.hoster + ':' + task.accountId, true);
|
||||||
lastError: lastError ? lastError.message : null
|
this._rotLog('mark-failed', {
|
||||||
});
|
hoster: task.hoster, fileName, accountId: task.accountId,
|
||||||
this.emit('account-failed', { hoster: task.hoster, accountId: task.accountId });
|
lastError: lastError ? lastError.message : null
|
||||||
await this._sleep(800, signal);
|
});
|
||||||
|
this.emit('account-failed', { hoster: task.hoster, accountId: task.accountId });
|
||||||
|
await this._sleep(800, signal);
|
||||||
|
} else {
|
||||||
|
this._rotLog('already-marked', {
|
||||||
|
hoster: task.hoster, fileName, accountId: task.accountId
|
||||||
|
});
|
||||||
|
}
|
||||||
const override = this._accountOverrides.get(task.hoster);
|
const override = this._accountOverrides.get(task.hoster);
|
||||||
if (!override) {
|
if (!override) {
|
||||||
this._rotLog('rotation-end', {
|
this._rotLog('rotation-end', {
|
||||||
@ -556,6 +573,13 @@ class UploadManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (override.id === task.accountId) {
|
||||||
|
this._rotLog('rotation-end', {
|
||||||
|
hoster: task.hoster, fileName, reason: 'override-same-as-current',
|
||||||
|
lastFailedAccountId: task.accountId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Switch to fallback account and retry this file
|
// Switch to fallback account and retry this file
|
||||||
this._rotLog('rotate', {
|
this._rotLog('rotate', {
|
||||||
hoster: task.hoster, fileName,
|
hoster: task.hoster, fileName,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user