import type { DB, DBTransaction, LevelPath, KeyPath } from '@matrixai/db'; import type { ResourceRelease } from '@matrixai/resources'; import type { TaskHandlerId, TaskHandler, TaskId, TaskIdEncoded, Task, TaskParameters, TaskPath } from './types.js'; import Logger from '@matrixai/logger'; import { createDestroyStartStop } from '@matrixai/async-init'; import { Lock } from '@matrixai/async-locks'; import { PromiseCancellable } from '@matrixai/async-cancellable'; import { Timer } from '@matrixai/timer'; interface TaskManager extends createDestroyStartStop.CreateDestroyStartStop { } declare class TaskManager { static createTaskManager({ db, handlers, lazy, activeLimit, stopWarningTimeout, logger, fresh, }: { db: DB; handlers?: Record; lazy?: boolean; activeLimit?: number; stopWarningTimeout?: number; logger?: Logger; fresh?: boolean; }): Promise; protected logger: Logger; protected schedulerLogger: Logger; protected queueLogger: Logger; protected db: DB; protected handlers: Map; protected activeLimit: number; protected stopWarningTimeout: number; protected generateTaskId: () => TaskId; protected taskPromises: Map>; protected activePromises: Map>; protected taskEvents: EventTarget; protected tasksDbPath: LevelPath; /** * Tasks collection * `Tasks/tasks/{TaskId} -> {json(TaskData)}` */ protected tasksTaskDbPath: LevelPath; /** * Scheduled Tasks * This is indexed by `TaskId` at the end to avoid conflicts * `Tasks/scheduled/{lexi(TaskTimestamp + TaskDelay)}/{TaskId} -> null` */ protected tasksScheduledDbPath: LevelPath; /** * Queued Tasks * This is indexed by `TaskId` at the end to avoid conflicts * `Tasks/queued/{lexi(TaskPriority)}/{lexi(TaskTimestamp + TaskDelay)}/{TaskId} -> null` */ protected tasksQueuedDbPath: LevelPath; /** * Tracks actively running tasks * `Tasks/active/{TaskId} -> null` */ protected tasksActiveDbPath: LevelPath; /** * Tasks indexed path * `Tasks/path/{...TaskPath}/{TaskId} -> null` */ protected tasksPathDbPath: LevelPath; /** * Maintain last Task ID to preserve monotonicity across process restarts * `Tasks/lastTaskId -> {raw(TaskId)}` */ protected tasksLastTaskIdPath: KeyPath; /** * Asynchronous scheduling loop * This is blocked by the `schedulingLock` * The `null` indicates that the scheduling loop isn't running */ protected schedulingLoop: PromiseCancellable | null; /** * Timer used to unblock the scheduling loop * This releases the `schedulingLock` if it is locked * The `null` indicates there is no timer running */ protected schedulingTimer: Timer | null; /** * Lock controls whether to run an iteration of the scheduling loop */ protected schedulingLock: Lock; /** * Releases the scheduling lock * On the first iteration of the scheduling loop * the lock may not be acquired yet, and therefore releaser is not set */ protected schedulingLockReleaser?: ResourceRelease; /** * Asynchronous queuing loop * This is blocked by the `queuingLock` * The `null` indicates that the queuing loop isn't running */ protected queuingLoop: PromiseCancellable | null; /** * Lock controls whether to run an iteration of the queuing loop */ protected queuingLock: Lock; /** * Releases the queuing lock * On the first iteration of the queuing loop * the lock may not be acquired yet, and therefore releaser is not set */ protected queuingLockReleaser?: ResourceRelease; get activeCount(): number; constructor({ db, activeLimit, stopWarningTimeout, logger, }: { db: DB; activeLimit: number; stopWarningTimeout: number; logger: Logger; }); start({ handlers, lazy, fresh, }?: { handlers?: Record; lazy?: boolean; fresh?: boolean; }): Promise; stop(): Promise; destroy(): Promise; /** * Start scheduling and queuing loop * This call is idempotent * Use this when `Tasks` is started in lazy mode */ startProcessing(): Promise; /** * Stop the scheduling and queuing loop * This call is idempotent */ stopProcessing(): Promise; isProcessing(): boolean; /** * Stop the active tasks * This call is idempotent */ stopTasks(): Promise; getHandler(handlerId: TaskHandlerId): TaskHandler | undefined; getHandlers(): Record; registerHandler(handlerId: TaskHandlerId, handler: TaskHandler): void; deregisterHandler(handlerId: TaskHandlerId): void; getLastTaskId(tran?: DBTransaction): Promise; getTask(taskId: TaskId, lazy?: boolean, tran?: DBTransaction): Promise; getTasks(order?: 'asc' | 'desc', lazy?: boolean, path?: TaskPath, tran?: DBTransaction): AsyncGenerator; getTaskPromise(taskId: TaskId, tran?: DBTransaction): PromiseCancellable; /** * Schedules a task * If `this.schedulingLoop` isn't running, then this will not * attempt to reset the `this.schedulingTimer` */ scheduleTask({ handlerId, parameters, delay, deadline, priority, path, lazy, }: { handlerId: TaskHandlerId; parameters?: TaskParameters; delay?: number; deadline?: number; priority?: number; path?: TaskPath; lazy?: boolean; }, tran?: DBTransaction): Promise; updateTask(taskId: TaskId, taskPatch: Partial<{ handlerId: TaskHandlerId; parameters: TaskParameters; delay: number; deadline: number; priority: number; path: TaskPath; }>, tran?: DBTransaction): Promise; /** * Transition tasks from `scheduled` to `queued` */ protected startScheduling(): Promise; protected stopScheduling(): Promise; protected startQueueing(): Promise; protected stopQueueing(): Promise; /** * Triggers the scheduler on a delayed basis * If the delay is 0, the scheduler is triggered immediately * The scheduling timer is a singleton that can be set by both * `this.schedulingLoop` and `this.scheduleTask` * This ensures that the timer is set to the earliest scheduled task */ protected triggerScheduling(scheduleTime: number): void; /** * Same idea as triggerScheduling * But this time unlocking the queue to proceed * If already unlocked, subsequent unlocking is idempotent * The unlocking of the scheduling is delayed * Whereas this unlocking is not * Remember the queuing just keeps running until finished */ protected triggerQueuing(): void; /** * Transition from scheduled to queued * If the task is cancelled, then this does nothing */ protected queueTask(taskId: TaskId): Promise; /** * Transition from queued to active * If the task is cancelled, then this does nothing */ protected startTask(taskId: TaskId): Promise; /** * This is used to garbage collect tasks that have settled * Explicit removal of tasks can only be done through task cancellation */ protected gcTask(taskId: TaskId, tran?: DBTransaction): Promise; protected requeueTask(taskId: TaskId, tran?: DBTransaction): Promise; protected cancelTask(taskId: TaskId, cancelReason: any): Promise; /** * Mutually exclude last task ID mutation * Prevents "counter racing" for the last task ID */ protected lockLastTaskId(tran: DBTransaction): Promise; /** * Mutual exclusion for task mutation * Used to lock: * - `this.updateTask` * - `this.queueTask` * - `this.startTask` * - `this.gcTask` * - `this.requeueTask` */ protected lockTask(tran: DBTransaction, taskId: TaskId): Promise; /** * If the process was killed ungracefully then we may need to * repair active dangling tasks by moving them back to the queued index */ protected repairDanglingTasks(): Promise; } export default TaskManager;