Skip to content
109 linesCodeBlameRaw
1/**
2 * How long the site waits on a service before it gives up. Nothing on
3 * Workers ends a subrequest that stops answering: the page waits as long
4 * as the visitor does, so a service stuck on one call holds the page on
5 * its loading bar for good. With a deadline it fails instead, as an error
6 * the page can draw and the next click can get past.
7 */
8
9/** A read: no page should wait longer than this on one service call. */
10export const READ_DEADLINE_MS = 15_000;
11
12/** A write: some do real work (a merge, a commit), so they get longer. */
13export const WRITE_DEADLINE_MS = 60_000;
14
15/** Reads that do real work inline the first time, or walk history. */
16export const SLOW_READ_DEADLINE_MS = 60_000;
17
18/** Jobs a person starts and waits on: an import, a model's draft, a computer waking. */
19export const LONG_DEADLINE_MS = 600_000;
20
21/**
22 * Reads that can take longer than `READ_DEADLINE_MS` and still be right,
23 * by service binding and method. Cut off, their work is lost and starts
24 * again on the next visit, so they get the time to finish.
25 */
26const SLOW_READS = new Set([
27 // A repository's first look runs its dependency scan inline (services/security/src/deps.rs).
28 "security overview",
29 "security dependency_graph",
30 "security sbom",
31 // Walks up to 400 commits, reading trees and blobs one after another (services/repos/src/blame.rs).
32 "repos blame",
33 // Up to 2,500 store reads per call (services/repos/src/last_commits.rs).
34 "repos last_commits",
35]);
36
37/**
38 * Writes that run for minutes and still finish: copying a repository in,
39 * syncing a mirror, a model writing a draft, a computer's home moved in or
40 * out of storage, a scan over many repositories.
41 */
42const LONG_CALLS = new Set([
43 "repos create",
44 "integrations github_import",
45 "integrations mirror_add",
46 "integrations mirror_sync",
47 "integrations mirror_move_in",
48 "integrations mirror_hand_back",
49 "agents draft",
50 "agents try_draft",
51 "agents redraft",
52 "agents draft_skill",
53 "agents sync_skill_mirror",
54 "agents computer_wake",
55 "agents computer_sleep",
56 "security dry_run_pattern",
57 "security rescan",
58 "security check_updates",
59 "artifacts add_repo_space",
60]);
61
62/** How long a call to `service`'s `method` may take; `writes` as lib/perf.ts `mayWrite` says. */
63export function deadlineFor(service: string, method: string, writes: boolean): number {
64 const call = `${service} ${method}`;
65 if (LONG_CALLS.has(call)) return LONG_DEADLINE_MS;
66 if (SLOW_READS.has(call)) return SLOW_READ_DEADLINE_MS;
67 return writes ? WRITE_DEADLINE_MS : READ_DEADLINE_MS;
68}
69
70/** What a call past its deadline throws. */
71export class DeadlineExceeded extends Error {
72 constructor(what: string, ms: number) {
73 super(`${what} did not answer within ${ms}ms`);
74 this.name = "DeadlineExceeded";
75 }
76}
77
78/**
79 * `run`'s answer, or a `DeadlineExceeded` after `ms`. `run` is handed a
80 * signal that aborts at the deadline, so the call it makes is cancelled
81 * too; `outer`, when given, aborts it sooner. Whatever `run` reads must be
82 * read inside it: the deadline ends when it returns.
83 */
84export async function withDeadline<T>(
85 what: string,
86 ms: number,
87 run: (signal: AbortSignal) => Promise<T>,
88 outer?: AbortSignal | null,
89): Promise<T> {
90 const controller = new AbortController();
91 let timer: ReturnType<typeof setTimeout> | undefined;
92 const expired = new Promise<never>((_, reject) => {
93 timer = setTimeout(() => {
94 const error = new DeadlineExceeded(what, ms);
95 controller.abort(error);
96 reject(error);
97 }, ms);
98 });
99 const stop = () => controller.abort(outer?.reason);
100 if (outer?.aborted) stop();
101 else outer?.addEventListener("abort", stop, { once: true });
102 try {
103 // Raced as well as aborted: a binding that ignores the signal still lets go.
104 return await Promise.race([run(controller.signal), expired]);
105 } finally {
106 clearTimeout(timer);
107 outer?.removeEventListener("abort", stop);
108 }
109}