From f70860ad60d0be68f8173ce66a97547e1a2b83cb Mon Sep 17 00:00:00 2001 From: nickbrowne Date: Fri, 23 Feb 2024 15:40:38 +1100 Subject: [PATCH] Check for late addition of listeners before reaping doc In the case where 2 clients are connected to a document, the first makes changes while the second is disconnected, then disconnects itself. When the second client connects it will immediately go into the catchup phase which does not immediately add a listener to the document - it only starts listening after the catchup is complete. This can result in a bit of a race condition where the initial check for listeners will return zero and incorrectly reap the document by the time the second client has completeted it's catchup. This avoids reaping the document from underneath clients that are catching up by adding an additional check immediately before reaping the document. --- src/server/model.coffee | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/server/model.coffee b/src/server/model.coffee index d1a919d..79ebad3 100644 --- a/src/server/model.coffee +++ b/src/server/model.coffee @@ -332,9 +332,16 @@ module.exports = Model = (db, options) -> clearTimeout doc.reapTimer doc.reapTimer = reapTimer = setTimeout -> tryWriteSnapshot docName, -> - # If the reaping timeout has been refreshed while we're writing the snapshot, or if we're - # in the middle of applying an operation, don't reap. - delete docs[docName] if docs[docName].reapTimer is reapTimer and doc.opQueue.busy is false + # If the document has newly added listeners, or the reaping timeout has been refreshed + # while we're writing the snapshot, or if we're in the middle of applying an operation, + # don't reap. + # + # Listeners can appear on the document "late" if they were in the middle of the catchup + # phase when `nextTick` was first called above. + if doc.eventEmitter.listeners('op').length == 0 and + docs[docName].reapTimer is reapTimer and + doc.opQueue.busy is false + delete docs[docName] , options.reapTime tryWriteSnapshot = (docName, callback) ->