mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(chord): workers start from the base code the server booted with
createWorkers bundles the worker entry once (esbuild, Node builtins and installed packages external) and forks every worker from that snapshot, so an edit of the app's source while the server runs reaches workers only through a restart. The kit watches the bundle's inputs as base code. p2 creates its session workers with SessionWorkers.create and keeps the worker base's inputs as a dev restart baseline.
This commit is contained in:
parent
df0d88aac1
commit
5ff8c02708
19 changed files with 627 additions and 74 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// Server host: HTTP + WebSocket accept, routes `session:` hosts to session workers, loads the server facets.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync } from "node:fs";
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -42,7 +42,7 @@ import {
|
|||
} from "./bundles.ts";
|
||||
import { devControl } from "./dev.ts";
|
||||
import { readDevCommand, sendDevCommand, serveDevChannel } from "./dev-channel.ts";
|
||||
import { LoadedModules, loadedModuleHashes, loadedPluginFiles } from "./dev-modules.ts";
|
||||
import { contentHash, LoadedModules, loadedModuleHashes, loadedPluginFiles } from "./dev-modules.ts";
|
||||
import { createP2Plugins, listPlugins, PLUGINS_DIRECTORY, SERVER_PACKAGES, selectPlugins } from "./plugins.ts";
|
||||
import {
|
||||
contentSecurityPolicy,
|
||||
|
|
@ -174,7 +174,7 @@ export async function startServer(options: ServerOptions = {}): Promise<P2Server
|
|||
const address = http.address();
|
||||
const actualPort = typeof address === "object" && address !== null ? address.port : port;
|
||||
const info = { app: APP, dataDir, fake, allowedHosts: [...allowedHosts], port: actualPort };
|
||||
const workers = new SessionWorkers(
|
||||
const workers = await SessionWorkers.create(
|
||||
{
|
||||
host,
|
||||
plugins: chordPlugins,
|
||||
|
|
@ -202,6 +202,14 @@ export async function startServer(options: ServerOptions = {}): Promise<P2Server
|
|||
},
|
||||
BACKGROUND_CONTEXT,
|
||||
);
|
||||
// Every session worker runs the worker base Chord bundled just now (not the files on disk): in dev mode its source
|
||||
// files are a restart baseline, compared by content like the server's own modules.
|
||||
devModules?.set(
|
||||
"worker-base",
|
||||
workers.workers.inputs
|
||||
.filter((file) => !file.startsWith(PLUGINS_DIRECTORY))
|
||||
.map((file) => ({ file, hash: contentHash(readFileSync(file)) })),
|
||||
);
|
||||
// Capture before plugin resources open (e.g. provider SDK startup), not only after the logs facet activates.
|
||||
const stopLogs = captureProcessLogs("server");
|
||||
const serveWeb = options.web ?? true;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ const CRASH_RESTARTS = 5;
|
|||
const CRASH_WINDOW_MS = 60_000;
|
||||
|
||||
export class SessionWorkers implements SessionWorkerControl {
|
||||
readonly workers: Workers<typeof Session>;
|
||||
/** Chord's workers component, made by `create` (its worker base bundled first). */
|
||||
#workers: Workers<typeof Session> | undefined;
|
||||
readonly #options: SessionWorkersOptions;
|
||||
readonly #generations = new Map<string, number>();
|
||||
readonly #operations = new Map<string, Promise<unknown>>();
|
||||
|
|
@ -53,14 +54,35 @@ export class SessionWorkers implements SessionWorkerControl {
|
|||
#states: Readonly<Record<string, WorkerState>> = {};
|
||||
#generation = 0;
|
||||
|
||||
constructor(options: SessionWorkersOptions, context: Context) {
|
||||
private constructor(options: SessionWorkersOptions) {
|
||||
this.#options = options;
|
||||
processLogs.redactor.addDirectory(options.dataDir);
|
||||
for (const [key, value] of Object.entries(options.env ?? {})) {
|
||||
if (/(?:key|token|secret|password|cookie|auth)/i.test(key)) processLogs.redactor.addSecret(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The session workers: Chord's workers component created (it bundles the worker entry once, so every worker runs the
|
||||
* code the server booted with; a broken entry rejects), then followed.
|
||||
*/
|
||||
static async create(options: SessionWorkersOptions, context: Context): Promise<SessionWorkers> {
|
||||
const made = new SessionWorkers(options);
|
||||
made.#workers = await made.#createWorkers(context);
|
||||
void made.#follow();
|
||||
return made;
|
||||
}
|
||||
|
||||
/** Chord's workers component for the sessions. */
|
||||
get workers(): Workers<typeof Session> {
|
||||
// Set by `create` before the instance is handed out.
|
||||
return this.#workers!;
|
||||
}
|
||||
|
||||
#createWorkers(context: Context): Promise<Workers<typeof Session>> {
|
||||
const options = this.#options;
|
||||
const dataDir = realpathSync(options.dataDir);
|
||||
this.workers = createWorkers(
|
||||
return createWorkers(
|
||||
options.host,
|
||||
Session,
|
||||
{
|
||||
|
|
@ -99,7 +121,6 @@ export class SessionWorkers implements SessionWorkerControl {
|
|||
},
|
||||
context,
|
||||
);
|
||||
void this.#follow();
|
||||
}
|
||||
|
||||
/** Generation of the session's latest worker (0: never started). */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue