Skip to content

Commit

Permalink
feat(nitro): improve dev worker stability (#1303)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Oct 21, 2021
1 parent 1b93a42 commit f71714d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 50 deletions.
26 changes: 19 additions & 7 deletions src/runtime/entries/dev.ts
Original file line number Diff line number Diff line change
@@ -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 } })
})
100 changes: 57 additions & 43 deletions src/server/dev.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<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) {
// 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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 = []
Expand Down

0 comments on commit f71714d

Please sign in to comment.