feat(nitro): improve dev worker stability (#1303)

This commit is contained in:
pooya parsa 2021-10-21 13:54:55 +02:00 committed by GitHub
parent 34910f1218
commit 9d40a271ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 50 deletions

View File

@ -1,14 +1,26 @@
import '#polyfill' import '#polyfill'
import { Server } from 'http' import { Server } from 'http'
import { parentPort } from 'worker_threads' import { tmpdir } from 'os'
import type { AddressInfo } from 'net' import { join } from 'path'
import { mkdirSync } from 'fs'
import { threadId, parentPort } from 'worker_threads'
import { handle } from '../server' import { handle } from '../server'
const server = new Server(handle) const server = new Server(handle)
const netServer = server.listen(0, () => { function createSocket () {
parentPort.postMessage({ const isWin = process.platform === 'win32'
event: 'listen', const socketName = `worker-${process.pid}-${threadId}.sock`
port: (netServer.address() as AddressInfo).port 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 } })
}) })

View File

@ -1,7 +1,7 @@
import { Worker } from 'worker_threads' import { Worker } from 'worker_threads'
import { IncomingMessage, ServerResponse } from 'http' 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 { loading as loadingTemplate } from '@nuxt/design'
import chokidar, { FSWatcher } from 'chokidar' import chokidar, { FSWatcher } from 'chokidar'
import debounce from 'p-debounce' import debounce from 'p-debounce'
@ -15,44 +15,59 @@ import connect from 'connect'
import type { NitroContext } from '../context' import type { NitroContext } from '../context'
import { handleVfs } from './vfs' import { handleVfs } from './vfs'
export interface NitroWorker {
worker: Worker,
address: string
}
function initWorker (filename): Promise<NitroWorker> {
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) { export function createDevServer (nitroContext: NitroContext) {
// Worker // Worker
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs') const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs')
let pendingWorker: Worker | null
let activeWorker: Worker let currentWorker: NitroWorker
let workerAddress: string | null
async function reload () { async function reload () {
if (pendingWorker) { // Create a new worker
await pendingWorker.terminate() const newWorker = await initWorker(workerEntry)
workerAddress = null
pendingWorker = null // Kill old worker in background
if (currentWorker) {
killWorker(currentWorker).catch(err => console.error(err))
} }
if (!(await fsp.stat(workerEntry)).isFile) {
throw new Error('Entry not found: ' + workerEntry) // Replace new worker as current
} currentWorker = newWorker
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)
}
})
})
} }
// App // App
@ -76,11 +91,13 @@ export function createDevServer (nitroContext: NitroContext) {
// SSR Proxy // SSR Proxy
const proxy = httpProxy.createProxy() const proxy = httpProxy.createProxy()
const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => proxy.web(req, res, { target: workerAddress }, (_err: unknown) => { const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => {
// console.error('[proxy]', err) proxy.web(req, res, { target: currentWorker.address }, (error: unknown) => {
})) console.error('[proxy]', error)
})
})
app.use((req, res) => { app.use((req, res) => {
if (workerAddress) { if (currentWorker?.address) {
// Workaround to pass legacy req.spa to proxy // Workaround to pass legacy req.spa to proxy
// @ts-ignore // @ts-ignore
if (req.spa) { if (req.spa) {
@ -119,11 +136,8 @@ export function createDevServer (nitroContext: NitroContext) {
if (watcher) { if (watcher) {
await watcher.close() await watcher.close()
} }
if (activeWorker) { if (currentWorker) {
await activeWorker.terminate() await killWorker(currentWorker)
}
if (pendingWorker) {
await pendingWorker.terminate()
} }
await Promise.all(listeners.map(l => l.close())) await Promise.all(listeners.map(l => l.close()))
listeners = [] listeners = []