try a convenience thing for scheduling providers

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-03-05 16:58:28 +01:00
parent c8f5f5c1d2
commit 9461f73643
13 changed files with 301 additions and 123 deletions
+23 -11
View File
@@ -11,17 +11,10 @@ import { Logger } from 'winston';
// @public
export interface PluginTaskScheduler {
scheduleTask(task: TaskDefinition): Promise<void>;
}
// @public
export interface TaskDefinition {
fn: TaskFunction;
frequency: Duration;
id: string;
initialDelay?: Duration;
signal?: AbortSignal_2;
timeout: Duration;
createTaskSchedule(schedule: TaskScheduleDefinition): TaskSchedule;
scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void>;
}
// @public
@@ -29,6 +22,25 @@ export type TaskFunction =
| ((abortSignal: AbortSignal_2) => void | Promise<void>)
| (() => void | Promise<void>);
// @public
export interface TaskInvocationDefinition {
fn: TaskFunction;
id: string;
signal?: AbortSignal_2;
}
// @public
export interface TaskSchedule {
run(task: TaskInvocationDefinition): Promise<void>;
}
// @public
export interface TaskScheduleDefinition {
frequency: Duration;
initialDelay?: Duration;
timeout: Duration;
}
// @public
export class TaskScheduler {
constructor(databaseManager: DatabaseManager, logger: Logger);
@@ -59,4 +59,31 @@ describe('PluginTaskManagerImpl', () => {
60_000,
);
});
// This is just to test the wrapper code; most of the actual tests are in
// TaskWorker.test.ts
describe('createTaskSchedule', () => {
it.each(databases.eachSupportedId())(
'can run the happy path, %p',
async databaseId => {
const { manager } = await init(databaseId);
const fn = jest.fn();
await manager
.createTaskSchedule({
timeout: Duration.fromMillis(5000),
frequency: Duration.fromMillis(5000),
})
.run({
id: 'task1',
fn,
});
await waitForExpect(() => {
expect(fn).toBeCalled();
});
},
60_000,
);
});
});
@@ -17,7 +17,12 @@
import { Knex } from 'knex';
import { Logger } from 'winston';
import { TaskWorker } from './TaskWorker';
import { PluginTaskScheduler, TaskDefinition } from './types';
import {
PluginTaskScheduler,
TaskInvocationDefinition,
TaskSchedule,
TaskScheduleDefinition,
} from './types';
import { validateId } from './util';
/**
@@ -29,7 +34,9 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
private readonly logger: Logger,
) {}
async scheduleTask(task: TaskDefinition): Promise<void> {
async scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void> {
validateId(task.id);
const knex = await this.databaseFactory();
@@ -47,4 +54,12 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
},
);
}
createTaskSchedule(schedule: TaskScheduleDefinition): TaskSchedule {
return {
run: async task => {
await this.scheduleTask({ ...task, ...schedule });
},
};
}
}
+3 -1
View File
@@ -17,6 +17,8 @@
export { TaskScheduler } from './TaskScheduler';
export type {
PluginTaskScheduler,
TaskDefinition,
TaskFunction,
TaskInvocationDefinition,
TaskSchedule,
TaskScheduleDefinition,
} from './types';
+60 -21
View File
@@ -31,27 +31,11 @@ export type TaskFunction =
| (() => void | Promise<void>);
/**
* Options that apply to the invocation of a given task.
* Options that control the scheduling of a task.
*
* @public
*/
export interface TaskDefinition {
/**
* A unique ID (within the scope of the plugin) for the task.
*/
id: string;
/**
* The actual task function to be invoked regularly.
*/
fn: TaskFunction;
/**
* An abort signal that, when triggered, will stop the recurring execution of
* the task.
*/
signal?: AbortSignal;
export interface TaskScheduleDefinition {
/**
* The maximum amount of time that a single task invocation can take, before
* it's considered timed out and gets "released" such that a new invocation
@@ -91,6 +75,43 @@ export interface TaskDefinition {
initialDelay?: Duration;
}
/**
* Options that apply to the invocation of a given task.
*
* @public
*/
export interface TaskInvocationDefinition {
/**
* A unique ID (within the scope of the plugin) for the task.
*/
id: string;
/**
* The actual task function to be invoked regularly.
*/
fn: TaskFunction;
/**
* An abort signal that, when triggered, will stop the recurring execution of
* the task.
*/
signal?: AbortSignal;
}
/**
* A previously prepared task schedule, ready to be invoked.
*
* @public
*/
export interface TaskSchedule {
/**
* Takes the schedule and executes an actual task using it.
*
* @param task - The actual runtime properties of the task
*/
run(task: TaskInvocationDefinition): Promise<void>;
}
/**
* Deals with the scheduling of distributed tasks, for a given plugin.
*
@@ -99,15 +120,33 @@ export interface TaskDefinition {
export interface PluginTaskScheduler {
/**
* Schedules a task function for coordinated exclusive invocation across
* workers.
* workers. This convenience method performs both the scheduling and
* invocation in one go.
*
* @remarks
*
* If the task was already scheduled since before by us or by another party,
* its options are just overwritten with the given options, and things
* continue from there.
*
* @param definition - The task definition
* @param task - The task definition
*/
scheduleTask(task: TaskDefinition): Promise<void>;
scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void>;
/**
* Creates a task schedule, ready to be invoked at a later time.
*
* @remarks
*
* This method is useful for pre-creating a schedule in outer code to be
* passed into an inner implementation, such that the outer code controls
* scheduling while inner code controls implementation.
*
* @param schedule - The task schedule
*/
createTaskSchedule(schedule: TaskScheduleDefinition): TaskSchedule;
}
function isValidOptionalDurationString(d: string | undefined): boolean {