|
| 1 | +import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; |
| 2 | +import type { TaskIdString } from './types'; |
| 3 | +import type { PolykeyWorkerManagerInterface } from '../workers/types'; |
| 4 | +import type { PromiseDeconstructed } from '../types'; |
| 5 | +import Logger from '@matrixai/logger'; |
| 6 | +import { |
| 7 | + CreateDestroyStartStop, |
| 8 | + ready, |
| 9 | +} from '@matrixai/async-init/dist/CreateDestroyStartStop'; |
| 10 | +import * as tasksUtils from './utils'; |
| 11 | +import * as tasksErrors from './errors'; |
| 12 | + |
| 13 | +interface Queue extends CreateDestroyStartStop {} |
| 14 | +@CreateDestroyStartStop( |
| 15 | + new tasksErrors.ErrorQueueRunning(), |
| 16 | + new tasksErrors.ErrorQueueDestroyed(), |
| 17 | +) |
| 18 | +class Queue { |
| 19 | + |
| 20 | + public static async createQueue({ |
| 21 | + db, |
| 22 | + logger = new Logger(this.name), |
| 23 | + fresh = false, |
| 24 | + }: { |
| 25 | + db: DB; |
| 26 | + logger?: Logger; |
| 27 | + fresh?: boolean; |
| 28 | + }) { |
| 29 | + logger.info(`Creating ${this.name}`); |
| 30 | + const queue = new this({ db, logger }); |
| 31 | + await queue.start({ fresh }); |
| 32 | + logger.info(`Created ${this.name}`); |
| 33 | + return queue; |
| 34 | + } |
| 35 | + |
| 36 | + protected logger: Logger; |
| 37 | + protected db: DB; |
| 38 | + protected workerManager?: PolykeyWorkerManagerInterface; |
| 39 | + protected queueDbPath: LevelPath = [this.constructor.name]; |
| 40 | + |
| 41 | + // when the queue to execute the tasks |
| 42 | + // the task id is generated outside |
| 43 | + // you don't get a task id here |
| 44 | + // you just "push" tasks there to be executed |
| 45 | + // this is the "shared" set of promises to be maintained |
| 46 | + protected promises: Map<TaskIdString, PromiseDeconstructed<any>> = new Map(); |
| 47 | + |
| 48 | + // /** |
| 49 | + // * Listeners for task execution |
| 50 | + // * When a task is executed, these listeners are synchronously executed |
| 51 | + // * The listeners are intended for resolving or rejecting task promises |
| 52 | + // */ |
| 53 | + // protected listeners: Map<TaskIdString, Array<TaskListener>> = new Map(); |
| 54 | + |
| 55 | + public constructor({ |
| 56 | + db, |
| 57 | + logger |
| 58 | + }: { |
| 59 | + db: DB; |
| 60 | + logger: Logger |
| 61 | + }) { |
| 62 | + this.logger = logger; |
| 63 | + this.db = db; |
| 64 | + } |
| 65 | + |
| 66 | + public setWorkerManager(workerManager: PolykeyWorkerManagerInterface) { |
| 67 | + this.workerManager = workerManager; |
| 68 | + } |
| 69 | + |
| 70 | + public unsetWorkerManager() { |
| 71 | + delete this.workerManager; |
| 72 | + } |
| 73 | + |
| 74 | + public async start({ |
| 75 | + fresh = false, |
| 76 | + }: { |
| 77 | + fresh?: boolean; |
| 78 | + } = {}): Promise<void> { |
| 79 | + this.logger.info(`Starting ${this.constructor.name}`); |
| 80 | + if (fresh) { |
| 81 | + await this.db.clear(this.queueDbPath); |
| 82 | + } |
| 83 | + this.logger.info(`Started ${this.constructor.name}`); |
| 84 | + } |
| 85 | + |
| 86 | + public async stop(): Promise<void> { |
| 87 | + this.logger.info(`Stopping ${this.constructor.name}`); |
| 88 | + this.logger.info(`Stopped ${this.constructor.name}`); |
| 89 | + } |
| 90 | + |
| 91 | + public async destroy() { |
| 92 | + this.logger.info(`Destroying ${this.constructor.name}`); |
| 93 | + await this.db.clear(this.queueDbPath); |
| 94 | + this.logger.info(`Destroyed ${this.constructor.name}`); |
| 95 | + } |
| 96 | + |
| 97 | + // promises are "connected" to events |
| 98 | + |
| 99 | + /** |
| 100 | + * IF a handler does not exist |
| 101 | + * if the task is executed |
| 102 | + * then an exception is thrown |
| 103 | + * if listener exists, the exception is passed into this listener function |
| 104 | + * if it doesn't exist, then it's just a reference exception in general, this can be logged |
| 105 | + * There's nothing else to do |
| 106 | + */ |
| 107 | + @ready(new tasksErrors.ErrorSchedulerNotRunning()) |
| 108 | + protected registerListener( |
| 109 | + taskId: TaskId, |
| 110 | + taskListener: TaskListener |
| 111 | + ): void { |
| 112 | + const taskIdString = taskId.toString() as TaskIdString; |
| 113 | + const taskListeners = this.listeners.get(taskIdString); |
| 114 | + if (taskListeners != null) { |
| 115 | + taskListeners.push(taskListener); |
| 116 | + } else { |
| 117 | + this.listeners.set(taskIdString, [taskListener]); |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + @ready(new tasksErrors.ErrorSchedulerNotRunning()) |
| 122 | + protected deregisterListener( |
| 123 | + taskId: TaskId, |
| 124 | + taskListener: TaskListener |
| 125 | + ): void { |
| 126 | + const taskIdString = taskId.toString() as TaskIdString; |
| 127 | + const taskListeners = this.listeners.get(taskIdString); |
| 128 | + if (taskListeners == null || taskListeners.length < 1) return; |
| 129 | + const index = taskListeners.indexOf(taskListener); |
| 130 | + if (index !== -1) { |
| 131 | + taskListeners.splice(index, 1); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | +} |
| 136 | + |
| 137 | +export default Queue; |
| 138 | + |
| 139 | + |
| 140 | +// epic queue |
| 141 | +// need to do a couple things: |
| 142 | +// 1. integrate fast-check |
| 143 | +// 2. integrate span checks |
| 144 | +// 3. might also consider span logs? |
| 145 | +// 4. open tracing observability |
| 146 | +// 5. structured logging |
| 147 | +// 6. async hooks to get traced promises to understand the situation |
| 148 | +// 7. do we also get fantasy land promises? and async cancellable stuff? |
| 149 | +// 8. task abstractions? |
| 150 | +// need to use the db for this |
| 151 | +// 9. priority structure |
| 152 | +// 10. timers |
| 153 | +// abort controller |
| 154 | + |
| 155 | +// kinetic data structure |
| 156 | +// the priority grows as a function of time |
| 157 | +// order by priority <- this thing has a static value |
| 158 | +// in a key value DB, you can maintain sorted index of values |
| 159 | +// IDs can be lexicographically sortable |
| 160 | + |
| 161 | +// this is a persistent queue |
| 162 | +// of tasks that should be EXECUTED right now |
| 163 | +// the scheduler is a persistent scheduler of scheduled tasks |
| 164 | +// tasks get pushed from the scheduler into the queue |
| 165 | +// the queue connects to the WorkerManager |
0 commit comments