fix(registry): improve HTTP fetch retry logging, backoff calculation, and token-cache warning

This commit is contained in:
2026-02-07 12:34:37 +00:00
parent 7591e0ed90
commit 3dc75f5cda
3 changed files with 24 additions and 4 deletions

View File

@@ -1,5 +1,14 @@
# Changelog
## 2026-02-07 - 1.17.2 - fix(registry)
improve HTTP fetch retry logging, backoff calculation, and token-cache warning
- Include HTTP method in logs and normalize method to uppercase for consistency
- Log retry attempts with method, URL and calculated exponential backoff delay
- Compute and reuse exponential backoff delay variable instead of inline calculation
- Log error when a 5xx response persists after all retry attempts and when fetch ultimately fails
- Add a warning log when clearing cached token after a 401 response
## 2026-02-07 - 1.17.1 - fix(registrycopy)
add fetchWithRetry wrapper to apply timeouts, retries with exponential backoff, and token cache handling; use it for registry HTTP requests

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/tsdocker',
version: '1.17.1',
version: '1.17.2',
description: 'develop npm modules cross platform with docker'
}

View File

@@ -31,26 +31,36 @@ export class RegistryCopy {
timeoutMs: number = 300_000,
maxRetries: number = 3,
): Promise<Response> {
const method = (options.method || 'GET').toUpperCase();
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (attempt > 1) {
logger.log('info', `Retry ${attempt}/${maxRetries} for ${method} ${url}`);
}
const resp = await fetch(url, {
...options,
signal: AbortSignal.timeout(timeoutMs),
});
// Retry on 5xx server errors (but not 4xx)
if (resp.status >= 500 && attempt < maxRetries) {
logger.log('warn', `Request to ${url} returned ${resp.status}, retrying (${attempt}/${maxRetries})...`);
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
const delay = 1000 * Math.pow(2, attempt - 1);
logger.log('warn', `${method} ${url} returned ${resp.status}, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`);
await new Promise(r => setTimeout(r, delay));
continue;
}
if (resp.status >= 500) {
logger.log('error', `${method} ${url} returned ${resp.status} after ${maxRetries} attempts, giving up`);
}
return resp;
} catch (err) {
lastError = err as Error;
if (attempt < maxRetries) {
const delay = 1000 * Math.pow(2, attempt - 1);
logger.log('warn', `fetch failed (attempt ${attempt}/${maxRetries}): ${lastError.message}, retrying in ${delay}ms...`);
logger.log('warn', `${method} ${url} failed (attempt ${attempt}/${maxRetries}): ${lastError.message}, retrying in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
} else {
logger.log('error', `${method} ${url} failed after ${maxRetries} attempts: ${lastError.message}`);
}
}
}
@@ -231,6 +241,7 @@ export class RegistryCopy {
// Token expired — clear cache so next call re-authenticates
if (resp.status === 401 && token) {
const cacheKey = `${registry}/${`repository:${repo}:${actions}`}`;
logger.log('warn', `Got 401 for ${registry}${path} — clearing cached token for ${cacheKey}`);
delete this.tokenCache[cacheKey];
}