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()
|
blockedCollections.clear()
|
||||||
|
|
||||||
logger.info('Doing app state sync')
|
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
|
// Sync is complete, go online and flush everything
|
||||||
syncState = SyncState.Online
|
syncState = SyncState.Online
|
||||||
@@ -1532,13 +1544,20 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// `postUpsertWork` cannot reject in practice — Promise.allSettled
|
// `postUpsertWork` is not expected to reject — `Promise.allSettled`
|
||||||
// inside postUpsertTasks never rejects, and the per-task warnings
|
// inside `postUpsertTasks` never rejects, and per-task failures are
|
||||||
// already log any failures. `void` marks the floating promise as
|
// already logged inline. The defensive catch routes any truly
|
||||||
// intentional. If `postUpsertMutex` itself ever throws (e.g. runtime
|
// unexpected rejection (e.g. `postUpsertMutex` internal corruption,
|
||||||
// corruption), the resulting unhandled rejection is the signal we
|
// future synchronous throws inside processMessage) through
|
||||||
// want — it's loud and points at a real bug.
|
// `onUnexpectedError` instead of letting it surface as an
|
||||||
void postUpsertWork
|
// 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