From 88af77ddc7c8150eb2c9870e5356f3278f4fa933 Mon Sep 17 00:00:00 2001 From: Sanju Sivalingam Date: Wed, 18 Feb 2026 13:56:34 +0530 Subject: [PATCH] fix: configure postgres idle timeout and connection recycling for Railway Railway proxy closes idle DB connections after ~60s, causing CONNECTION_CLOSED errors on stale sockets. Set idle_timeout=20s and max_lifetime=5m so postgres-js recycles connections before they die. Also fix sendCommand to fall back to persistent device ID on reconnect. --- .../com/thisux/droidclaw/connection/ReliableWebSocket.kt | 1 + server/src/db.ts | 6 +++++- server/src/ws/sessions.ts | 3 ++- web/src/lib/server/db/index.ts | 6 +++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/com/thisux/droidclaw/connection/ReliableWebSocket.kt b/android/app/src/main/java/com/thisux/droidclaw/connection/ReliableWebSocket.kt index 8274540..f3dab02 100644 --- a/android/app/src/main/java/com/thisux/droidclaw/connection/ReliableWebSocket.kt +++ b/android/app/src/main/java/com/thisux/droidclaw/connection/ReliableWebSocket.kt @@ -7,6 +7,7 @@ import com.thisux.droidclaw.model.DeviceInfoMsg import com.thisux.droidclaw.model.ServerMessage import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import io.ktor.client.engine.cio.endpoint import io.ktor.client.plugins.websocket.WebSockets import io.ktor.client.plugins.websocket.webSocket import io.ktor.websocket.Frame diff --git a/server/src/db.ts b/server/src/db.ts index 0cfc3bc..97cc466 100644 --- a/server/src/db.ts +++ b/server/src/db.ts @@ -3,5 +3,9 @@ import postgres from "postgres"; import { env } from "./env.js"; import * as schema from "./schema.js"; -const client = postgres(env.DATABASE_URL); +const client = postgres(env.DATABASE_URL, { + idle_timeout: 20, // close idle connections after 20s (Railway proxy kills at ~60s) + max_lifetime: 60 * 5, // recycle connections every 5 minutes + connect_timeout: 10, // fail fast on connection issues +}); export const db = drizzle(client, { schema }); diff --git a/server/src/ws/sessions.ts b/server/src/ws/sessions.ts index 19df9c1..69345b1 100644 --- a/server/src/ws/sessions.ts +++ b/server/src/ws/sessions.ts @@ -122,7 +122,8 @@ class SessionManager { command: Record, timeout = DEFAULT_COMMAND_TIMEOUT ): Promise { - const device = this.devices.get(deviceId); + // Try ephemeral ID first, then fall back to persistent DB ID (handles reconnects) + const device = this.devices.get(deviceId) ?? this.getDeviceByPersistentId(deviceId); if (!device) { return Promise.reject(new Error(`Device ${deviceId} not connected`)); } diff --git a/web/src/lib/server/db/index.ts b/web/src/lib/server/db/index.ts index fb8b355..3ef9c0b 100644 --- a/web/src/lib/server/db/index.ts +++ b/web/src/lib/server/db/index.ts @@ -5,6 +5,10 @@ import { env } from '$env/dynamic/private'; if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); -const client = postgres(env.DATABASE_URL); +const client = postgres(env.DATABASE_URL, { + idle_timeout: 20, + max_lifetime: 60 * 5, + connect_timeout: 10 +}); export const db = drizzle(client, { schema });