Refactor: Fix Event Buffer Deadlock with State Machine (#1633)

* feat: implement state machine for chat synchronization and buffer management

* test: Add Jest configuration and tests for connection deadlock handling

- Created a Jest configuration file to set up testing environment.
- Implemented utility functions for creating isolated authentication sessions and mocking WebSocket connections.
- Added a test case to ensure that the connection does not deadlock when history sync is disabled, verifying the correct handling of message events.

* feat: add GitHub Actions workflow for running tests

* chore: sort import lint

* fix: implement timeout handling for AwaitingInitialSync state in chat socket, maybe fix memory leak

* feat: enhance chat synchronization by refining AwaitingInitialSync handling and adding history sync checks
This commit is contained in:
João Lucas de Oliveira Lopes
2025-08-06 19:15:41 -03:00
committed by GitHub
parent 1a721bb242
commit 812e53e4eb
13 changed files with 1137 additions and 717 deletions
+14 -22
View File
@@ -56,10 +56,9 @@ type BaileysBufferableEventEmitter = BaileysEventEmitter & {
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): (...args: A) => Promise<T>
/**
* flushes all buffered events
* @param force if true, will flush all data regardless of any pending buffers
* @returns returns true if the flush actually happened, otherwise false
*/
flush(force?: boolean): boolean
flush(): boolean
/** is there an ongoing buffer */
isBuffering(): boolean
}
@@ -74,7 +73,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
const historyCache = new Set<string>()
let data = makeBufferData()
let buffersInProgress = 0
let isBuffering = false
// take the generic event and fire it as a baileys event
ev.on('event', (map: BaileysEventData) => {
@@ -84,28 +83,22 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
})
function buffer() {
buffersInProgress += 1
if (!isBuffering) {
logger.info('Event buffer activated')
isBuffering = true
}
}
function flush(force = false) {
// no buffer going on
if (!buffersInProgress) {
function flush() {
if (!isBuffering) {
return false
}
if (!force) {
// reduce the number of buffers in progress
buffersInProgress -= 1
// if there are still some buffers going on
// then we don't flush now
if (buffersInProgress) {
return false
}
}
logger.info('Flushing event buffer')
isBuffering = false
const newData = makeBufferData()
const chatUpdates = Object.values(data.chatUpdates)
// gather the remaining conditional events so we re-queue them
let conditionalChatUpdatesLeft = 0
for (const update of chatUpdates) {
if (update.conditional) {
@@ -139,7 +132,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
}
},
emit<T extends BaileysEvent>(event: BaileysEvent, evData: BaileysEventMap[T]) {
if (buffersInProgress && BUFFERABLE_EVENT_SET.has(event)) {
if (isBuffering && BUFFERABLE_EVENT_SET.has(event)) {
append(data, historyCache, event as BufferableEvent, evData, logger)
return true
}
@@ -147,7 +140,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
return ev.emit('event', { [event]: evData })
},
isBuffering() {
return buffersInProgress > 0
return isBuffering
},
buffer,
flush,
@@ -155,10 +148,9 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
return async (...args) => {
buffer()
try {
const result = await work(...args)
return result
return await work(...args)
} finally {
flush()
// Flushing is now controlled centrally by the state machine.
}
}
},