fix(server): graceful shutdown on SIGTERM/SIGINT with reset-connected and timeout (#26) - #131
Conversation
| let paletteId = entry.paletteId; | ||
|
|
||
| if (!paletteId) { | ||
| // Asignar siguiente ID de paleta para el mapa (por encima de las paletas estándar base) | ||
| const nextIdResult = await pool.query<{ next_id: number }>( | ||
| `SELECT COALESCE(MAX(palette_id), 1000) + 1 AS next_id | ||
| FROM game_map_palette_overrides | ||
| WHERE map_num = $1`, | ||
| [mapNum], | ||
| ); | ||
| paletteId = Number(nextIdResult.rows[0]?.next_id ?? 1001); | ||
| } | ||
|
|
||
| const graphicsArray = entry.graphics.map((g) => (g == null ? 0 : g)); | ||
|
|
There was a problem hiding this comment.
💡 Edge Case: Palette next_id allocation is racy under concurrency
When paletteId is omitted, the next id is computed via SELECT COALESCE(MAX(palette_id),1000)+1 and then inserted in a separate statement. Two concurrent upsertPaletteEntry calls for the same map can compute the same next_id; the ON CONFLICT (map_num, palette_id) DO UPDATE then makes the second silently overwrite the first entry instead of creating a new one. Since map editing is low-frequency admin-only, impact is limited, but consider using a sequence or a single INSERT ... SELECT with a computed id to make allocation atomic.
Was this helpful? React with 👍 / 👎
| const accountKey = | ||
| (user as any).idAccount || | ||
| (user as any).account_id || | ||
| (client as any).accountId || | ||
| socket.getIp(client) || | ||
| idUser; |
There was a problem hiding this comment.
💡 Quality: Account-key fallback references non-existent fields
The accountKey chain checks (user as any).account_id and (client as any).accountId, but only user.idAccount actually exists on the runtime types (RuntimeCharacter.idAccount). The two extra as any conditions are dead code that will never contribute and mask type checking. Since idAccount is optional and undefined for some sessions, those clients silently fall back to socket.getIp(client), re-introducing the CGNAT grouping the PR aims to avoid — worth confirming idAccount is reliably populated. Simplify to user.idAccount ?? socket.getIp(client) ?? idUser.
Was this helpful? React with 👍 / 👎
Code Review 👍 Approved with suggestions 1 resolved / 3 findingsImplements graceful server shutdown on SIGTERM and SIGINT with websocket notifications and database connection resets. Consider addressing the racy palette next_id allocation and the non-existent accountKey field references. 💡 Edge Case: Palette next_id allocation is racy under concurrency📄 api/src/repositories/worldBuilder.ts:619-633 When 💡 Quality: Account-key fallback references non-existent fields📄 server/src/server.ts:782-787 The ✅ 1 resolved✅ Edge Case: Race timeout timer not cleared after fetch wins
🤖 Prompt for agentsOptionsAuto-apply is off → Gitar will not commit updates to this branch. Comment with these commands to change the behavior for this request:
Important Your trial ends in 7 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more. Was this helpful? React with 👍 / 👎 | Gitar |
Closes #26
Summary of Changes
Implements graceful server shutdown on
SIGTERMandSIGINTto cleanly unmark connected characters in the database and notify connected players before termination.Features & Robustness
gracefulShutdown(signal)with re-entrancy protection.1000and reason"Servidor reiniciando. Por favor vuelve a conectar en unos momentos.".POST /internal/characters/reset-connectedto reset character connection state in PostgreSQL.resetConnectedCharactersOnStartup) remains active for abrupt crashes.Summary by Gitar
api/src/repositories/worldBuilder.tsapi/src/server.tsThis will update automatically on new commits.