Reuse global locks, handle empty envelopes

This commit is contained in:
Fedor Indutny 2021-05-19 11:33:14 -07:00 committed by Scott Nonnenberg
parent 25f271e61c
commit 1f0119a7ac
5 changed files with 29 additions and 11 deletions

View File

@ -44,20 +44,22 @@ export type SessionsOptions = {
};
export class Sessions extends SessionStore {
private readonly lock: Lock;
private readonly lock: Lock | undefined;
private inTransaction = false;
constructor(private readonly options: SessionsOptions = {}) {
super();
this.lock = options.lock || new Lock();
this.lock = options.lock;
}
public async transaction<T>(fn: () => Promise<T>): Promise<T> {
assert(!this.inTransaction, 'Already in transaction');
this.inTransaction = true;
assert(this.lock, "Can't start transaction without lock");
try {
return await window.textsecure.storage.protocol.sessionTransaction(
'Sessions.transaction',
@ -117,9 +119,9 @@ export type IdentityKeysOptions = {
};
export class IdentityKeys extends IdentityKeyStore {
private readonly lock: Lock;
private readonly lock: Lock | undefined;
constructor({ lock = new Lock() }: IdentityKeysOptions = {}) {
constructor({ lock }: IdentityKeysOptions = {}) {
super();
this.lock = lock;
}

View File

@ -118,7 +118,7 @@ export type SessionTransactionOptions = {
readonly lock?: Lock;
};
const GLOBAL_LOCK = new Lock();
const GLOBAL_LOCK = new Lock('GLOBAL_LOCK');
async function _fillCaches<ID, T extends HasIdType<ID>, HydratedType>(
object: SignalProtocolStore,
@ -608,16 +608,25 @@ export class SignalProtocolStore extends EventsMixin {
body: () => Promise<T>,
lock: Lock = GLOBAL_LOCK
): Promise<T> {
const debugName = `sessionTransaction(${lock.name}:${name})`;
// Allow re-entering from LibSignalStores
const isNested = this.sessionLock === lock;
if (this.sessionLock && !isNested) {
window.log.info(`sessionTransaction(${name}): sessions locked, waiting`);
const start = Date.now();
window.log.info(
`${debugName}: locked by ${this.sessionLock.name}, waiting`
);
await new Promise<void>(resolve => this.sessionLockQueue.push(resolve));
const duration = Date.now() - start;
window.log.info(`${debugName}: unlocked after ${duration}ms`);
}
if (!isNested) {
if (lock !== GLOBAL_LOCK) {
window.log.info(`sessionTransaction(${name}): enter`);
window.log.info(`${debugName}: enter`);
}
this.sessionLock = lock;
}

View File

@ -1346,7 +1346,7 @@ describe('SignalProtocolStore', () => {
const id = `${number}.1`;
const testRecord = getSessionRecord();
const lock = new Lock();
const lock = new Lock('lock');
await store.sessionTransaction(
'test',

View File

@ -49,6 +49,7 @@ import WebSocketResource, {
import Crypto from './Crypto';
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
import { ContactBuffer, GroupBuffer } from './ContactsParser';
import { assert } from '../util/assert';
import { isByteBufferEmpty } from '../util/isByteBufferEmpty';
import {
@ -609,7 +610,11 @@ class MessageReceiverInner extends EventTarget {
envelopePlaintext = MessageReceiverInner.stringToArrayBufferBase64(
item.envelope
);
} else if (item.envelope && typeof item.envelope === 'string') {
} else if (typeof item.envelope === 'string') {
assert(
item.envelope || item.decrypted,
'MessageReceiver.queueCached: empty envelope without decrypted data'
);
envelopePlaintext = MessageReceiverInner.stringToArrayBuffer(
item.envelope
);
@ -762,7 +767,7 @@ class MessageReceiverInner extends EventTarget {
const decrypted: Array<DecryptedEnvelope> = [];
try {
const lock = new Lock();
const lock = new Lock('cacheAndQueueBatch');
const sessionStore = new Sessions({
transactionOnly: true,
lock,

View File

@ -1,4 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export class Lock {}
export class Lock {
constructor(public readonly name: string) {}
}