@nivinjoseph/n-eda
Version:
Event Driven Architecture framework
135 lines (108 loc) • 3.83 kB
text/typescript
import { given } from "@nivinjoseph/n-defensive";
import { Deferred } from "@nivinjoseph/n-util";
import { RoutedEvent } from "./broker.js";
import { Processor } from "./processor.js";
import { Scheduler, WorkItem } from "./scheduler.js";
/**
* @deprecated Only used for baselining
*/
export class DefaultScheduler implements Scheduler
{
private readonly _queues = new Map<string, SchedulerQueue>();
private readonly _processing = new Set<string>();
private readonly _processors: ReadonlyArray<Processor>;
public constructor(processors: ReadonlyArray<Processor>)
{
given(processors, "processors").ensureHasValue().ensureIsArray().ensure(t => t.isNotEmpty);
this._processors = processors;
this._processors.forEach(t =>
{
t.availability.subscribe(this._executeAvailableWork.bind(this));
t.doneProcessing.subscribe((workItem) => this._processing.delete(workItem.partitionKey));
});
}
public scheduleWork(routedEvent: RoutedEvent): Promise<void>
{
const deferred = new Deferred<void>();
const workItem: WorkItem = {
...routedEvent,
deferred
};
if (this._queues.has(workItem.partitionKey))
this._queues.get(workItem.partitionKey)!.queue.unshift(workItem);
else
this._queues.set(workItem.partitionKey, {
partitionKey: workItem.partitionKey,
// lastAccessed: Date.now(),
queue: [workItem]
});
this._executeAvailableWork();
return workItem.deferred.promise;
}
public dispose(): Promise<void>
{
return Promise.resolve();
}
private _executeAvailableWork(processor?: Processor): void
{
const availableProcessor = processor ?? this._processors.find(t => !t.isBusy);
if (availableProcessor == null)
return;
const workItem = this._findWork();
if (workItem == null)
return;
availableProcessor.process(workItem);
this._processing.add(workItem.partitionKey);
}
// private _findWork(): WorkItem | null
// {
// // FIXME: this is a shitty priority queue
// const entries = [...this._queues.values()].orderBy(t => t.lastAccessed);
// for (const entry of entries)
// {
// if (entry.queue.isEmpty)
// {
// this._queues.delete(entry.partitionKey);
// continue;
// }
// if (this._processing.has(entry.partitionKey))
// continue;
// const workItem = entry.queue.pop()!;
// if (entry.queue.isEmpty)
// this._queues.delete(entry.partitionKey);
// else
// entry.lastAccessed = Date.now();
// return workItem;
// }
// return null;
// }
private _findWork(): WorkItem | null
{
// Because we know that Map.Values() returns entries in insertion order
for (const entry of this._queues.values())
{
if (entry.queue.isEmpty)
{
this._queues.delete(entry.partitionKey);
continue;
}
if (this._processing.has(entry.partitionKey))
continue;
const workItem = entry.queue.pop()!;
this._queues.delete(entry.partitionKey);
if (entry.queue.isNotEmpty)
{
// entry.lastAccessed = Date.now();
this._queues.set(entry.partitionKey, entry);
}
return workItem;
}
return null;
}
}
interface SchedulerQueue
{
partitionKey: string;
// lastAccessed: number;
queue: Array<WorkItem>;
}