Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/app_core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2858,12 +2858,17 @@ export function createAppCore(cfg: Config, opts: CreateAppCoreOptions): App {
}
};

const close = () => {
const close = async () => {
closing = true;
touch.stop();
// Await the worker-thread pools so their threads are fully gone before we
// return. The host process (e.g. @prisma/dev) frees other native resources
// -- PGlite's WebAssembly JIT pages -- right after this resolves; a worker
// thread still tearing down at that moment races V8's process-global JIT
// bookkeeping and can abort the process on Linux.
await touch.stop();
await segmenter.stop(true);
uploader.stop(true);
indexer?.stop();
segmenter.stop(true);
metricsEmitter.stop();
expirySweeper.stop();
streamSizeReconciler.stop();
Expand Down
4 changes: 2 additions & 2 deletions src/local/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function startLocalDurableStreamsServer(opts: {
if (http) await http.close();
} finally {
try {
app?.close();
await app?.close();
} finally {
await releaseLock();
}
Expand All @@ -79,7 +79,7 @@ export async function startLocalDurableStreamsServer(opts: {
// ignore
}
try {
app?.close();
await app?.close();
} catch {
// ignore
}
Expand Down
24 changes: 15 additions & 9 deletions src/segment/segmenter_workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ export class SegmenterWorkerPool implements SegmenterController {
}
}

stop(_hard?: boolean): void {
async stop(_hard?: boolean): Promise<void> {
if (!this.started) return;
this.started = false;
for (const w of this.workers) {
try {
w.postMessage({ type: "stop" });
} catch {
// ignore
}
void w.terminate();
}
const workers = this.workers.slice();
this.workers.length = 0;
this.workerMemory.clear();
// Await termination so the worker threads are gone before stop() resolves;
// see the note in TouchProcessorWorkerPool.stop -- a lingering worker thread
// racing the host process's WASM teardown can abort the process on Linux.
await Promise.all(
workers.map((w) => {
try {
w.postMessage({ type: "stop" });
} catch {
// ignore
}
return w.terminate();
}),
);
}

getMemoryStats(): SegmenterMemoryStats {
Expand Down
4 changes: 2 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ statsReporter?.start();
histReporter?.start();

let shuttingDown = false;
const shutdown = (signal: NodeJS.Signals) => {
const shutdown = async (signal: NodeJS.Signals) => {
if (shuttingDown) return;
shuttingDown = true;
// eslint-disable-next-line no-console
Expand All @@ -207,7 +207,7 @@ const shutdown = (signal: NodeJS.Signals) => {
console.error("failed to stop HTTP server cleanly", err);
}
try {
app.close();
await app.close();
} catch (err) {
// eslint-disable-next-line no-console
console.error("failed to close application cleanly", err);
Expand Down
6 changes: 3 additions & 3 deletions src/touch/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ export class TouchProcessorManager {
}
}

stop(): void {
async stop(): Promise<void> {
this.stopping = true;
if (this.timer) clearInterval(this.timer);
this.timer = null;
this.pool.stop();
await this.pool.stop();
this.liveMetrics.stop();
for (const j of this.journals.values()) j.stop();
this.journals.clear();
Expand Down Expand Up @@ -305,7 +305,7 @@ export class TouchProcessorManager {
if (this.restartWorkerPoolRequested) {
this.restartWorkerPoolRequested = false;
try {
this.pool.restart();
await this.pool.restart();
this.lastWorkerPoolRestartAtMs = Date.now();
} catch (e) {
// eslint-disable-next-line no-console
Expand Down
30 changes: 19 additions & 11 deletions src/touch/worker_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,36 @@ export class TouchProcessorWorkerPool {
for (let i = 0; i < this.workerCount; i++) this.spawnWorker(i, generation);
}

stop(): void {
async stop(): Promise<void> {
if (!this.started) return;
this.started = false;
this.generation += 1;
for (const w of this.workers) {
try {
w.worker.postMessage({ type: "stop" });
} catch {
// ignore
}
void w.worker.terminate();
}
const workers = this.workers.slice();
this.workers.length = 0;
this.queue.length = 0;
for (const [id, p] of this.pending.entries()) {
p.resolve(Result.err({ kind: "worker_pool_failure", message: "worker pool stopped" }));
this.pending.delete(id);
}
// Await termination so the worker threads are actually gone before stop()
// resolves. Callers (the local server's close path) rely on this: a worker
// thread still tearing down while the host process frees other native
// resources -- e.g. PGlite's WebAssembly JIT pages in @prisma/dev -- races
// V8's process-global JIT bookkeeping and can abort the process on Linux.
await Promise.all(
workers.map((w) => {
try {
w.worker.postMessage({ type: "stop" });
} catch {
// ignore
}
return w.worker.terminate();
}),
);
}

restart(): void {
this.stop();
async restart(): Promise<void> {
await this.stop();
this.start();
}

Expand Down
Loading