signal-desktop/ts/test-node/jobs/TestJobQueueStore.ts

133 lines
3 KiB
TypeScript
Raw Normal View History

2021-04-29 18:02:27 -05:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable max-classes-per-file */
/* eslint-disable no-await-in-loop */
import EventEmitter, { once } from 'events';
import type { JobQueueStore, StoredJob } from '../../jobs/types';
2021-04-29 18:02:27 -05:00
import { sleep } from '../../util/sleep';
import { drop } from '../../util/drop';
2021-04-29 18:02:27 -05:00
export class TestJobQueueStore implements JobQueueStore {
events = new EventEmitter();
#openStreams = new Set<string>();
#pipes = new Map<string, Pipe>();
2021-04-29 18:02:27 -05:00
storedJobs: Array<StoredJob> = [];
constructor(jobs: ReadonlyArray<StoredJob> = []) {
jobs.forEach(job => {
drop(this.insert(job));
2021-04-29 18:02:27 -05:00
});
}
async insert(
job: Readonly<StoredJob>,
{ shouldPersist = true }: Readonly<{ shouldPersist?: boolean }> = {}
): Promise<void> {
2021-04-29 18:02:27 -05:00
await fakeDelay();
this.storedJobs.forEach(storedJob => {
if (job.id === storedJob.id) {
throw new Error('Cannot store two jobs with the same ID');
}
});
if (shouldPersist) {
this.storedJobs.push(job);
}
2021-04-29 18:02:27 -05:00
this.#getPipe(job.queueType).add(job);
2021-04-29 18:02:27 -05:00
this.events.emit('insert');
}
async delete(id: string): Promise<void> {
await fakeDelay();
this.storedJobs = this.storedJobs.filter(job => job.id !== id);
this.events.emit('delete');
}
stream(queueType: string): Pipe {
if (this.#openStreams.has(queueType)) {
2021-04-29 18:02:27 -05:00
throw new Error('Cannot stream the same queueType more than once');
}
this.#openStreams.add(queueType);
2021-04-29 18:02:27 -05:00
return this.#getPipe(queueType);
2021-04-29 18:02:27 -05:00
}
pauseStream(queueType: string): void {
return this.#getPipe(queueType).pause();
2021-04-29 18:02:27 -05:00
}
resumeStream(queueType: string): void {
return this.#getPipe(queueType).resume();
2021-04-29 18:02:27 -05:00
}
#getPipe(queueType: string): Pipe {
const existingPipe = this.#pipes.get(queueType);
2021-04-29 18:02:27 -05:00
if (existingPipe) {
return existingPipe;
}
const result = new Pipe();
this.#pipes.set(queueType, result);
2021-04-29 18:02:27 -05:00
return result;
}
}
class Pipe implements AsyncIterable<StoredJob> {
#queue: Array<StoredJob> = [];
#eventEmitter = new EventEmitter();
#isLocked = false;
#isPaused = false;
2021-04-29 18:02:27 -05:00
add(value: Readonly<StoredJob>) {
this.#queue.push(value);
this.#eventEmitter.emit('add');
2021-04-29 18:02:27 -05:00
}
async *[Symbol.asyncIterator]() {
if (this.#isLocked) {
2021-04-29 18:02:27 -05:00
throw new Error('Cannot iterate over a pipe more than once');
}
this.#isLocked = true;
2021-04-29 18:02:27 -05:00
while (true) {
for (const value of this.#queue) {
await this.#waitForUnpaused();
2021-04-29 18:02:27 -05:00
yield value;
}
this.#queue = [];
2021-04-29 18:02:27 -05:00
// We do this because we want to yield values in series.
await once(this.#eventEmitter, 'add');
2021-04-29 18:02:27 -05:00
}
}
pause(): void {
this.#isPaused = true;
2021-04-29 18:02:27 -05:00
}
resume(): void {
this.#isPaused = false;
this.#eventEmitter.emit('resume');
2021-04-29 18:02:27 -05:00
}
async #waitForUnpaused() {
if (this.#isPaused) {
await once(this.#eventEmitter, 'resume');
2021-04-29 18:02:27 -05:00
}
}
}
function fakeDelay(): Promise<void> {
return sleep(0);
}