Skip to content

Commit

Permalink
Harvest http codes out of type errors (#131)
Browse files Browse the repository at this point in the history
Type errors can be opaque but in Node.js `fetch` will often throw them
(even for rate limit errors). This change tries to harvest the HTTP code
out of the type error so we can use our rate-limit handling, etc. if we
encounter one.
  • Loading branch information
ankrgyl authored Jan 5, 2025
1 parent dd8625f commit 0546ef3
Showing 1 changed file with 48 additions and 38 deletions.
86 changes: 48 additions & 38 deletions packages/proxy/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ async function fetchModelLoop(
const additionalHeaders = secret.metadata?.additionalHeaders || {};

let httpCode = undefined;
let httpHeaders = new Headers();
endpointCalls.add(1, loggableInfo);
try {
proxyResponse = await fetchModel(
Expand Down Expand Up @@ -836,54 +837,63 @@ async function fetchModelLoop(
proxyResponse.response.statusText,
);
httpCode = proxyResponse.response.status;

// If we hit a rate-limit error, and we're at the end of the
// loop, and we haven't waited the maximum allotted time, then
// sleep for a bit, and reset the loop.
if (
httpCode === RATE_LIMIT_ERROR_CODE &&
i === secrets.length - 1 &&
totalWaitedTime < RATE_LIMIT_MAX_WAIT_MS
) {
const limitReset = tryParseRateLimitReset(
proxyResponse.response.headers,
);
delayMs = Math.max(
// Make sure we sleep at least 10ms. Sometimes the random backoff logic can get wonky.
Math.min(
// If we have a rate limit reset time, use that. Otherwise, use a random backoff.
// Sometimes, limitReset is 0 (errantly), so fall back to the random backoff in that case too.
// And never sleep longer than 10 seconds or the remaining budget.
limitReset || delayMs * (BACKOFF_EXPONENT - Math.random()),
10 * 1000,
RATE_LIMIT_MAX_WAIT_MS - totalWaitedTime,
),
10,
);
console.warn(
`Ran out of endpoints and hit rate limit errors, so sleeping for ${delayMs}ms`,
loopIndex,
);
await new Promise((r) => setTimeout(r, delayMs));

totalWaitedTime += delayMs;
i = -1; // Reset the loop variable
}
httpHeaders = proxyResponse.response.headers;
}
} catch (e) {
lastException = e;
if (e instanceof TypeError) {
console.log(
"Failed to fetch (most likely an invalid URL",
secret.id,
e,
);
if ("cause" in e && e.cause && isObject(e.cause)) {
if ("statusCode" in e.cause) {
httpCode = e.cause.statusCode;
}
if ("headers" in e.cause) {
httpHeaders = new Headers(e.cause.headers);
}
}
if (!httpCode) {
console.log(
"Failed to fetch with a generic error (could be an invalid URL or an unhandled network error)",
secret.id,
e,
);
}
} else {
endpointFailures.add(1, loggableInfo);
throw e;
}
}

// If we hit a rate-limit error, and we're at the end of the
// loop, and we haven't waited the maximum allotted time, then
// sleep for a bit, and reset the loop.
if (
httpCode === RATE_LIMIT_ERROR_CODE &&
i === secrets.length - 1 &&
totalWaitedTime < RATE_LIMIT_MAX_WAIT_MS
) {
const limitReset = tryParseRateLimitReset(httpHeaders);
delayMs = Math.max(
// Make sure we sleep at least 10ms. Sometimes the random backoff logic can get wonky.
Math.min(
// If we have a rate limit reset time, use that. Otherwise, use a random backoff.
// Sometimes, limitReset is 0 (errantly), so fall back to the random backoff in that case too.
// And never sleep longer than 10 seconds or the remaining budget.
limitReset || delayMs * (BACKOFF_EXPONENT - Math.random()),
10 * 1000,
RATE_LIMIT_MAX_WAIT_MS - totalWaitedTime,
),
10,
);
console.warn(
`Ran out of endpoints and hit rate limit errors, so sleeping for ${delayMs}ms`,
loopIndex,
);
await new Promise((r) => setTimeout(r, delayMs));

totalWaitedTime += delayMs;
i = -1; // Reset the loop variable
}

endpointRetryableErrors.add(1, {
...loggableInfo,
http_code: httpCode,
Expand Down

0 comments on commit 0546ef3

Please sign in to comment.