Make JobQueue an abstract class

This commit is contained in:
Fedor Indutny 2021-07-21 14:10:08 -07:00 committed by GitHub
parent d9e90e9ea8
commit 943bb38af1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 208 additions and 133 deletions

View file

@ -15,7 +15,7 @@ const noopOnCompleteCallbacks = {
reject: noop,
};
type JobQueueOptions<T> = {
type JobQueueOptions = {
/**
* The backing store for jobs. Typically a wrapper around the database.
*/
@ -32,38 +32,13 @@ type JobQueueOptions<T> = {
* the job to fail; a value of 2 will allow the job to fail once; etc.
*/
maxAttempts: number;
/**
* `parseData` will be called with the raw data from `store`. For example, if the job
* takes a single number, `parseData` should throw if `data` is a number and should
* return the number otherwise.
*
* If it throws, the job will be deleted from the store and the job will not be run.
*
* Will only be called once per job, even if `maxAttempts > 1`.
*/
parseData: (data: unknown) => T;
/**
* Run the job, given data.
*
* If it resolves, the job will be deleted from the store.
*
* If it rejects, the job will be retried up to `maxAttempts - 1` times, after which it
* will be deleted from the store.
*/
run: (job: Readonly<ParsedJob<T>>) => Promise<void>;
};
export class JobQueue<T> {
export abstract class JobQueue<T> {
private readonly maxAttempts: number;
private readonly parseData: (data: unknown) => T;
private readonly queueType: string;
private readonly run: (job: Readonly<ParsedJob<T>>) => Promise<unknown>;
private readonly store: JobQueueStore;
private readonly logPrefix: string;
@ -78,7 +53,7 @@ export class JobQueue<T> {
private started = false;
constructor(options: Readonly<JobQueueOptions<T>>) {
constructor(options: Readonly<JobQueueOptions>) {
assert(
Number.isInteger(options.maxAttempts) && options.maxAttempts >= 1,
'maxAttempts should be a positive integer'
@ -93,14 +68,33 @@ export class JobQueue<T> {
);
this.maxAttempts = options.maxAttempts;
this.parseData = options.parseData;
this.queueType = options.queueType;
this.run = options.run;
this.store = options.store;
this.logPrefix = `${this.queueType} job queue:`;
}
/**
* `parseData` will be called with the raw data from `store`. For example, if the job
* takes a single number, `parseData` should throw if `data` is a number and should
* return the number otherwise.
*
* If it throws, the job will be deleted from the store and the job will not be run.
*
* Will only be called once per job, even if `maxAttempts > 1`.
*/
protected abstract parseData(data: unknown): T;
/**
* Run the job, given data.
*
* If it resolves, the job will be deleted from the store.
*
* If it rejects, the job will be retried up to `maxAttempts - 1` times, after which it
* will be deleted from the store.
*/
protected abstract run(job: Readonly<ParsedJob<T>>): Promise<void>;
/**
* Start streaming jobs from the store.
*/

View file

@ -1,5 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable class-methods-use-this */
import { z } from 'zod';
@ -12,18 +13,12 @@ const removeStorageKeyJobDataSchema = z.object({
type RemoveStorageKeyJobData = z.infer<typeof removeStorageKeyJobDataSchema>;
export const removeStorageKeyJobQueue = new JobQueue<RemoveStorageKeyJobData>({
store: jobQueueDatabaseStore,
queueType: 'remove storage key',
maxAttempts: 100,
parseData(data: unknown): RemoveStorageKeyJobData {
export class RemoveStorageKeyJobQueue extends JobQueue<RemoveStorageKeyJobData> {
protected parseData(data: unknown): RemoveStorageKeyJobData {
return removeStorageKeyJobDataSchema.parse(data);
},
}
async run({
protected async run({
data,
}: Readonly<{ data: RemoveStorageKeyJobData }>): Promise<void> {
await new Promise<void>(resolve => {
@ -31,5 +26,13 @@ export const removeStorageKeyJobQueue = new JobQueue<RemoveStorageKeyJobData>({
});
await window.storage.remove(data.key);
},
}
}
export const removeStorageKeyJobQueue = new RemoveStorageKeyJobQueue({
store: jobQueueDatabaseStore,
queueType: 'remove storage key',
maxAttempts: 100,
});

View file

@ -1,5 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable class-methods-use-this */
import * as z from 'zod';
import * as moment from 'moment';
@ -45,18 +46,14 @@ const reportSpamJobDataSchema = z.object({
export type ReportSpamJobData = z.infer<typeof reportSpamJobDataSchema>;
export const reportSpamJobQueue = new JobQueue<ReportSpamJobData>({
store: jobQueueDatabaseStore,
queueType: 'report spam',
maxAttempts: 25,
parseData(data: unknown): ReportSpamJobData {
export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
protected parseData(data: unknown): ReportSpamJobData {
return reportSpamJobDataSchema.parse(data);
},
}
async run({ data }: Readonly<{ data: ReportSpamJobData }>): Promise<void> {
protected async run({
data,
}: Readonly<{ data: ReportSpamJobData }>): Promise<void> {
const { e164, serverGuids } = data;
await new Promise<void>(resolve => {
@ -115,5 +112,13 @@ export const reportSpamJobQueue = new JobQueue<ReportSpamJobData>({
throw err;
}
},
}
}
export const reportSpamJobQueue = new ReportSpamJobQueue({
store: jobQueueDatabaseStore,
queueType: 'report spam',
maxAttempts: 25,
});