feat: registerShutdownHandlers — SIGTERM/SIGINT stops heartbeat + unregisters worker
This commit is contained in:
parent
994f28f103
commit
512b06cd75
2 changed files with 88 additions and 0 deletions
28
src/presence/shutdown.ts
Normal file
28
src/presence/shutdown.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { unregisterWorker } from './worker.js'
|
||||
|
||||
export function registerShutdownHandlers(opts: {
|
||||
userId: string
|
||||
tokenId: string
|
||||
stopHeartbeat: () => void
|
||||
}): void {
|
||||
let shuttingDown = false
|
||||
|
||||
const handleShutdown = async (signal: string) => {
|
||||
if (shuttingDown) return
|
||||
shuttingDown = true
|
||||
|
||||
console.log(`[scrum4me-mcp] ${signal} received — cleaning up worker presence`)
|
||||
opts.stopHeartbeat()
|
||||
|
||||
try {
|
||||
await unregisterWorker({ userId: opts.userId, tokenId: opts.tokenId })
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => { void handleShutdown('SIGTERM') })
|
||||
process.on('SIGINT', () => { void handleShutdown('SIGINT') })
|
||||
}
|
||||
60
src/presence/worker.ts
Normal file
60
src/presence/worker.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { Client } from 'pg'
|
||||
import { prisma } from '../prisma.js'
|
||||
|
||||
export async function pgNotify(payload: Record<string, unknown>): Promise<void> {
|
||||
const pg = new Client({ connectionString: process.env.DATABASE_URL })
|
||||
await pg.connect()
|
||||
await pg.query('SELECT pg_notify($1, $2)', ['scrum4me_changes', JSON.stringify(payload)])
|
||||
await pg.end()
|
||||
}
|
||||
|
||||
export async function registerWorker(opts: {
|
||||
userId: string
|
||||
tokenId: string
|
||||
productId?: string | null
|
||||
notify?: (payload: Record<string, unknown>) => Promise<void>
|
||||
}): Promise<void> {
|
||||
await prisma.claudeWorker.upsert({
|
||||
where: { token_id: opts.tokenId },
|
||||
create: {
|
||||
user_id: opts.userId,
|
||||
token_id: opts.tokenId,
|
||||
product_id: opts.productId ?? null,
|
||||
},
|
||||
update: {
|
||||
last_seen_at: new Date(),
|
||||
product_id: opts.productId ?? null,
|
||||
},
|
||||
})
|
||||
|
||||
const notify = opts.notify ?? pgNotify
|
||||
try {
|
||||
await notify({
|
||||
type: 'worker_connected',
|
||||
user_id: opts.userId,
|
||||
token_id: opts.tokenId,
|
||||
product_id: opts.productId ?? null,
|
||||
})
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
}
|
||||
|
||||
export async function unregisterWorker(opts: {
|
||||
userId: string
|
||||
tokenId: string
|
||||
notify?: (payload: Record<string, unknown>) => Promise<void>
|
||||
}): Promise<void> {
|
||||
await prisma.claudeWorker.deleteMany({ where: { token_id: opts.tokenId } }).catch(() => {})
|
||||
|
||||
const notify = opts.notify ?? pgNotify
|
||||
try {
|
||||
await notify({
|
||||
type: 'worker_disconnected',
|
||||
user_id: opts.userId,
|
||||
token_id: opts.tokenId,
|
||||
})
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue