feat: add Python and Java regex engines
This commit is contained in:
@@ -71,26 +71,36 @@ class ResponsiveWorker implements WorkerLike {
|
||||
onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
|
||||
readonly operations: EngineWorkerOperation[] = [];
|
||||
terminateCalls = 0;
|
||||
private readonly flavour: RegexFlavourId;
|
||||
|
||||
constructor(flavour: RegexFlavourId) {
|
||||
this.flavour = flavour;
|
||||
}
|
||||
|
||||
postMessage(message: unknown): void {
|
||||
const request = message as WorkerRequest<EngineWorkerOperation>;
|
||||
this.operations.push(request.payload);
|
||||
const payload: EngineWorkerResult =
|
||||
request.payload.kind === "execute"
|
||||
request.payload.kind === "load"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: result(request.payload.request.flavour),
|
||||
kind: "load",
|
||||
info: result(this.flavour).engine,
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: {
|
||||
execution: result(request.payload.request.flavour),
|
||||
output: "",
|
||||
outputBytes: 0,
|
||||
outputTruncated: false,
|
||||
truncated: false,
|
||||
},
|
||||
};
|
||||
: request.payload.kind === "execute"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: result(request.payload.request.flavour),
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: {
|
||||
execution: result(request.payload.request.flavour),
|
||||
output: "",
|
||||
outputBytes: 0,
|
||||
outputTruncated: false,
|
||||
truncated: false,
|
||||
},
|
||||
};
|
||||
const response: WorkerResponse<EngineWorkerResult> = {
|
||||
protocolVersion: WORKER_PROTOCOL_VERSION,
|
||||
requestId: request.requestId,
|
||||
@@ -106,14 +116,34 @@ class ResponsiveWorker implements WorkerLike {
|
||||
}
|
||||
}
|
||||
|
||||
class StallingWorker implements WorkerLike {
|
||||
onmessage: ((event: MessageEvent<unknown>) => void) | null = null;
|
||||
onerror: ((event: ErrorEvent) => void) | null = null;
|
||||
onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
|
||||
readonly operations: EngineWorkerOperation[] = [];
|
||||
terminateCalls = 0;
|
||||
|
||||
postMessage(message: unknown): void {
|
||||
const request = message as WorkerRequest<EngineWorkerOperation>;
|
||||
this.operations.push(request.payload);
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
this.terminateCalls += 1;
|
||||
}
|
||||
}
|
||||
|
||||
describe("engine worker selection", () => {
|
||||
it("registers the shipped ECMAScript and PCRE2 execution workers", () => {
|
||||
it("registers every shipped execution worker with a startup budget", () => {
|
||||
expect(
|
||||
ENGINE_WORKER_REGISTRY.registrations.map(
|
||||
(registration) => registration.flavour,
|
||||
),
|
||||
).toEqual(["ecmascript", "pcre2"]);
|
||||
).toEqual(["ecmascript", "pcre2", "python", "java"]);
|
||||
expect(ENGINE_WORKER_REGISTRY.require("pcre2").label).toBe("PCRE2 engine");
|
||||
expect(ENGINE_WORKER_REGISTRY.require("python").startupTimeoutMs).toBe(
|
||||
30_000,
|
||||
);
|
||||
});
|
||||
|
||||
it("selects and reuses a dedicated supervisor per registered flavour", async () => {
|
||||
@@ -121,8 +151,9 @@ describe("engine worker selection", () => {
|
||||
const registration = (flavour: RegexFlavourId) => ({
|
||||
flavour,
|
||||
label: `${flavour} fixture`,
|
||||
startupTimeoutMs: 100,
|
||||
createWorker: () => {
|
||||
const worker = new ResponsiveWorker();
|
||||
const worker = new ResponsiveWorker(flavour);
|
||||
workers.set(flavour, [...(workers.get(flavour) ?? []), worker]);
|
||||
return worker;
|
||||
},
|
||||
@@ -150,7 +181,14 @@ describe("engine worker selection", () => {
|
||||
|
||||
expect(workers.get("ecmascript")).toHaveLength(1);
|
||||
expect(workers.get("pcre2")).toHaveLength(1);
|
||||
expect(workers.get("ecmascript")?.[0]?.operations).toHaveLength(2);
|
||||
expect(
|
||||
workers
|
||||
.get("ecmascript")?.[0]
|
||||
?.operations.map((operation) => operation.kind),
|
||||
).toEqual(["load", "execute", "execute"]);
|
||||
expect(
|
||||
workers.get("pcre2")?.[0]?.operations.map((operation) => operation.kind),
|
||||
).toEqual(["load", "execute"]);
|
||||
|
||||
supervisor.dispose();
|
||||
expect(workers.get("ecmascript")?.[0]?.terminateCalls).toBe(1);
|
||||
@@ -165,4 +203,72 @@ describe("engine worker selection", () => {
|
||||
);
|
||||
supervisor.dispose();
|
||||
});
|
||||
|
||||
it("lets the newest request supersede a shared ready-engine continuation", async () => {
|
||||
const worker = new ResponsiveWorker("python");
|
||||
const supervisor = new EngineSupervisor(
|
||||
new EngineWorkerRegistry([
|
||||
{
|
||||
flavour: "python",
|
||||
label: "Python fixture",
|
||||
startupTimeoutMs: 100,
|
||||
createWorker: () => worker,
|
||||
},
|
||||
]),
|
||||
);
|
||||
await supervisor.load("python");
|
||||
|
||||
const older = supervisor.execute(request("python"), 100);
|
||||
const newer = supervisor.execute(request("python"), 100);
|
||||
|
||||
await expect(older).rejects.toMatchObject({ kind: "cancelled" });
|
||||
await expect(newer).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
engine: expect.objectContaining({ flavour: "python" }),
|
||||
}),
|
||||
);
|
||||
expect(worker.operations.map((operation) => operation.kind)).toEqual([
|
||||
"load",
|
||||
"execute",
|
||||
]);
|
||||
supervisor.dispose();
|
||||
});
|
||||
|
||||
it("restarts a superseded cold runtime under the startup budget before execution", async () => {
|
||||
const coldWorker = new StallingWorker();
|
||||
const readyWorker = new ResponsiveWorker("python");
|
||||
let factoryCalls = 0;
|
||||
const supervisor = new EngineSupervisor(
|
||||
new EngineWorkerRegistry([
|
||||
{
|
||||
flavour: "python",
|
||||
label: "Python fixture",
|
||||
startupTimeoutMs: 100,
|
||||
createWorker: () => {
|
||||
factoryCalls += 1;
|
||||
return factoryCalls === 1 ? coldWorker : readyWorker;
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const older = supervisor.execute(request("python"), 1);
|
||||
const newer = supervisor.execute(request("python"), 1);
|
||||
|
||||
await expect(older).rejects.toMatchObject({ kind: "cancelled" });
|
||||
await expect(newer).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
engine: expect.objectContaining({ flavour: "python" }),
|
||||
}),
|
||||
);
|
||||
expect(coldWorker.operations.map((operation) => operation.kind)).toEqual([
|
||||
"load",
|
||||
]);
|
||||
expect(coldWorker.terminateCalls).toBe(1);
|
||||
expect(readyWorker.operations.map((operation) => operation.kind)).toEqual([
|
||||
"load",
|
||||
"execute",
|
||||
]);
|
||||
supervisor.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user