Avoid race condition in AttachmentDownloadQueue

This commit is contained in:
automated-signal 2024-06-13 12:16:26 -05:00 committed by GitHub
parent eb2573187d
commit e72a5c3320
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,16 +1,16 @@
// Copyright 2023 Signal Messenger, LLC // Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { MessageModel } from '../models/messages'; import type { MessageModel } from '../models/messages';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { isMoreRecentThan } from './timestamp'; import { isMoreRecentThan } from './timestamp';
import { isNotNil } from './isNotNil';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000; const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250; const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250;
let isEnabled = true; let isEnabled = true;
let attachmentDownloadQueue: Array<MessageModel> | undefined = []; let attachmentDownloadQueue: Array<string> | undefined = [];
const queueEmptyCallbacks: Set<() => void> = new Set(); const queueEmptyCallbacks: Set<() => void> = new Set();
export function shouldUseAttachmentDownloadQueue(): boolean { export function shouldUseAttachmentDownloadQueue(): boolean {
@ -25,6 +25,11 @@ export function registerQueueEmptyCallback(callback: () => void): void {
queueEmptyCallbacks.add(callback); queueEmptyCallbacks.add(callback);
} }
function onQueueEmpty() {
queueEmptyCallbacks.forEach(callback => callback());
queueEmptyCallbacks.clear();
}
export function addToAttachmentDownloadQueue( export function addToAttachmentDownloadQueue(
idLog: string, idLog: string,
message: MessageModel message: MessageModel
@ -33,7 +38,7 @@ export function addToAttachmentDownloadQueue(
return; return;
} }
attachmentDownloadQueue.unshift(message); attachmentDownloadQueue.unshift(message.id);
log.info( log.info(
`${idLog}: Adding to attachmentDownloadQueue`, `${idLog}: Adding to attachmentDownloadQueue`,
@ -42,48 +47,63 @@ export function addToAttachmentDownloadQueue(
} }
export async function flushAttachmentDownloadQueue(): Promise<void> { export async function flushAttachmentDownloadQueue(): Promise<void> {
if (!attachmentDownloadQueue) {
return;
}
// NOTE: ts/models/messages.ts expects this global to become undefined // NOTE: ts/models/messages.ts expects this global to become undefined
// once we stop processing the queue. // once we stop processing the queue.
isEnabled = false; isEnabled = false;
const attachmentsToDownload = attachmentDownloadQueue.filter( if (!attachmentDownloadQueue?.length) {
(message, index) => onQueueEmpty();
index <= MAX_ATTACHMENT_MSGS_TO_DOWNLOAD || return;
isMoreRecentThan(message.getReceivedAt(), MAX_ATTACHMENT_DOWNLOAD_AGE) || }
// Stickers and long text attachments has to be downloaded for UI
// to display the message properly. const messageIdsToDownload = attachmentDownloadQueue.slice(
message.hasRequiredAttachmentDownloads() 0,
MAX_ATTACHMENT_MSGS_TO_DOWNLOAD
);
const messageIdsToSave: Array<string> = [];
let numMessagesQueued = 0;
await Promise.all(
messageIdsToDownload.map(async messageId => {
const message = window.MessageCache.__DEPRECATED$getById(messageId);
if (!message) {
log.warn(
'attachmentDownloadQueue: message not found in messageCache, maybe it was deleted?'
);
return;
}
if (
isMoreRecentThan(
message.getReceivedAt(),
MAX_ATTACHMENT_DOWNLOAD_AGE
) ||
// Stickers and long text attachments has to be downloaded for UI
// to display the message properly.
message.hasRequiredAttachmentDownloads()
) {
const shouldSave = await message.queueAttachmentDownloads();
if (shouldSave) {
messageIdsToSave.push(messageId);
}
numMessagesQueued += 1;
}
})
); );
log.info( log.info(
'Downloading recent attachments of total attachments', `Downloading recent attachments for ${numMessagesQueued} ` +
attachmentsToDownload.length, `of ${attachmentDownloadQueue.length} total messages`
attachmentDownloadQueue.length
); );
const messagesWithDownloads = await Promise.all( const messagesToSave = messageIdsToSave
attachmentsToDownload.map(message => { .map(messageId => window.MessageCache.accessAttributes(messageId))
const updatedMessage = .filter(isNotNil);
window.MessageCache.__DEPRECATED$getById(message.id) ?? message;
return updatedMessage.queueAttachmentDownloads();
})
);
const messagesToSave: Array<MessageAttributesType> = [];
messagesWithDownloads.forEach((shouldSave, messageKey) => {
if (shouldSave) {
const message = attachmentsToDownload[messageKey];
messagesToSave.push(message.attributes);
}
});
await window.Signal.Data.saveMessages(messagesToSave, { await window.Signal.Data.saveMessages(messagesToSave, {
ourAci: window.storage.user.getCheckedAci(), ourAci: window.storage.user.getCheckedAci(),
}); });
attachmentDownloadQueue = undefined; attachmentDownloadQueue = undefined;
queueEmptyCallbacks.forEach(callback => callback()); onQueueEmpty();
queueEmptyCallbacks.clear();
} }