diff --git a/ts/background.ts b/ts/background.ts index cf3bb46caa..635208ca53 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -658,6 +658,17 @@ export async function startApp(): Promise { await window.waitForAllBatchers(); } + log.info('background/shutdown: flushing conversations'); + + // Flush debounced updates for conversations + await Promise.all( + window.ConversationController.getAll().map(convo => + convo.flushDebouncedUpdates() + ) + ); + + log.info('background/shutdown: waiting for all batchers'); + // A number of still-to-queue database queries might be waiting inside batchers. // We wait for these to empty first, and then shut down the data interface. await Promise.all([ @@ -665,6 +676,8 @@ export async function startApp(): Promise { window.waitForAllWaitBatchers(), ]); + log.info('background/shutdown: closing the database'); + // Shut down the data interface cleanly await window.Signal.Data.shutdown(); }, diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index aec4f78b27..b4f6b6eadb 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only /* eslint-disable camelcase */ -import { compact, isNumber } from 'lodash'; +import { compact, isNumber, throttle, debounce } from 'lodash'; import { batch as batchDispatch } from 'react-redux'; import PQueue from 'p-queue'; @@ -178,7 +178,7 @@ export class ConversationModel extends window.Backbone contactCollection?: Backbone.Collection; - debouncedUpdateLastMessage?: () => void; + debouncedUpdateLastMessage?: (() => void) & { flush(): void }; initialPromise?: Promise; @@ -293,14 +293,14 @@ export class ConversationModel extends window.Backbone // our first save to the database. Or first fetch from the database. this.initialPromise = Promise.resolve(); - this.throttledBumpTyping = window._.throttle(this.bumpTyping, 300); - this.debouncedUpdateLastMessage = window._.debounce( + this.throttledBumpTyping = throttle(this.bumpTyping, 300); + this.debouncedUpdateLastMessage = debounce( this.updateLastMessage.bind(this), 200 ); this.throttledUpdateSharedGroups = this.throttledUpdateSharedGroups || - window._.throttle(this.updateSharedGroups.bind(this), FIVE_MINUTES); + throttle(this.updateSharedGroups.bind(this), FIVE_MINUTES); this.contactCollection = this.getContactCollection(); this.contactCollection.on( @@ -362,11 +362,11 @@ export class ConversationModel extends window.Backbone // conversation for the first time. this.isFetchingUUID = this.isSMSOnly(); - this.throttledFetchSMSOnlyUUID = window._.throttle( + this.throttledFetchSMSOnlyUUID = throttle( this.fetchSMSOnlyUUID.bind(this), FIVE_MINUTES ); - this.throttledMaybeMigrateV1Group = window._.throttle( + this.throttledMaybeMigrateV1Group = throttle( this.maybeMigrateV1Group.bind(this), FIVE_MINUTES ); @@ -5465,6 +5465,18 @@ export class ConversationModel extends window.Backbone log.info(`conversation ${this.idForLogging()} open took ${delta}ms`); window.CI?.handleEvent('conversation:open', { delta }); } + + async flushDebouncedUpdates(): Promise { + try { + await this.debouncedUpdateLastMessage?.flush(); + } catch (error) { + const logId = this.idForLogging(); + log.error( + `flushDebouncedUpdates(${logId}): got error`, + Errors.toLogFormat(error) + ); + } + } } window.Whisper.Conversation = ConversationModel; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 17eca8b7db..4fc1f913d8 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -3133,8 +3133,10 @@ function removeUnprocessedSync(id: string | Array): void { return; } + // This can happen normally due to flushing of `cacheRemoveBatcher` in + // MessageReceiver. if (!id.length) { - throw new Error('removeUnprocessedSync: No ids to delete!'); + return; } assertSync(batchMultiVarQuery(db, id, removeUnprocessedsSync));