fix(inbound): recover from doAppStateSync failures + route bg errors
Addresses two Copilot review comments on PR #392. 1. doAppStateSync stuck-state recovery (pre-existing bug, made more visible by Promise.allSettled silencing the propagation): If `resyncAppState` throws, the lines that set `syncState = Online` and call `ev.flush()` never execute — leaving the event buffer pinned and `syncState` stuck at `Syncing` until the buffer's own safety timeout expires. Wrap `resyncAppState` in try/catch that forces the state machine forward (`syncState = Online`, `ev.flush()`) before re-throwing, so live inbound events can flow even when app-state sync fails. Collections were already cleared, so blocked patches will be retried on the next creds.update tick. 2. Replace `void postUpsertWork` with a defensive `.catch(err => onUnexpectedError(err, ...))`. The previous `void` was clean but exposed the long-running socket to potential process termination from UnhandledPromiseRejection on Node ≥15. The new pattern matches existing background-work handling elsewhere in chats.ts (presence updates, init queries) and routes truly unexpected rejections through the centralised error handler instead of letting them surface as unhandled.
This commit is contained in:
+27
-8
@@ -1411,7 +1411,19 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
blockedCollections.clear()
|
||||
|
||||
logger.info('Doing app state sync')
|
||||
await resyncAppState(ALL_WA_PATCH_NAMES, true)
|
||||
try {
|
||||
await resyncAppState(ALL_WA_PATCH_NAMES, true)
|
||||
} catch (err) {
|
||||
// Failure recovery: without this, syncState would stay at Syncing
|
||||
// and ev.flush() would never run, leaving the event buffer pinned
|
||||
// until the buffer's own safety timeout expires. Force the state
|
||||
// machine forward so live inbound events can flow even if the
|
||||
// app-state resync failed (collections are already cleared, so
|
||||
// blocked patches will be retried on the next creds.update tick).
|
||||
syncState = SyncState.Online
|
||||
ev.flush()
|
||||
throw err
|
||||
}
|
||||
|
||||
// Sync is complete, go online and flush everything
|
||||
syncState = SyncState.Online
|
||||
@@ -1532,13 +1544,20 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// `postUpsertWork` cannot reject in practice — Promise.allSettled
|
||||
// inside postUpsertTasks never rejects, and the per-task warnings
|
||||
// already log any failures. `void` marks the floating promise as
|
||||
// intentional. If `postUpsertMutex` itself ever throws (e.g. runtime
|
||||
// corruption), the resulting unhandled rejection is the signal we
|
||||
// want — it's loud and points at a real bug.
|
||||
void postUpsertWork
|
||||
// `postUpsertWork` is not expected to reject — `Promise.allSettled`
|
||||
// inside `postUpsertTasks` never rejects, and per-task failures are
|
||||
// already logged inline. The defensive catch routes any truly
|
||||
// unexpected rejection (e.g. `postUpsertMutex` internal corruption,
|
||||
// future synchronous throws inside processMessage) through
|
||||
// `onUnexpectedError` instead of letting it surface as an
|
||||
// UnhandledPromiseRejection — which on Node ≥15 can terminate the
|
||||
// long-running socket process.
|
||||
postUpsertWork.catch(err =>
|
||||
onUnexpectedError(
|
||||
err,
|
||||
`processing post-upsert work for message ${msg.key?.id || 'unknown'} on ${msg.key?.remoteJid || 'unknown chat'}`
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user