Skip to content

Commit

Permalink
refactor: use process and net to improve restart and lock
Browse files Browse the repository at this point in the history
  • Loading branch information
GrinZero committed Oct 31, 2024
1 parent 9e4164d commit 73d5987
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 27 deletions.
5 changes: 2 additions & 3 deletions apps/koa/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ const run = () => {
const Router = require('koa-router')
const { createFetch } = require('ofetch')

const unregister = register({
autoOpenDevtool: true
})
const unregister = register()
register()

const app = new Koa()
const router = new Router()
Expand Down
2 changes: 1 addition & 1 deletion packages/network-debugger/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-network-devtools",
"version": "1.0.18",
"version": "1.0.19",
"description": "Inspecting Node.js's Network with Chrome DevTools",
"homepage": "https://grinzero.github.io/node-network-devtools/",
"main": "./dist/index.js",
Expand Down
56 changes: 35 additions & 21 deletions packages/network-debugger/src/core/fork.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { IS_DEV_MODE, READY_MESSAGE, RequestDetail } from '../common'
import { READY_MESSAGE, RequestDetail } from '../common'
import { type IncomingMessage } from 'http'
import WebSocket from 'ws'
import { ChildProcess, fork } from 'child_process'
import { __dirname } from '../common'
import { resolve as resolvePath } from 'path'
import { RegisterOptions } from '../common'
import fs from 'fs'
import { sleep, checkMainProcessAlive } from '../utils/process'
import { unlinkSafe } from '../utils/file'
import { warn } from '../utils'

let hasLogError = false
class ExpectError extends Error {
constructor(message: string) {
super(message)
}
}

export class MainProcess {
private ws: Promise<WebSocket>
Expand All @@ -16,24 +23,34 @@ export class MainProcess {

constructor(props: RegisterOptions & { key: string }) {
this.options = props
this.ws = new Promise<WebSocket>((resolve, reject) => {
this.ws = new Promise<WebSocket>(async (resolve, reject) => {
const lockFilePath = resolvePath(__dirname, `./${props.key}`)
if (fs.existsSync(lockFilePath)) {
fs.watchFile(lockFilePath, (e) => {
if (!fs.existsSync(lockFilePath)) {
reject(new Error('MainProcess is already running'))
}
})
return
// 读取 lock 文件中的进程号
const pid = fs.readFileSync(lockFilePath, 'utf-8')
await sleep(800)

// 检测该进程是否存活且 port 是否被占用
const isProcessAlice = await checkMainProcessAlive(pid, props.port!)
if (isProcessAlice) {
warn(`The main process with same options is already running, skip it.`)
return
}
// 如果进程不存在:
// 1. 热更新导致 process 重启
// 2. 上一个进程未成功删除 lock
// 都应该继续往下
unlinkSafe(lockFilePath)
}
fs.writeFileSync(lockFilePath, `LOCKED`)
fs.writeFileSync(lockFilePath, `${process.pid}`)
const socket = new WebSocket(`ws://localhost:${props.port}`)
socket.on('open', () => {
unlinkSafe(lockFilePath)
resolve(socket)
})
socket.on('error', () => {
this.openProcess(() => {
fs.unlinkSync(lockFilePath)
unlinkSafe(lockFilePath)
const socket = new WebSocket(`ws://localhost:${props.port}`)
socket.on('open', () => {
resolve(socket)
Expand All @@ -49,14 +66,14 @@ export class MainProcess {
})
})
.catch((e) => {
if (!hasLogError) {
!IS_DEV_MODE && (hasLogError = true)
console.warn('MainProcess Warning: ', e)
if (e instanceof ExpectError) {
return
}
throw e
})
}

private openProcess(callback?: () => void) {
private openProcess(callback?: (cp: ChildProcess) => void) {
const forkProcess = () => {
// fork a new process with options
const cp = fork(resolvePath(__dirname, './fork'), {
Expand All @@ -67,7 +84,7 @@ export class MainProcess {
})
const handleMsg = (e: any) => {
if (e === READY_MESSAGE) {
callback && callback()
callback && callback(cp)
cp.off('message', handleMsg)
}
}
Expand All @@ -81,13 +98,10 @@ export class MainProcess {

public async send(data: any) {
const ws = await this.ws.catch((err) => {
if (hasLogError) {
// has error in main process or websocket
if (err instanceof ExpectError) {
return null
}
!IS_DEV_MODE && (hasLogError = true)
console.warn('MainProcess Websocket Error: ', err)
return null
throw err
})
if (!ws) return
ws.send(JSON.stringify(data))
Expand Down
2 changes: 2 additions & 0 deletions packages/network-debugger/src/utils/console.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const log = console.log.bind(console, '\x1B[36m[node-network-debugger]:', '\x1B[32m')

export const warn = console.warn.bind(console, '\x1B[36m[node-network-debugger](warn):', '\x1B[33m')
7 changes: 7 additions & 0 deletions packages/network-debugger/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import fs from 'fs'

export const unlinkSafe = (path: string) => {
try {
fs.unlinkSync(path)
} catch {}
}
35 changes: 35 additions & 0 deletions packages/network-debugger/src/utils/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import net from 'net'

export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

export const checkMainProcessAlive = (pid: number | string, port: number) => {
try {
if (Number(pid) === process.pid) {
return Promise.resolve(true)
}
process.kill(Number(pid), 0)

// 检查 port 是否被占用
return new Promise<boolean>((resolve) => {
const server = net.createServer()

server.once('error', (err: any) => {
if (err.code === 'EADDRINUSE') {
// 端口被占用
resolve(false)
}
// fallback 其他错误
resolve(false)
})

server.once('listening', () => {
// 端口未被占用
server.close(() => resolve(true))
})

server.listen(port)
})
} catch {
return Promise.resolve(false)
}
}
6 changes: 4 additions & 2 deletions packages/network-debugger/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export default defineConfig(({ mode }) => ({
'node:diagnostics_channel',
'node:async_hooks',
'node:buffer',
'stream'
'stream',
'net'
],
output: {
globals: {
Expand All @@ -43,7 +44,8 @@ export default defineConfig(({ mode }) => ({
'node:diagnostics_channel': 'diagnostics_channel',
'node:async_hooks': 'async_hooks',
'node:buffer': 'buffer',
stream: 'stream'
stream: 'stream',
net: 'net'
}
}
}
Expand Down

0 comments on commit 73d5987

Please sign in to comment.