feat: add Python and Java regex engines
This commit is contained in:
@@ -4,7 +4,7 @@ import type {
|
||||
RegexReplacementRequest,
|
||||
RegexReplacementResult,
|
||||
} from "../model/match";
|
||||
import { WorkerSupervisor } from "./WorkerSupervisor";
|
||||
import { WorkerRequestError, WorkerSupervisor } from "./WorkerSupervisor";
|
||||
import type {
|
||||
EngineWorkerOperation,
|
||||
EngineWorkerResult,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ENGINE_WORKER_REGISTRY,
|
||||
type EngineWorkerRegistry,
|
||||
} from "./engine-registry";
|
||||
import type { RegexFlavourId } from "../model/flavour";
|
||||
import type { RegexEngineInfo, RegexFlavourId } from "../model/flavour";
|
||||
|
||||
export type EngineRuntimeState =
|
||||
| { readonly status: "unloaded" }
|
||||
@@ -28,6 +28,18 @@ export class EngineSupervisor {
|
||||
RegexFlavourId,
|
||||
WorkerSupervisor<EngineWorkerOperation, EngineWorkerResult>
|
||||
>();
|
||||
private readonly loaded = new Map<
|
||||
RegexFlavourId,
|
||||
{
|
||||
readonly generation: number;
|
||||
readonly info: RegexEngineInfo;
|
||||
}
|
||||
>();
|
||||
private readonly loading = new Map<
|
||||
RegexFlavourId,
|
||||
Promise<RegexEngineInfo>
|
||||
>();
|
||||
private readonly operationRevisions = new Map<RegexFlavourId, number>();
|
||||
private disposed = false;
|
||||
|
||||
constructor(registry: EngineWorkerRegistry = ENGINE_WORKER_REGISTRY) {
|
||||
@@ -51,14 +63,76 @@ export class EngineSupervisor {
|
||||
return supervisor;
|
||||
}
|
||||
|
||||
async load(flavour: RegexFlavourId): Promise<RegexEngineInfo> {
|
||||
const registration = this.registry.require(flavour);
|
||||
const supervisor = this.supervisorFor(flavour);
|
||||
const loaded = this.loaded.get(flavour);
|
||||
if (
|
||||
loaded &&
|
||||
supervisor.hasWorker &&
|
||||
loaded.generation === supervisor.currentGeneration
|
||||
) {
|
||||
return loaded.info;
|
||||
}
|
||||
const current = this.loading.get(flavour);
|
||||
if (current && supervisor.hasWorker) return current;
|
||||
const loading: Promise<RegexEngineInfo> = supervisor
|
||||
.run({ kind: "load" }, registration.startupTimeoutMs)
|
||||
.then((response) => {
|
||||
if (response.kind !== "load") {
|
||||
throw new Error(
|
||||
"Engine worker returned the wrong load response kind",
|
||||
);
|
||||
}
|
||||
if (response.info.flavour !== flavour) {
|
||||
throw new Error(
|
||||
`Engine worker for ${flavour} loaded a ${response.info.flavour} runtime.`,
|
||||
);
|
||||
}
|
||||
this.loaded.set(flavour, {
|
||||
generation: supervisor.currentGeneration,
|
||||
info: response.info,
|
||||
});
|
||||
return response.info;
|
||||
})
|
||||
.finally(() => {
|
||||
if (this.loading.get(flavour) === loading) {
|
||||
this.loading.delete(flavour);
|
||||
}
|
||||
});
|
||||
this.loading.set(flavour, loading);
|
||||
return loading;
|
||||
}
|
||||
|
||||
private async prepareOperation(flavour: RegexFlavourId): Promise<{
|
||||
readonly revision: number;
|
||||
readonly supervisor: WorkerSupervisor<
|
||||
EngineWorkerOperation,
|
||||
EngineWorkerResult
|
||||
>;
|
||||
}> {
|
||||
const revision = (this.operationRevisions.get(flavour) ?? 0) + 1;
|
||||
this.operationRevisions.set(flavour, revision);
|
||||
const supervisor = this.supervisorFor(flavour);
|
||||
if (supervisor.isRunning) supervisor.cancel();
|
||||
await this.load(flavour);
|
||||
if (this.operationRevisions.get(flavour) !== revision) {
|
||||
throw new WorkerRequestError(
|
||||
"cancelled",
|
||||
`${this.registry.require(flavour).label} request was superseded`,
|
||||
);
|
||||
}
|
||||
return { revision, supervisor };
|
||||
}
|
||||
|
||||
async execute(
|
||||
request: RegexExecutionRequest,
|
||||
timeoutMs: number,
|
||||
): Promise<RegexExecutionResult> {
|
||||
const response = await this.supervisorFor(request.flavour).run(
|
||||
const { supervisor } = await this.prepareOperation(request.flavour);
|
||||
const response = await supervisor.run(
|
||||
{ kind: "execute", request },
|
||||
timeoutMs,
|
||||
{ supersede: true },
|
||||
);
|
||||
if (response.kind !== "execute") {
|
||||
throw new Error("Engine worker returned the wrong response kind");
|
||||
@@ -75,10 +149,10 @@ export class EngineSupervisor {
|
||||
request: RegexReplacementRequest,
|
||||
timeoutMs: number,
|
||||
): Promise<RegexReplacementResult> {
|
||||
const response = await this.supervisorFor(request.flavour).run(
|
||||
const { supervisor } = await this.prepareOperation(request.flavour);
|
||||
const response = await supervisor.run(
|
||||
{ kind: "replace", request },
|
||||
timeoutMs,
|
||||
{ supersede: true },
|
||||
);
|
||||
if (response.kind !== "replace") {
|
||||
throw new Error("Engine worker returned the wrong response kind");
|
||||
@@ -92,7 +166,13 @@ export class EngineSupervisor {
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
for (const supervisor of this.supervisors.values()) supervisor.cancel();
|
||||
for (const [flavour, supervisor] of this.supervisors) {
|
||||
this.operationRevisions.set(
|
||||
flavour,
|
||||
(this.operationRevisions.get(flavour) ?? 0) + 1,
|
||||
);
|
||||
supervisor.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -100,5 +180,8 @@ export class EngineSupervisor {
|
||||
this.disposed = true;
|
||||
for (const supervisor of this.supervisors.values()) supervisor.dispose();
|
||||
this.supervisors.clear();
|
||||
this.loaded.clear();
|
||||
this.loading.clear();
|
||||
this.operationRevisions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user