-
Notifications
You must be signed in to change notification settings - Fork 729
perf: prioritize critical packages in go enrichment scans #4308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e15341d
76e9914
43c6ead
13a1cda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,11 @@ import { FetchError, GoProxyLatest } from './types' | |
|
|
||
| const BASE = process.env.GO_PROXY_BASE_URL ?? 'https://proxy.golang.org' | ||
| const ZERO_TIME = '0001-01-01T00:00:00Z' | ||
| const MAX_429_RETRIES = 5 | ||
|
|
||
| function sleep(ms: number): Promise<void> { | ||
| return new Promise((r) => setTimeout(r, ms)) | ||
| } | ||
|
|
||
| // GOPROXY spec: uppercase letters in a module path are escaped as '!' + lowercase. | ||
| export function escapeModulePath(module: string): string { | ||
|
|
@@ -13,40 +18,55 @@ export async function fetchLatest( | |
| timeoutMs: number, | ||
| ): Promise<GoProxyLatest | FetchError> { | ||
| const url = `${BASE}/${escapeModulePath(module)}/@latest` | ||
| const controller = new AbortController() | ||
| const timer = setTimeout(() => controller.abort(), timeoutMs) | ||
|
|
||
| let res: Response | ||
| try { | ||
| res = await fetch(url, { signal: controller.signal }) | ||
| } catch (e) { | ||
| return { kind: 'TRANSIENT', message: `network error: ${(e as Error).message}` } | ||
| } finally { | ||
| clearTimeout(timer) | ||
| } | ||
|
|
||
| if (res.status === 429) { | ||
| return { kind: 'RATE_LIMIT', statusCode: 429, message: 'rate limited' } | ||
| } | ||
| // Any other 4xx is permanent (unknown/invalid module path) — skip, don't retry. | ||
| if (res.status >= 400 && res.status < 500) { | ||
| return { kind: 'NOT_FOUND', statusCode: res.status, message: `${res.status}` } | ||
| } | ||
| if (res.status !== 200) { | ||
| return { kind: 'TRANSIENT', statusCode: res.status, message: `unexpected status ${res.status}` } | ||
| } | ||
| for (let attempt = 0; attempt <= MAX_429_RETRIES; attempt++) { | ||
| const controller = new AbortController() | ||
| const timer = setTimeout(() => controller.abort(), timeoutMs) | ||
|
|
||
| let body: { Version?: string; Time?: string; Origin?: { URL?: string } } | ||
| try { | ||
| body = (await res.json()) as { Version?: string; Time?: string; Origin?: { URL?: string } } | ||
| } catch { | ||
| return { kind: 'MALFORMED', message: 'invalid json' } | ||
| } | ||
| if (!body.Version) return { kind: 'MALFORMED', message: 'missing Version' } | ||
| let res: Response | ||
| try { | ||
| res = await fetch(url, { signal: controller.signal }) | ||
| } catch (e) { | ||
| return { kind: 'TRANSIENT', message: `network error: ${(e as Error).message}` } | ||
| } finally { | ||
| clearTimeout(timer) | ||
| } | ||
|
|
||
| if (res.status === 429) { | ||
| if (attempt === MAX_429_RETRIES) { | ||
| return { kind: 'RATE_LIMIT', statusCode: 429, message: '429 after retries' } | ||
| } | ||
| const retryAfterSec = parseInt(res.headers.get('retry-after') ?? '', 10) | ||
| const waitMs = Number.isNaN(retryAfterSec) ? 1000 * 2 ** attempt : retryAfterSec * 1000 | ||
| await sleep(waitMs) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Long Retry-After misses heartbeatsMedium Severity On HTTP 429, Reviewed by Cursor Bugbot for commit 76e9914. Configure here. |
||
| continue | ||
|
Comment on lines
+35
to
+42
|
||
| } | ||
| // Any other 4xx is permanent (unknown/invalid module path) — skip, don't retry. | ||
| if (res.status >= 400 && res.status < 500) { | ||
| return { kind: 'NOT_FOUND', statusCode: res.status, message: `${res.status}` } | ||
| } | ||
| if (res.status !== 200) { | ||
| return { | ||
| kind: 'TRANSIENT', | ||
| statusCode: res.status, | ||
| message: `unexpected status ${res.status}`, | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| version: body.Version, | ||
| releaseAt: body.Time && body.Time !== ZERO_TIME ? body.Time : null, | ||
| repoUrl: body.Origin?.URL || null, | ||
| let body: { Version?: string; Time?: string; Origin?: { URL?: string } } | ||
| try { | ||
| body = (await res.json()) as { Version?: string; Time?: string; Origin?: { URL?: string } } | ||
| } catch { | ||
| return { kind: 'MALFORMED', message: 'invalid json' } | ||
| } | ||
| if (!body.Version) return { kind: 'MALFORMED', message: 'missing Version' } | ||
|
|
||
| return { | ||
| version: body.Version, | ||
| releaseAt: body.Time && body.Time !== ZERO_TIME ? body.Time : null, | ||
| repoUrl: body.Origin?.URL || null, | ||
| } | ||
| } | ||
|
|
||
| return { kind: 'RATE_LIMIT', statusCode: 429, message: '429 after retries' } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,26 +15,22 @@ const acts = proxyActivities<typeof activities>({ | |
| const BATCH = 100 | ||
| const ROUNDS_PER_RUN = 200 | ||
|
|
||
| interface ScanState { | ||
| cursor: string | ||
| } | ||
| const START_CURSOR = { criticalAfter: '', after: '' } | ||
|
|
||
| export async function enrichGoVersions(state: ScanState = { cursor: '' }): Promise<void> { | ||
| let { cursor } = state | ||
| export async function enrichGoVersions(cursor = START_CURSOR): Promise<void> { | ||
| for (let r = 0; r < ROUNDS_PER_RUN; r++) { | ||
| const next = await acts.enrichGoVersionsBatch(cursor, BATCH) | ||
| if (next === null) return | ||
| cursor = next | ||
| } | ||
| await continueAsNew<typeof enrichGoVersions>({ cursor }) | ||
| await continueAsNew<typeof enrichGoVersions>(cursor) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Legacy workflow cursor shape breaksHigh Severity The Go enrichment workflows now take a Additional Locations (1)Reviewed by Cursor Bugbot for commit 13a1cda. Configure here. |
||
| } | ||
|
|
||
| export async function enrichGoStatus(state: ScanState = { cursor: '' }): Promise<void> { | ||
| let { cursor } = state | ||
| export async function enrichGoStatus(cursor = START_CURSOR): Promise<void> { | ||
| for (let r = 0; r < ROUNDS_PER_RUN; r++) { | ||
| const next = await acts.enrichGoStatusBatch(cursor, BATCH) | ||
| if (next === null) return | ||
| cursor = next | ||
| } | ||
| await continueAsNew<typeof enrichGoStatus>({ cursor }) | ||
| await continueAsNew<typeof enrichGoStatus>(cursor) | ||
| } | ||


Uh oh!
There was an error while loading. Please reload this page.