Skip to content

Move Twilio/Teams sends out of DB transactions (sensor + vitals paths)#396

Merged
CCheng00 merged 1 commit into
mainfrom
fix-sends-outside-transaction
Jul 15, 2026
Merged

Move Twilio/Teams sends out of DB transactions (sensor + vitals paths)#396
CCheng00 merged 1 commit into
mainfrom
fix-sends-outside-transaction

Conversation

@CCheng00

Copy link
Copy Markdown
Contributor

External sends (Twilio SMS / MS Teams) ran inside open SERIALIZABLE transactions, holding a pooled pg client checked out during the (often slow) network call. When a send was slow the transaction sat idle past idle_in_transaction_session_timeout (60s); Postgres killed the connection and the checked-out client emitted an unhandled 'error' -> uncaughtException -> process exit. This removes the root cause: sends now run AFTER commit, so no pg client is held during them.

Pattern: handlers collect deferred-send descriptors ({ describe, run }) during the transaction and return them; the caller commits, then awaits helpers.flushDeferredSends (Promise.allSettled with per-send try/catch, never throws). Twilio event rows stay in-transaction (their inputs don't depend on the send); Teams sends move together with their createTeamsEvent(messageId) write since that write needs the returned id. The flush is awaited before the HTTP response so send-before-ack timing is unchanged. No external-call timeouts added (a slow post-commit send is harmless), preserving delivery and the Teams messageId chain.

Scope: sensorEvents.js (processSensorEvent/handleNewSession/ handleExistingSession) and vitals.js (checkDeviceDisconnectionVitals/ processHeartbeat). eventHandlers.js responder-reply dispatch is a separate follow-up.

External sends (Twilio SMS / MS Teams) ran inside open SERIALIZABLE
transactions, holding a pooled pg client checked out during the (often
slow) network call. When a send was slow the transaction sat idle past
idle_in_transaction_session_timeout (60s); Postgres killed the
connection and the checked-out client emitted an unhandled 'error' ->
uncaughtException -> process exit. This removes the root cause: sends
now run AFTER commit, so no pg client is held during them.

Pattern: handlers collect deferred-send descriptors ({ describe, run })
during the transaction and return them; the caller commits, then awaits
helpers.flushDeferredSends (Promise.allSettled with per-send try/catch,
never throws). Twilio event rows stay in-transaction (their inputs don't
depend on the send); Teams sends move together with their
createTeamsEvent(messageId) write since that write needs the returned id.
The flush is awaited before the HTTP response so send-before-ack timing
is unchanged. No external-call timeouts added (a slow post-commit send
is harmless), preserving delivery and the Teams messageId chain.

Scope: sensorEvents.js (processSensorEvent/handleNewSession/
handleExistingSession) and vitals.js (checkDeviceDisconnectionVitals/
processHeartbeat). eventHandlers.js responder-reply dispatch is a
separate follow-up.
@CCheng00
CCheng00 merged commit db626b5 into main Jul 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant