Better handle group call ring race conditions

This commit is contained in:
Evan Hahn 2022-11-16 20:52:04 -06:00 committed by GitHub
parent 629b5c3f6a
commit a88243f26d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 116 deletions

View file

@ -10,7 +10,6 @@ import type {
import type { StoredJob } from '../jobs/types';
import type { ReactionType } from '../types/Reactions';
import type { ConversationColorType, CustomColorType } from '../types/Colors';
import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
import type { StorageAccessType } from '../types/Storage.d';
import type { AttachmentType } from '../types/Attachment';
import type { BodyRangesType, BytesToStrings } from '../types/Util';
@ -689,11 +688,9 @@ export type DataInterface = {
insertJob(job: Readonly<StoredJob>): Promise<void>;
deleteJob(id: string): Promise<void>;
processGroupCallRingRequest(
ringId: bigint
): Promise<ProcessGroupCallRingRequestResult>;
processGroupCallRingCancelation(ringId: bigint): Promise<void>;
cleanExpiredGroupCallRings(): Promise<void>;
wasGroupCallRingPreviouslyCanceled(ringId: bigint): Promise<boolean>;
processGroupCallRingCancellation(ringId: bigint): Promise<void>;
cleanExpiredGroupCallRingCancellations(): Promise<void>;
getMaxMessageCounter(): Promise<number | undefined>;

View file

@ -46,7 +46,6 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
import * as durations from '../util/durations';
import { formatCountForLogging } from '../logging/formatCountForLogging';
import type { ConversationColorType, CustomColorType } from '../types/Colors';
import { ProcessGroupCallRingRequestResult } from '../types/Calling';
import { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
import type { BadgeType, BadgeImageType } from '../badges/types';
import { parseBadgeCategory } from '../badges/BadgeCategory';
@ -333,9 +332,9 @@ const dataInterface: ServerInterface = {
insertJob,
deleteJob,
processGroupCallRingRequest,
processGroupCallRingCancelation,
cleanExpiredGroupCallRings,
wasGroupCallRingPreviouslyCanceled,
processGroupCallRingCancellation,
cleanExpiredGroupCallRingCancellations,
getMaxMessageCounter,
@ -4819,7 +4818,7 @@ async function removeAll(): Promise<void> {
DELETE FROM badges;
DELETE FROM conversations;
DELETE FROM emojis;
DELETE FROM groupCallRings;
DELETE FROM groupCallRingCancellations;
DELETE FROM identityKeys;
DELETE FROM items;
DELETE FROM jobs;
@ -5425,69 +5424,36 @@ async function deleteJob(id: string): Promise<void> {
db.prepare<Query>('DELETE FROM jobs WHERE id = $id').run({ id });
}
async function processGroupCallRingRequest(
async function wasGroupCallRingPreviouslyCanceled(
ringId: bigint
): Promise<ProcessGroupCallRingRequestResult> {
): Promise<boolean> {
const db = getInstance();
return db.transaction(() => {
let result: ProcessGroupCallRingRequestResult;
const wasRingPreviouslyCanceled = Boolean(
db
.prepare<Query>(
`
SELECT 1 FROM groupCallRings
WHERE ringId = $ringId AND isActive = 0
LIMIT 1;
`
)
.pluck(true)
.get({ ringId })
);
if (wasRingPreviouslyCanceled) {
result = ProcessGroupCallRingRequestResult.RingWasPreviouslyCanceled;
} else {
const isThereAnotherActiveRing = Boolean(
db
.prepare<EmptyQuery>(
`
SELECT 1 FROM groupCallRings
WHERE isActive = 1
LIMIT 1;
`
)
.pluck(true)
.get()
return db
.prepare<Query>(
`
SELECT EXISTS (
SELECT 1 FROM groupCallRingCancellations
WHERE ringId = $ringId
AND createdAt >= $ringsOlderThanThisAreIgnored
);
if (isThereAnotherActiveRing) {
result = ProcessGroupCallRingRequestResult.ThereIsAnotherActiveRing;
} else {
result = ProcessGroupCallRingRequestResult.ShouldRing;
}
db.prepare<Query>(
`
INSERT OR IGNORE INTO groupCallRings (ringId, isActive, createdAt)
VALUES ($ringId, 1, $createdAt);
`
);
}
return result;
})();
`
)
.pluck()
.get({
ringId,
ringsOlderThanThisAreIgnored: Date.now() - MAX_GROUP_CALL_RING_AGE,
});
}
async function processGroupCallRingCancelation(ringId: bigint): Promise<void> {
async function processGroupCallRingCancellation(ringId: bigint): Promise<void> {
const db = getInstance();
db.prepare<Query>(
`
INSERT INTO groupCallRings (ringId, isActive, createdAt)
VALUES ($ringId, 0, $createdAt)
ON CONFLICT (ringId) DO
UPDATE SET isActive = 0;
INSERT INTO groupCallRingCancellations (ringId, createdAt)
VALUES ($ringId, $createdAt)
ON CONFLICT (ringId) DO NOTHING;
`
).run({ ringId, createdAt: Date.now() });
}
@ -5496,12 +5462,12 @@ async function processGroupCallRingCancelation(ringId: bigint): Promise<void> {
// that, it doesn't really matter what the value is.
const MAX_GROUP_CALL_RING_AGE = 30 * durations.MINUTE;
async function cleanExpiredGroupCallRings(): Promise<void> {
async function cleanExpiredGroupCallRingCancellations(): Promise<void> {
const db = getInstance();
db.prepare<Query>(
`
DELETE FROM groupCallRings
DELETE FROM groupCallRingCancellations
WHERE createdAt < $expiredRingTime;
`
).run({

View file

@ -0,0 +1,33 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from 'better-sqlite3';
import type { LoggerType } from '../../types/Logging';
export default function updateToSchemaVersion69(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 69) {
return;
}
db.transaction(() => {
db.exec(
`
DROP TABLE IF EXISTS groupCallRings;
CREATE TABLE groupCallRingCancellations(
ringId INTEGER PRIMARY KEY,
createdAt INTEGER NOT NULL
);
`
);
db.pragma('user_version = 69');
})();
logger.info('updateToSchemaVersion69: success!');
}

View file

@ -44,6 +44,7 @@ import updateToSchemaVersion65 from './65-add-storage-id-to-stickers';
import updateToSchemaVersion66 from './66-add-pni-signature-to-sent-protos';
import updateToSchemaVersion67 from './67-add-story-to-unprocessed';
import updateToSchemaVersion68 from './68-drop-deprecated-columns';
import updateToSchemaVersion69 from './69-group-call-ring-cancellations';
function updateToSchemaVersion1(
currentVersion: number,
@ -1950,6 +1951,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion66,
updateToSchemaVersion67,
updateToSchemaVersion68,
updateToSchemaVersion69,
];
export function updateSchema(db: Database, logger: LoggerType): void {