fix: retry transient pickup checks
Retry transient Foodsharing network failures such as socket hang up for safe read/check actions and add explicit pickup fallback scheduling, tick, and result logs so missed fallback checks can be diagnosed.
This commit is contained in:
@@ -3,8 +3,14 @@ const sessionStore = require('./sessionStore');
|
||||
const notificationService = require('./notificationService');
|
||||
|
||||
const ADMIN_SESSION_ERROR_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
||||
const DEFAULT_TRANSIENT_RETRY_DELAYS_MS = [1000, 3000];
|
||||
const NON_RETRIABLE_TRANSIENT_LABELS = new Set(['bookSlot']);
|
||||
const adminSessionErrorCooldowns = new Map();
|
||||
|
||||
function wait(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function shouldNotifyAdminSessionError(key) {
|
||||
if (!key) {
|
||||
return false;
|
||||
@@ -51,6 +57,26 @@ function isCsrfError(error) {
|
||||
return message.toLowerCase().includes('csrf');
|
||||
}
|
||||
|
||||
function isTransientNetworkError(error) {
|
||||
if (!error) {
|
||||
return false;
|
||||
}
|
||||
if (error.response) {
|
||||
return false;
|
||||
}
|
||||
const code = String(error.code || '').toUpperCase();
|
||||
if (['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'EPIPE', 'ENOTFOUND', 'EAI_AGAIN'].includes(code)) {
|
||||
return true;
|
||||
}
|
||||
const message = String(error.message || '').toLowerCase();
|
||||
return (
|
||||
message.includes('socket hang up') ||
|
||||
message.includes('timeout') ||
|
||||
message.includes('network error') ||
|
||||
message.includes('connection reset')
|
||||
);
|
||||
}
|
||||
|
||||
async function refreshSession(session, { label } = {}) {
|
||||
if (!session?.credentials?.email || !session?.credentials?.password) {
|
||||
console.warn(
|
||||
@@ -110,7 +136,7 @@ async function ensureSession(session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function withSessionRetry(session, action, { label } = {}) {
|
||||
async function withSessionRetry(session, action, { label, transientRetries } = {}) {
|
||||
if (!session) {
|
||||
throw new Error('Session fehlt');
|
||||
}
|
||||
@@ -120,22 +146,42 @@ async function withSessionRetry(session, action, { label } = {}) {
|
||||
throw new Error('Session konnte nicht erneuert werden');
|
||||
}
|
||||
}
|
||||
try {
|
||||
return await action();
|
||||
} catch (error) {
|
||||
if (!isUnauthorizedError(error) && !isCsrfError(error)) {
|
||||
throw error;
|
||||
const retryDelays = NON_RETRIABLE_TRANSIENT_LABELS.has(label)
|
||||
? []
|
||||
: DEFAULT_TRANSIENT_RETRY_DELAYS_MS.slice(
|
||||
0,
|
||||
Number.isFinite(transientRetries) ? Math.max(0, transientRetries) : DEFAULT_TRANSIENT_RETRY_DELAYS_MS.length
|
||||
);
|
||||
let transientAttempt = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return await action();
|
||||
} catch (error) {
|
||||
if (isTransientNetworkError(error) && transientAttempt < retryDelays.length) {
|
||||
transientAttempt += 1;
|
||||
const delay = retryDelays[transientAttempt - 1];
|
||||
console.warn(
|
||||
`[SESSION] Transienter Fehler${label ? ` (${label})` : ''}: ${error.message}. ` +
|
||||
`Retry ${transientAttempt}/${retryDelays.length} in ${delay}ms.`
|
||||
);
|
||||
await wait(delay);
|
||||
continue;
|
||||
}
|
||||
if (!isUnauthorizedError(error) && !isCsrfError(error)) {
|
||||
throw error;
|
||||
}
|
||||
const refreshed = await refreshSession(session, { label });
|
||||
if (!refreshed) {
|
||||
throw error;
|
||||
}
|
||||
return action();
|
||||
}
|
||||
const refreshed = await refreshSession(session, { label });
|
||||
if (!refreshed) {
|
||||
throw error;
|
||||
}
|
||||
return action();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ensureSession,
|
||||
isTransientNetworkError,
|
||||
refreshSession,
|
||||
withSessionRetry
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user