Skip to content

Commit e1c870f

Browse files
committed
chore: add examples for reusing workers
1 parent 04a0a87 commit e1c870f

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

examples/main-session/index.ts

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// @ts-ignore
2+
import { STATUS_CODE } from 'https://deno.land/std/http/status.ts';
3+
4+
interface Worker {
5+
new(key: string, rid: string): Worker;
6+
7+
fetch(request: Request, options?: any): Promise<Response>;
8+
dispose(): void;
9+
10+
get active(): boolean;
11+
}
12+
13+
const SESSION_HEADER_NAME = 'X-Edge-Runtime-Session-Id';
14+
const WORKERS = new Map<string, Worker>();
15+
16+
setInterval(() => {
17+
const shouldBeRemoved: string[] = [];
18+
19+
for (const [uuid, worker] of WORKERS) {
20+
if (!worker.active) {
21+
shouldBeRemoved.push(uuid);
22+
}
23+
}
24+
25+
for (const uuid of shouldBeRemoved) {
26+
console.log("deleted: ", uuid);
27+
WORKERS.delete(uuid);
28+
}
29+
}, 2500);
30+
31+
console.log('main function started (session mode)');
32+
33+
Deno.serve(async (req: Request) => {
34+
const headers = new Headers({
35+
'Content-Type': 'application/json',
36+
});
37+
38+
const url = new URL(req.url);
39+
const { pathname } = url;
40+
41+
// handle health checks
42+
if (pathname === '/_internal/health') {
43+
return new Response(
44+
JSON.stringify({ 'message': 'ok' }),
45+
{
46+
status: STATUS_CODE.OK,
47+
headers,
48+
},
49+
);
50+
}
51+
52+
if (pathname === '/_internal/metric') {
53+
const metric = await EdgeRuntime.getRuntimeMetrics();
54+
return Response.json(metric);
55+
}
56+
57+
const path_parts = pathname.split('/');
58+
const service_name = path_parts[1];
59+
60+
if (!service_name || service_name === '') {
61+
const error = { msg: 'missing function name in request' };
62+
return new Response(
63+
JSON.stringify(error),
64+
{ status: STATUS_CODE.BadRequest, headers: { 'Content-Type': 'application/json' } },
65+
);
66+
}
67+
68+
const servicePath = `./examples/${service_name}`;
69+
const createWorker = async (): Promise<Worker> => {
70+
const memoryLimitMb = 150;
71+
const workerTimeoutMs = 30 * 1000;
72+
const noModuleCache = false;
73+
74+
const importMapPath = null;
75+
const envVarsObj = Deno.env.toObject();
76+
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]);
77+
const forceCreate = false;
78+
const netAccessDisabled = false;
79+
const cpuTimeSoftLimitMs = 10000;
80+
const cpuTimeHardLimitMs = 20000;
81+
82+
return await EdgeRuntime.userWorkers.create({
83+
servicePath,
84+
memoryLimitMb,
85+
workerTimeoutMs,
86+
noModuleCache,
87+
importMapPath,
88+
envVars,
89+
forceCreate,
90+
netAccessDisabled,
91+
cpuTimeSoftLimitMs,
92+
cpuTimeHardLimitMs,
93+
});
94+
};
95+
96+
const callWorker = async () => {
97+
98+
try {
99+
let worker: Worker | null = null;
100+
101+
if (req.headers.get(SESSION_HEADER_NAME)) {
102+
const sessionId = req.headers.get(SESSION_HEADER_NAME)!;
103+
const complexSessionId = `${servicePath}/${sessionId}`;
104+
105+
let maybeWorker = WORKERS.get(complexSessionId);
106+
107+
if (maybeWorker && maybeWorker.active) {
108+
worker = maybeWorker;
109+
}
110+
}
111+
112+
if (!worker) {
113+
worker = await createWorker();
114+
}
115+
116+
let resp = await worker.fetch(req);
117+
118+
if (resp.headers.has(SESSION_HEADER_NAME)) {
119+
const sessionIdFromWorker = resp.headers.get(SESSION_HEADER_NAME)!;
120+
const complexSessionId = `${servicePath}/${sessionIdFromWorker}`;
121+
122+
WORKERS.set(complexSessionId, worker);
123+
}
124+
125+
return resp;
126+
} catch (e) {
127+
console.error(e);
128+
129+
if (e instanceof Deno.errors.WorkerRequestCancelled) {
130+
headers.append('Connection', 'close');
131+
}
132+
133+
const error = { msg: e.toString() };
134+
return new Response(
135+
JSON.stringify(error),
136+
{
137+
status: STATUS_CODE.InternalServerError,
138+
headers,
139+
},
140+
);
141+
}
142+
};
143+
144+
return callWorker();
145+
});

examples/serve-session/index.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// @ts-ignore
2+
import { STATUS_CODE } from "https://deno.land/std/http/status.ts";
3+
4+
const SESSION_HEADER_NAME = "X-Edge-Runtime-Session-Id";
5+
const SESSIONS = new Map<string, object>();
6+
7+
function makeNewSession(): [string, object] {
8+
const uuid = crypto.randomUUID();
9+
const storage = {};
10+
11+
SESSIONS.set(uuid, storage);
12+
return [uuid, storage];
13+
}
14+
15+
function getSessionStorageFromRequest(req: Request): object | void {
16+
let maybeSessionId = req.headers.get(SESSION_HEADER_NAME);
17+
18+
if (typeof maybeSessionId === "string" && SESSIONS.has(maybeSessionId)) {
19+
return SESSIONS.get(maybeSessionId);
20+
}
21+
}
22+
23+
Deno.serve((req: Request) => {
24+
const headers = new Headers();
25+
let storage: object;
26+
27+
if (req.headers.get(SESSION_HEADER_NAME)) {
28+
const maybeStorage = getSessionStorageFromRequest(req);
29+
30+
if (!maybeStorage) {
31+
return new Response(null, {
32+
status: STATUS_CODE.BadRequest
33+
});
34+
}
35+
36+
storage = maybeStorage;
37+
} else {
38+
const [sessionId, newStorage] = makeNewSession();
39+
40+
headers.set(SESSION_HEADER_NAME, sessionId);
41+
42+
storage = newStorage;
43+
}
44+
45+
if (!("count" in storage)) {
46+
storage["count"] = 0;
47+
} else {
48+
(storage["count"] as number)++;
49+
}
50+
51+
const count = storage["count"] as number;
52+
53+
return new Response(
54+
JSON.stringify({ count }),
55+
{
56+
headers
57+
}
58+
);
59+
});

0 commit comments

Comments
 (0)