fix: cancel token-gate waits and detect complete LFS prefixes

This commit is contained in:
Thomas Durieux committed 2026-10-09 10:08:49 +00:00
1 parent 705f7c428a
commit bfcea3e2b7
4 files changed
+113 -12

No files matched your search

+17 -3
View File
@@ -15,6 +15,7 @@ import { getCredential, getCredentialToken } from "./credentials";
import config from "../config";
import { createLogger } from "./logger";
import { measureStage } from "./request-monitoring";
import { setTimeout as delay } from "timers/promises";
const logger = createLogger("github");
@@ -85,7 +86,18 @@ export function getTokenGateResetAt(token: string): number {
return gate.resetAt;
}
async function waitForTokenGate(token: string): Promise<void> {
function abortableGateLookup(lookup: Promise<number>, signal: AbortSignal): Promise<number> {
return new Promise((resolve, reject) => {
const cancelled = () => { signal.removeEventListener("abort", cancelled); reject(signal.reason); };
if (signal.aborted) cancelled();
else signal.addEventListener("abort", cancelled, { once: true });
lookup.then(value => { signal.removeEventListener("abort", cancelled); resolve(value); },
error => { signal.removeEventListener("abort", cancelled); reject(error); });
});
}
async function waitForTokenGate(token: string, signal?: AbortSignal): Promise<void> {
signal?.throwIfAborted();
const key = githubQuotaKey(token);
const localGate = tokenGates.get(key);
let waitMs = 0;
@@ -96,7 +108,9 @@ async function waitForTokenGate(token: string): Promise<void> {
waitMs = resetAt - Date.now();
}
const redisResetAt = await getRedisGateResetAt(key);
const lookup = getRedisGateResetAt(key);
const redisResetAt = await (signal ? abortableGateLookup(lookup, signal) : lookup);
signal?.throwIfAborted();
if (redisResetAt > resetAt) {
resetAt = redisResetAt;
waitMs = resetAt - Date.now();
@@ -112,7 +126,7 @@ async function waitForTokenGate(token: string): Promise<void> {
waitMs,
resetAt: new Date(resetAt).toISOString(),
});
await new Promise((resolve) => setTimeout(resolve, waitMs));
await delay(waitMs, undefined, { signal });
if (localGate) tokenGates.delete(key);
}
+3 -3
View File
@@ -149,7 +149,7 @@ export default class GitHubStream extends GitHubBase {
} catch (error) {
if (!token || githubTokenContext(token)?.publicRepository ||
(error as { response?: { statusCode?: number } }).response?.statusCode !== 404) throw error;
await waitForTokenGate(token);
await waitForTokenGate(token, cancellation.signal);
const response = await octokit(token).repos.getContent({
owner: data.organization, repo: data.repoName,
path: filePath, ref: data.commit || "HEAD", mediaType: { format: "object" },
@@ -186,7 +186,7 @@ export default class GitHubStream extends GitHubBase {
if (checked) { yield chunk; continue; }
probe = Buffer.concat([probe, Buffer.from(chunk)]);
if (probe.length < 150) continue;
if (probe.toString("utf8", 0, 39) === "version https://git-lfs.github.com/spec/") {
if (probe.toString("utf8").startsWith("version https://git-lfs.github.com/spec/")) {
throw new AnonymousError("upstream_error", { httpStatus: 502 });
}
checked = true; yield probe;
@@ -294,7 +294,7 @@ export default class GitHubStream extends GitHubBase {
probe = Buffer.concat([probe, bytes]);
if (probe.length < 150) continue;
decided = true;
if (probe.toString("utf8", 0, 39) === "version https://git-lfs.github.com/spec/") {
if (probe.toString("utf8").startsWith("version https://git-lfs.github.com/spec/")) {
active = downloadRaw();
for await (const raw of active) yield raw;
return;