Skip to content

Commit

Apps on g1t.page answer each request within 50 ms of CPU time and 50 requests of their own

syntaqxcommitted Parentbeb4f9bBrowse files
4 files+53−40/4 viewed
+5−0
124124 are served under the binding name your config gives them. Cron triggers
125125 (`triggers.crons`) are not scheduled, so a `scheduled` handler never
126126 runs; the deployment says so in its warnings.
127+- Each request your Worker answers may use up to 50 ms of CPU time
128+ (waiting on the network does not count) and make up to 50 requests of
129+ its own (`fetch` calls and the like). A request that goes over either
130+ is stopped and answered with a 503 page that says so. Static assets are
131+ served without running your Worker, and count toward neither.
127132 - `vars` are deployed as plain-text bindings (or JSON, for objects). Rows
128133 of the project's [secrets and variables](/guides/secrets-and-variables/)
129134 available to Deployments are bound too, and replace a `var` of the same
+12−3
55 * Platforms namespace (`web-git-fix-login-acme.g1t.page` is the fix-login
66 * branch's preview of acme's web project), so the app is fetched by name
77 * and runs only for as long as it answers. An app no one visits runs
8− * nothing and costs nothing. The one lookup is for an address an app
8+ * nothing and costs nothing. Each request it serves is held to
9+ * `APP_LIMITS`, so one app cannot spend without bound. The one lookup is for an address an app
910 * had before its project moved: `DOMAINS` holds a redirect under the old
1011 * hostname for as long as the old name is held, and it is followed before
1112 * anything the old script would answer (such as a paused notice).
1819 * people sign in to.
1920 */
2021
21−import { DOMAIN, FALLBACK, parseEntry, redirectTo, route, type DomainEntry } from "./route.ts";
22+import { APP_LIMITS, DOMAIN, FALLBACK, overLimits, parseEntry, redirectTo, route, type DomainEntry } from "./route.ts";
2223
2324 type Env = {
2425 APPS: DispatchNamespace;
7677 async function dispatch(env: Env, request: Request, script: string, shown: string, missing: () => Response): Promise<Response> {
7778 let app: Fetcher;
7879 try {
79− app = env.APPS.get(script);
80+ app = env.APPS.get(script, {}, { limits: APP_LIMITS });
8081 } catch {
8182 return missing();
8283 }
8485 return await app.fetch(request);
8586 } catch (error) {
8687 if (/worker not found|script not found|does not exist/i.test(String(error))) return missing();
88+ if (overLimits(error)) {
89+ return page(
90+ 503,
91+ "The app went over its limits",
92+ `<p>The app at <code>${escape(shown)}</code> used more than ${APP_LIMITS.cpuMs} ms of CPU time, or made more than ${APP_LIMITS.subRequests} requests of its own, answering this request.</p>`,
93+ shown,
94+ );
95+ }
8796 return page(
8897 502,
8998 "The app failed",
+23−1
11 import assert from "node:assert/strict";
22 import { test } from "node:test";
33
4−import { parseEntry, redirectTo, route } from "./route.ts";
4+import { APP_LIMITS, overLimits, parseEntry, redirectTo, route } from "./route.ts";
55 import worker from "./index.ts";
66
77 test("hosts on g1t.page are the home page, the fallback origin, or an app", () => {
124124 };
125125 assert.equal(await (await call("https://shop-acme.g1t.page/", e)).text(), "shop");
126126 });
127+
128+test("every app runs within the limits, and one that goes over them says so", async () => {
129+ const asked: unknown[] = [];
130+ const e = {
131+ APPS: {
132+ get(_name: string, _args: unknown, options: unknown) {
133+ asked.push(options);
134+ return {
135+ async fetch() {
136+ throw new Error("Worker exceeded CPU time limit.");
137+ },
138+ };
139+ },
140+ },
141+ } as any;
142+ const response = await call("https://web-acme.g1t.page/", e);
143+ assert.deepEqual(asked, [{ limits: APP_LIMITS }]);
144+ assert.equal(response.status, 503);
145+ assert.match(await response.text(), /went over its limits/);
146+ assert.ok(overLimits(new Error("Too many subrequests.")));
147+ assert.ok(!overLimits(new Error("TypeError: x is undefined")));
148+});
+13−0
4747 const url = new URL(requestUrl);
4848 return `https://${target}${url.pathname}${url.search}`;
4949 }
50+
51+/**
52+ * What one request to an app may use: CPU time, not counting time spent
53+ * waiting on the network, and requests it makes of its own. Static assets
54+ * are served without running the app, and use neither. No plan sets
55+ * others; the Deployments guide names these.
56+ */
57+export const APP_LIMITS = { cpuMs: 50, subRequests: 50 } as const;
58+
59+/** Whether an app's failure was going over `APP_LIMITS`, as the runtime words it. */
60+export function overLimits(error: unknown): boolean {
61+ return /exceeded (its )?cpu|cpu time limit|too many subrequests|subrequest limit/i.test(String(error));
62+}