-
-
Notifications
You must be signed in to change notification settings - Fork 96
Description
Description
In PostgresMessageQueue.listen(), the NOTIFY callback passed to this.#sql.listen() calls Temporal.Duration.from(delay) without any error handling. If the NOTIFY payload is malformed (e.g., empty string or invalid duration format), the resulting RangeError propagates through the postgres driver's NotificationResponse handler as an unhandled promise rejection, crashing the entire process.
Relevant code
https://github.com/fedify-dev/fedify/blob/2.0-maintenance/packages/postgres/src/mq.ts#L338-L353
const listen = await this.#sql.listen(
this.#channelName,
async (delay) => {
const duration = Temporal.Duration.from(delay); // ← No try-catch
const durationMs = duration.total("millisecond");
if (durationMs < 1) await safeSerializedPoll("notify-immediate");
else {
const timeout = setTimeout(() => {
timeouts.delete(timeout);
void safeSerializedPoll("notify-delayed");
}, durationMs);
timeouts.add(timeout);
}
},
() => safeSerializedPoll("subscribe"),
);Note that while the poll() errors are handled by safeSerializedPoll(), the Temporal.Duration.from() call happens before safeSerializedPoll() is reached, so the error is completely unguarded.
Error observed in production
Uncaught (in promise) RangeError: Temporal error: Parsing ended abruptly.
at NotificationResponse (postgres/src/connection.js:814:5)
at handle (postgres/src/connection.js:480:6)
at Socket.data (postgres/src/connection.js:315:9)
at Socket.emit (ext:deno_node/_events.mjs:436:20)
at addChunk (node:_stream_readable:452:12)
All three application instances crashed simultaneously when a malformed NOTIFY payload was received on the fedify_channel.
Suggested fix
Wrap the NOTIFY callback body in a try–catch:
async (delay) => {
try {
const duration = Temporal.Duration.from(delay);
const durationMs = duration.total("millisecond");
if (durationMs < 1) await safeSerializedPoll("notify-immediate");
else {
const timeout = setTimeout(() => {
timeouts.delete(timeout);
void safeSerializedPoll("notify-delayed");
}, durationMs);
timeouts.add(timeout);
}
} catch (error) {
logger.error(
"Error parsing NOTIFY payload {delay}: {error}",
{ delay, error },
);
// Fall back to an immediate poll
await safeSerializedPoll("notify-fallback");
}
},Environment
@fedify/postgresused with Fedify 2.0.2- PostgreSQL 17
- Deno runtime
- Multiple application instances sharing the same database