mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
feat(nitro): improve dev worker stability (#1303)
This commit is contained in:
parent
34910f1218
commit
9d40a271ee
@ -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 } })
|
||||||
})
|
})
|
||||||
|
@ -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 = []
|
||||||
|
Loading…
Reference in New Issue
Block a user