From f71714d3dde83a496de404d83c651c5774ae25bb Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Thu, 21 Oct 2021 13:54:55 +0200 Subject: [PATCH] feat(nitro): improve dev worker stability (#1303) --- src/runtime/entries/dev.ts | 26 +++++++--- src/server/dev.ts | 100 +++++++++++++++++++++---------------- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/runtime/entries/dev.ts b/src/runtime/entries/dev.ts index 9e6ea538fb..1a32ab041f 100644 --- a/src/runtime/entries/dev.ts +++ b/src/runtime/entries/dev.ts @@ -1,14 +1,26 @@ import '#polyfill' import { Server } from 'http' -import { parentPort } from 'worker_threads' -import type { AddressInfo } from 'net' +import { tmpdir } from 'os' +import { join } from 'path' +import { mkdirSync } from 'fs' +import { threadId, parentPort } from 'worker_threads' import { handle } from '../server' const server = new Server(handle) -const netServer = server.listen(0, () => { - parentPort.postMessage({ - event: 'listen', - port: (netServer.address() as AddressInfo).port - }) +function createSocket () { + const isWin = process.platform === 'win32' + const socketName = `worker-${process.pid}-${threadId}.sock` + if (isWin) { + return join('\\\\.\\pipe\\nitro', socketName) + } else { + const socketDir = join(tmpdir(), 'nitro') + mkdirSync(socketDir, { recursive: true }) + return join(socketDir, socketName) + } +} + +const socketPath = createSocket() +server.listen(socketPath, () => { + parentPort.postMessage({ event: 'listen', address: { socketPath } }) }) diff --git a/src/server/dev.ts b/src/server/dev.ts index 7edb83a8c4..45c59d387f 100644 --- a/src/server/dev.ts +++ b/src/server/dev.ts @@ -1,7 +1,7 @@ import { Worker } from 'worker_threads' import { IncomingMessage, ServerResponse } from 'http' -import { promises as fsp } from 'fs' +import { existsSync, promises as fsp } from 'fs' import { loading as loadingTemplate } from '@nuxt/design' import chokidar, { FSWatcher } from 'chokidar' import debounce from 'p-debounce' @@ -15,44 +15,59 @@ import connect from 'connect' import type { NitroContext } from '../context' import { handleVfs } from './vfs' +export interface NitroWorker { + worker: Worker, + address: string +} + +function initWorker (filename): Promise { + return new Promise((resolve, reject) => { + const worker = new Worker(filename) + worker.once('exit', (code) => { + if (code) { + reject(new Error('[worker] exited with code: ' + code)) + } + }) + worker.on('error', (err) => { + err.message = '[worker] ' + err.message + reject(err) + }) + worker.on('message', (event) => { + if (event && event.address) { + resolve({ + worker, + address: event.address + } as NitroWorker) + } + }) + }) +} + +async function killWorker (worker: NitroWorker) { + await worker.worker.terminate() + worker.worker = null + if (worker.address && existsSync(worker.address)) { + await fsp.rm(worker.address).catch(() => {}) + } +} + export function createDevServer (nitroContext: NitroContext) { // Worker const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs') - let pendingWorker: Worker | null - let activeWorker: Worker - let workerAddress: string | null + + let currentWorker: NitroWorker + async function reload () { - if (pendingWorker) { - await pendingWorker.terminate() - workerAddress = null - pendingWorker = null - } - if (!(await fsp.stat(workerEntry)).isFile) { - throw new Error('Entry not found: ' + workerEntry) + // Create a new worker + const newWorker = await initWorker(workerEntry) + + // Kill old worker in background + if (currentWorker) { + killWorker(currentWorker).catch(err => console.error(err)) } - return new Promise((resolve, reject) => { - const worker = pendingWorker = new Worker(workerEntry) - worker.once('exit', (code) => { - if (code) { - reject(new Error('[worker] exited with code: ' + code)) - } - }) - worker.on('error', (err) => { - err.message = '[worker] ' + err.message - reject(err) - }) - worker.on('message', (event) => { - if (event && event.port) { - workerAddress = 'http://localhost:' + event.port - if (activeWorker) { - activeWorker.terminate() - } - activeWorker = worker - pendingWorker = null - resolve(workerAddress) - } - }) - }) + + // Replace new worker as current + currentWorker = newWorker } // App @@ -76,11 +91,13 @@ export function createDevServer (nitroContext: NitroContext) { // SSR Proxy const proxy = httpProxy.createProxy() - const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => proxy.web(req, res, { target: workerAddress }, (_err: unknown) => { - // console.error('[proxy]', err) - })) + const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => { + proxy.web(req, res, { target: currentWorker.address }, (error: unknown) => { + console.error('[proxy]', error) + }) + }) app.use((req, res) => { - if (workerAddress) { + if (currentWorker?.address) { // Workaround to pass legacy req.spa to proxy // @ts-ignore if (req.spa) { @@ -119,11 +136,8 @@ export function createDevServer (nitroContext: NitroContext) { if (watcher) { await watcher.close() } - if (activeWorker) { - await activeWorker.terminate() - } - if (pendingWorker) { - await pendingWorker.terminate() + if (currentWorker) { + await killWorker(currentWorker) } await Promise.all(listeners.map(l => l.close())) listeners = []