fix(rotation): transient network errors don't blacklist the account + clearer byse 'OK' error
Two bugs visible in the user's rotation log:
1. 'error=OK' for byse.sx — the server returned a payload with
msg='OK' and no file_code anywhere we recognized. Our generic
uploadFile threw the bare 'OK' as the error message, which is
useless and misleading. Now when we see an ok-ish msg without
the expected file_code we throw a descriptive error that
includes the first ~400 bytes of the payload so the next time
it happens we can see what's actually being returned (API
changed, new field name, etc.).
2. 'getaddrinfo ENOTFOUND s1055.filemoon' was marking accounts as
permanently failed, blacklisting BOTH byse accounts within the
same batch even though neither was the actual problem — filemoon
(byse's storage backend) briefly had a DNS blip. Added
_isTransientNetworkError() covering DNS/ECONNRESET/ETIMEDOUT/etc.
When all retries on an account exhaust with a transient error,
we now fail just that file and emit 'skip-rotation-transient'
instead of adding the account to _failedAccounts. Other files
in the same batch still get a fresh try on the same account.
This commit is contained in:
parent
22869df8a5
commit
0ea92ad6d0
@ -413,11 +413,19 @@ async function uploadFile(hosterName, filePath, apiKey, onProgress, signal, thro
|
||||
throw new Error(payload.msg || payload.message || `Upload zu ${hosterName} wurde vom Server abgelehnt.`);
|
||||
}
|
||||
|
||||
// Avoid throwing a bare "OK" / "SUCCESS" as the error message — that happens
|
||||
// when the server says "msg: OK" but ships no file_code anywhere we know
|
||||
// about, typically an API change. Surface the full (trimmed) payload so
|
||||
// future logs actually show what the server returned.
|
||||
const msg = String(payload.msg || payload.message || '').trim();
|
||||
const isOkishNoPayload = /^(ok|success|done|accepted)$/i.test(msg);
|
||||
if (isOkishNoPayload || !msg) {
|
||||
const snippet = JSON.stringify(payload).slice(0, 400);
|
||||
throw new Error(
|
||||
payload.msg
|
||||
|| payload.message
|
||||
|| `Upload zu ${hosterName} lieferte keine verwendbaren Dateidaten zurueck.`
|
||||
`Upload zu ${hosterName} lieferte keine file_code-Antwort (Payload: ${snippet})`
|
||||
);
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -56,11 +56,38 @@ class UploadManager extends EventEmitter {
|
||||
this.emit('rot-log', { ts: Date.now(), event, ...data });
|
||||
}
|
||||
|
||||
// Transient network errors — the account is fine, the network or the
|
||||
// hoster's own backend hiccuped. Retrying on the SAME account is the right
|
||||
// move; marking it failed would wrongly poison the fallback chain. If all
|
||||
// retries on the current account still hit this class of error, we bail
|
||||
// out for this file without blacklisting the account, so other jobs in the
|
||||
// batch still get a fresh chance on it.
|
||||
_isTransientNetworkError(err) {
|
||||
if (!err || !err.message) return false;
|
||||
const m = String(err.message);
|
||||
const TRANSIENT = [
|
||||
/ENOTFOUND/i,
|
||||
/ECONNRESET/i,
|
||||
/ECONNREFUSED/i,
|
||||
/ETIMEDOUT/i,
|
||||
/EAI_AGAIN/i,
|
||||
/EHOSTUNREACH/i,
|
||||
/ENETUNREACH/i,
|
||||
/EPIPE/i,
|
||||
/socket hang up/i,
|
||||
/network (error|failure|problem)/i,
|
||||
/dns (lookup|error|failed)/i,
|
||||
/getaddrinfo/i,
|
||||
/fetch failed/i,
|
||||
/\bconnect (ETIMEDOUT|ECONN)/i
|
||||
];
|
||||
return TRANSIENT.some(p => p.test(m));
|
||||
}
|
||||
|
||||
// Error classes that mean "this account is the problem, retrying on it won't
|
||||
// help" — we skip the remaining retries and go straight to the fallback
|
||||
// account. Keeps single runs fast when an account is rate-limited, banned,
|
||||
// or out of quota. Transient network issues still go through the normal
|
||||
// retry loop on the same account.
|
||||
// or out of quota.
|
||||
_shouldSkipRetryOnAccountError(err) {
|
||||
if (!err || !err.message) return false;
|
||||
const m = String(err.message);
|
||||
@ -542,6 +569,19 @@ class UploadManager extends EventEmitter {
|
||||
hoster: task.hoster, fileName, accountId: task.accountId,
|
||||
lastError: lastError ? lastError.message : null
|
||||
});
|
||||
// If the reason for failure was a transient network error we do NOT
|
||||
// blacklist the account. Other jobs on the same account in this batch
|
||||
// can still try fresh. This file just errors out for now.
|
||||
if (this._isTransientNetworkError(lastError)) {
|
||||
this._rotLog('skip-rotation-transient', {
|
||||
hoster: task.hoster, fileName, accountId: task.accountId,
|
||||
lastError: lastError ? lastError.message : null
|
||||
});
|
||||
const error = lastError.message || 'Netzwerkfehler';
|
||||
emitFinalStatus('error', { error });
|
||||
recordFinalResult('error', { error });
|
||||
return;
|
||||
}
|
||||
while (task.accountId) {
|
||||
if (signal.aborted || this.stopAfterActive) break;
|
||||
const alreadyMarked = this._failedAccounts.has(task.hoster + ':' + task.accountId);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user