Skip to content

Commit 57be109

Browse files
committed
refactor: single kill confirmation queue
1 parent 740635f commit 57be109

1 file changed

Lines changed: 39 additions & 29 deletions

File tree

src/main/ts/ps.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,17 @@ export const kill = (pid: string | number, opts?: TPsNext | TPsKillOptions | TPs
172172

173173
return new Promise<any>((resolve, reject) => {
174174
let done = false
175+
const entry: TKillEntry = { pid: sPid, registered: 0, interval, settle: noop }
175176
const settle = (err?: unknown) => {
176177
if (done) return
177178
done = true
178179
clearTimeout(timer)
180+
killPending.delete(entry)
179181
if (err) reject(err)
180182
else resolve(pid)
181183
next?.(err ?? null, pid)
182184
}
185+
entry.settle = settle
183186

184187
const timer = setTimeout(() => settle(new Error('Kill process timeout')), timeout * 1000)
185188

@@ -190,40 +193,47 @@ export const kill = (pid: string | number, opts?: TPsNext | TPsKillOptions | TPs
190193
return
191194
}
192195

193-
let since = Date.now()
194-
const poll = (): unknown =>
195-
sharedSnapshot(since).then(({ startedAt, list }) => {
196-
if (done) return
197-
since = startedAt + 1
198-
if (list.some(p => p.pid === sPid)) {
199-
setTimeout(poll, Math.max(0, startedAt + interval - Date.now()))
200-
} else {
201-
settle()
202-
}
203-
}, settle)
204-
205-
poll()
196+
entry.registered = Date.now()
197+
killPending.add(entry)
198+
scheduleKillTick()
206199
})
207200
}
208201

209202
/**
210-
* Returns a `lookup()` snapshot started at or after `since`, dedupes concurrent callers.
211-
* Lets parallel `kill()` polls share a single `ps` invocation: join the in-flight one
212-
* if it's fresh enough, otherwise wait for the next queued one.
203+
* Shared kill-confirmation loop. A single `lookup()` per tick serves *all* pending kills
204+
* registered before the tick's snapshot started — so a flood of kills can never indefinitely
205+
* postpone any single confirmation, and we never spawn more than one `ps` per tick.
213206
*/
214-
type TSnapshot = { startedAt: number, list: TPsLookupEntry[] }
215-
let inflight: { startedAt: number, promise: Promise<TSnapshot> } | null = null
216-
let queued: Promise<TSnapshot> | null = null
217-
const sharedSnapshot = (since: number): Promise<TSnapshot> => {
218-
if (inflight && inflight.startedAt >= since) return inflight.promise
219-
if (queued) return queued
220-
const after = inflight?.promise.catch(noop) ?? Promise.resolve()
221-
return queued = after.then(() => {
222-
queued = null
223-
const startedAt = Date.now()
224-
const promise = lookup().then(list => ({ startedAt, list }))
225-
inflight = { startedAt, promise }
226-
return promise.finally(() => { if (inflight?.promise === promise) inflight = null })
207+
type TKillEntry = { pid: string, registered: number, interval: number, settle: (err?: unknown) => void }
208+
const killPending = new Set<TKillEntry>()
209+
let killTickTimer: NodeJS.Timeout | null = null
210+
let killTickRunning = false
211+
212+
const scheduleKillTick = (lastStart = 0): void => {
213+
if (killTickTimer || killTickRunning || killPending.size === 0) return
214+
let minInterval = Infinity
215+
for (const k of killPending) if (k.interval < minInterval) minInterval = k.interval
216+
const delay = lastStart === 0 ? 0 : Math.max(0, lastStart + minInterval - Date.now())
217+
killTickTimer = setTimeout(runKillTick, delay)
218+
}
219+
220+
const runKillTick = (): void => {
221+
killTickTimer = null
222+
if (killPending.size === 0) return
223+
killTickRunning = true
224+
const startedAt = Date.now()
225+
lookup().then(list => {
226+
const alive = new Set(list.map(p => p.pid))
227+
for (const k of killPending) {
228+
// Snapshot predates this kill's process.kill — can't trust it, wait for next tick
229+
if (k.registered >= startedAt) continue
230+
if (!alive.has(k.pid)) k.settle()
231+
}
232+
killTickRunning = false
233+
scheduleKillTick(startedAt)
234+
}, err => {
235+
for (const k of killPending) k.settle(err)
236+
killTickRunning = false
227237
})
228238
}
229239

0 commit comments

Comments
 (0)