fix: improve gateway PID recovery and port detection (#660)
- Refactor port detection into reusable getListeningPids/killListeningPids - Add ss command fallback when lsof is unavailable - Extract readPidFile for cleaner PID file reading - Remove unnecessary PowerShell candidate from Windows shell detection - Add PID validity check in gateway_state.json fallback (Number.isFinite + > 0) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+45
-32
@@ -64,7 +64,6 @@ function getWindowsShell() {
|
|||||||
const systemRoot = process.env.SystemRoot || 'C:\\Windows'
|
const systemRoot = process.env.SystemRoot || 'C:\\Windows'
|
||||||
const candidates = [
|
const candidates = [
|
||||||
process.env.ComSpec,
|
process.env.ComSpec,
|
||||||
join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
||||||
join(systemRoot, 'System32', 'cmd.exe'),
|
join(systemRoot, 'System32', 'cmd.exe'),
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
|
|
||||||
@@ -141,11 +140,12 @@ function getPort() {
|
|||||||
|
|
||||||
function getListeningPids(port) {
|
function getListeningPids(port) {
|
||||||
if (!port || isNaN(port)) return []
|
if (!port || isNaN(port)) return []
|
||||||
|
const uniquePids = (pids) => [...new Set(pids.filter(pid => Number.isFinite(pid)))]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const out = execSync('netstat -aon -p tcp', { encoding: 'utf-8' })
|
const out = execSync('netstat -aon -p tcp', { encoding: 'utf-8' })
|
||||||
return [...new Set(out.split('\n')
|
return uniquePids(out.split('\n')
|
||||||
.map(line => line.trim())
|
.map(line => line.trim())
|
||||||
.filter(line => line.includes('LISTENING'))
|
.filter(line => line.includes('LISTENING'))
|
||||||
.map(line => line.split(/\s+/))
|
.map(line => line.split(/\s+/))
|
||||||
@@ -154,15 +154,38 @@ function getListeningPids(port) {
|
|||||||
const listenPort = parseInt(address.split(':').pop(), 10)
|
const listenPort = parseInt(address.split(':').pop(), 10)
|
||||||
return listenPort === port
|
return listenPort === port
|
||||||
})
|
})
|
||||||
.map(parts => parseInt(parts[parts.length - 1], 10))
|
.map(parts => parseInt(parts[parts.length - 1], 10)))
|
||||||
.filter(pid => Number.isFinite(pid)))]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const out = execSync(`lsof -tiTCP:${port} -sTCP:LISTEN`, { encoding: 'utf-8' }).trim()
|
|
||||||
return [...new Set(out.split('\n').map(pid => parseInt(pid, 10)).filter(pid => Number.isFinite(pid)))]
|
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const out = execSync(`lsof -tiTCP:${port} -sTCP:LISTEN`, { encoding: 'utf-8' }).trim()
|
||||||
|
return uniquePids(out.split('\n').map(pid => parseInt(pid, 10)))
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const out = execSync(`ss -ltnp 'sport = :${port}'`, { encoding: 'utf-8' })
|
||||||
|
return uniquePids(out.split('\n')
|
||||||
|
.map(line => line.match(/pid=(\d+)/)?.[1])
|
||||||
|
.map(pid => parseInt(pid || '', 10)))
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function killListeningPids(port, pids = getListeningPids(port)) {
|
||||||
|
if (pids.length === 0) return
|
||||||
|
|
||||||
|
console.log(` ⚠ Port ${port} is in use by PID(s): ${pids.join(' ')}, killing...`)
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
execSync(`taskkill /F /PID ${pids.join(' /PID ')}`, { encoding: 'utf-8' })
|
||||||
|
} else {
|
||||||
|
execSync(`kill -9 ${pids.join(' ')}`, { encoding: 'utf-8' })
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recoverPidFromPort() {
|
function recoverPidFromPort() {
|
||||||
@@ -177,18 +200,25 @@ function recoverPidFromPort() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPid() {
|
function readPidFile() {
|
||||||
const recovered = recoverPidFromPort()
|
|
||||||
if (recovered) return recovered
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim())
|
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim())
|
||||||
if (pid && isRunning(pid)) return pid
|
return Number.isFinite(pid) ? pid : null
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPid() {
|
||||||
|
const pid = readPidFile()
|
||||||
|
if (pid) {
|
||||||
|
if (isRunning(pid)) return pid
|
||||||
|
removePid()
|
||||||
|
}
|
||||||
|
|
||||||
|
return recoverPidFromPort()
|
||||||
|
}
|
||||||
|
|
||||||
function isRunning(pid) {
|
function isRunning(pid) {
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 0)
|
process.kill(pid, 0)
|
||||||
@@ -216,29 +246,12 @@ function startDaemon(port) {
|
|||||||
removePid()
|
removePid()
|
||||||
|
|
||||||
// Check if port is already in use
|
// Check if port is already in use
|
||||||
try {
|
const occupied = getListeningPids(port)
|
||||||
const isWin = process.platform === 'win32'
|
if (occupied.length) {
|
||||||
let pids = ''
|
killListeningPids(port, occupied)
|
||||||
if (isWin) {
|
|
||||||
const out = execSync(`netstat -aon | findstr :${port}`, { encoding: 'utf-8' }).trim()
|
|
||||||
const lines = out.split('\n').filter(l => l.includes('LISTENING'))
|
|
||||||
pids = [...new Set(lines.map(l => l.trim().split(/\s+/).pop()).filter(Boolean))].join(' ')
|
|
||||||
} else {
|
|
||||||
pids = execSync(`lsof -ti:${port}`, { encoding: 'utf-8' }).trim()
|
|
||||||
}
|
|
||||||
if (pids) {
|
|
||||||
console.log(` ⚠ Port ${port} is in use by PID(s): ${pids}, killing...`)
|
|
||||||
if (isWin) {
|
|
||||||
execSync(`taskkill /F /PID ${pids.split(' ').join(' /PID ')}`, { encoding: 'utf-8' })
|
|
||||||
} else {
|
|
||||||
execSync(`kill -9 ${pids}`, { encoding: 'utf-8' })
|
|
||||||
}
|
|
||||||
// Brief wait for port to be released
|
// Brief wait for port to be released
|
||||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500)
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500)
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// Port is free
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirSync(PID_DIR, { recursive: true })
|
mkdirSync(PID_DIR, { recursive: true })
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export class GatewayManager {
|
|||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
const pid = typeof data.pid === 'number' ? data.pid : parseInt(data.pid, 10) || null
|
const pid = typeof data.pid === 'number' ? data.pid : parseInt(data.pid, 10) || null
|
||||||
const state = data?.gateway_state
|
const state = data?.gateway_state
|
||||||
return pid && (state === 'running' || state === 'starting') ? pid : null
|
return pid && Number.isFinite(pid) && pid > 0 && (state === 'running' || state === 'starting') ? pid : null
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user