////
// A dedicated coordinator Worker creates subworkers because callers may be
// blocked in Wasm and unable to use postMessage directly.
import { AllocatorUseArrayBuffer } from "../allocator.js";
import * as Serializer from "../serialize_error.js";
import {
beginDestroy,
DESTROY_REQUESTER_STATUS,
DESTROY_WAKE_EPOCH,
markDestroyed,
markDestroyFailed,
markRequesterClosing,
readLifecycle,
readRequesterAnimalId,
RequesterStatus,
wakeDestroyWaiters,
WorkerDestroyFailureCode,
WorkerLifecycle,
} from "../worker_lifecycle.js";
import {
claimRuntimeCompletion,
completeWorkerRequest,
createWorkerRequestView,
finishRuntimeCompletion,
RuntimeCompletion,
WORKER_REQUEST_STATE,
WorkerRequestFailure,
WorkerRequestState,
} from "../worker_request.js";
import {
SIGNATURE_REQUEST_STATUS_LEN,
SIGNATURE_REQUEST_STATUS_PTR,
wakeWorkerCommandActivity,
WORKER_BACKGROUND_LOCK_BYTES,
WORKER_COMMAND_ACK,
WORKER_COMMAND_ACTIVITY_EPOCH,
WORKER_COMMAND_MUTEX,
WORKER_COMMAND_PHASE,
WORKER_RUNTIME_COMPLETION_OFFSET_BYTES,
WORKER_RUNTIME_COMPLETION_WORDS,
WorkerCommandState,
WorkerTerminationGate,
reserveWorkerTermination,
releaseWorkerTermination,
} from "./worker_export.js";
import type { WorkerBackgroundRefObject } from "./worker_export.js";
type WorkerRecord = {
worker?: Worker;
request_view?: Int32Array;
worker_id: number;
animal_id: number;
retired: boolean;
};
type WorkerCommand = {
obj: Record;
requestView: Int32Array;
url: string;
};
function asError(value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
export class WorkerBackground {
private override_object: T;
private allocator: AllocatorUseArrayBuffer;
private lock: SharedArrayBuffer;
private signature_input: SharedArrayBuffer;
private destroy_status: SharedArrayBuffer;
private workers: Array = [undefined];
// @ts-expect-error retained so the coordinator loop remains strongly held.
private listen_holder: Promise;
private start_worker?: WorkerRecord;
private start_worker_requested = false;
private animal_workers = new Map();
private pending_animal_workers = new Map();
private worker_animals = new Map();
private next_animal_id = 0;
private free_animal_ids: number[] = [];
private terminated_records = new WeakSet();
private pending_retirements = new Set();
private stopping_workers = false;
private teardown_promise?: Promise;
private teardown_failure?: WorkerDestroyFailureCode;
private teardown_requester?: WorkerRecord;
private schedule_timeout: typeof setTimeout = setTimeout;
private cancel_timeout: typeof clearTimeout = clearTimeout;
constructor(
override_object: T,
destroy_status: SharedArrayBuffer,
lock?: SharedArrayBuffer,
allocator?: AllocatorUseArrayBuffer,
signature_input?: SharedArrayBuffer,
) {
this.override_object = override_object;
this.destroy_status = destroy_status;
this.lock = lock ?? new SharedArrayBuffer(WORKER_BACKGROUND_LOCK_BYTES);
const initialLockView = new Int32Array(this.lock);
if (
Atomics.load(initialLockView, WORKER_COMMAND_PHASE) === 0 &&
Atomics.load(initialLockView, WORKER_COMMAND_ACK) === 0
) {
Atomics.store(
initialLockView,
WORKER_COMMAND_PHASE,
WorkerCommandState.Idle,
);
wakeWorkerCommandActivity(initialLockView);
}
this.allocator =
allocator ??
new AllocatorUseArrayBuffer(new SharedArrayBuffer(10 * 1024));
this.signature_input = signature_input ?? new SharedArrayBuffer(32);
this.listen_holder = this.listen();
}
static init_self(
override_object: T,
worker_background_ref_object: WorkerBackgroundRefObject,
): WorkerBackground {
return new WorkerBackground(
override_object,
worker_background_ref_object.destroy_status,
worker_background_ref_object.lock,
AllocatorUseArrayBuffer.init_self(worker_background_ref_object.allocator),
worker_background_ref_object.signature_input,
);
}
private assign_worker_id(): number {
for (let i = 1; i < this.workers.length; i++) {
if (this.workers[i] === undefined) return i;
}
this.workers.push(undefined);
return this.workers.length - 1;
}
private allocate_animal_id(): number {
const reused = this.free_animal_ids.pop();
if (reused !== undefined) return reused;
if (this.next_animal_id > 2 ** 30 - 2) {
throw new RangeError("no Animal IDs are available");
}
return this.next_animal_id++;
}
private release_animal_id(animalId: number): void {
this.free_animal_ids.push(animalId);
}
private ref(): WorkerBackgroundRefObject {
return {
allocator: this.allocator.get_object(),
lock: this.lock,
signature_input: this.signature_input,
destroy_status: this.destroy_status,
};
}
private terminate_worker(record: WorkerRecord): void {
if (this.terminated_records.has(record)) return;
const worker = record.worker;
if (worker === undefined) return;
this.terminated_records.add(record);
this.detach_handlers(record);
worker.terminate();
}
private retire_record(record: WorkerRecord): void {
if (this.terminated_records.has(record)) return;
this.pending_retirements.add(record);
reserveWorkerTermination(new Int32Array(this.lock));
}
private drain_retirements(): void {
for (const record of this.pending_retirements) {
this.retire_record_inner(record);
}
this.pending_retirements.clear();
this.stopping_workers = false;
}
private retire_record_inner(record: WorkerRecord, terminate = true): boolean {
if (record.retired) return false;
record.retired = true;
let ownsIdentity = false;
if (record.worker_id > 0 && this.workers[record.worker_id] === record) {
this.workers[record.worker_id] = undefined;
ownsIdentity = true;
}
if (record.worker_id === 0 && this.start_worker === record) {
this.start_worker = undefined;
ownsIdentity = true;
}
if (this.pending_animal_workers.get(record.animal_id) === record) {
this.pending_animal_workers.delete(record.animal_id);
ownsIdentity = true;
}
if (this.animal_workers.get(record.animal_id) === record) {
this.animal_workers.delete(record.animal_id);
ownsIdentity = true;
}
if (
ownsIdentity &&
this.worker_animals.get(record.worker_id) === record.animal_id
) {
this.worker_animals.delete(record.worker_id);
}
if (
ownsIdentity &&
!this.pending_animal_workers.has(record.animal_id) &&
!this.animal_workers.has(record.animal_id)
) {
this.release_animal_id(record.animal_id);
}
if (terminate) this.terminate_worker(record);
return ownsIdentity;
}
private is_active_record(record: WorkerRecord): boolean {
return (
!record.retired &&
!this.pending_retirements.has(record) &&
(record.worker_id === 0
? this.start_worker === record
: this.workers[record.worker_id] === record)
);
}
private mark_inactive_requester_closing(record: WorkerRecord): boolean {
if (this.teardown_requester !== record) return false;
markRequesterClosing(new Int32Array(this.destroy_status));
return true;
}
private detach_handlers(record: WorkerRecord): void {
const worker = record.worker;
if (worker === undefined) return;
worker.onmessage = null;
worker.onerror = null;
worker.onmessageerror = null;
}
private terminate_registered_workers(): void {
this.stopping_workers = true;
const records = new Set();
for (const record of this.workers) {
if (record !== undefined) records.add(record);
}
if (this.start_worker !== undefined) records.add(this.start_worker);
for (const record of this.pending_animal_workers.values()) {
records.add(record);
}
for (const record of this.animal_workers.values()) records.add(record);
const destroyView = new Int32Array(this.destroy_status);
for (const record of records) {
const requestView = record.request_view;
record.request_view = undefined;
if (requestView !== undefined) {
completeWorkerRequest(
requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
record.worker_id,
destroyView,
);
}
this.retire_record(record);
}
reserveWorkerTermination(new Int32Array(this.lock));
}
private cancel_runtime(): void {
const completionView = new Int32Array(
this.lock,
WORKER_RUNTIME_COMPLETION_OFFSET_BYTES,
WORKER_RUNTIME_COMPLETION_WORDS,
);
if (claimRuntimeCompletion(completionView)) {
finishRuntimeCompletion(
completionView,
RuntimeCompletion.Cancelled,
new Int32Array(this.destroy_status),
);
}
}
private teardown(): Promise {
this.teardown_promise ??= Promise.resolve().then(() =>
this.teardown_inner(),
);
return this.teardown_promise;
}
private fail_runtime(
_error: unknown,
code = WorkerDestroyFailureCode.CoordinatorRuntime,
): void {
this.teardown_failure ??= code;
beginDestroy(new Int32Array(this.destroy_status));
wakeWorkerCommandActivity(new Int32Array(this.lock));
}
private record_teardown_error(error: unknown): void {
this.teardown_failure ??= WorkerDestroyFailureCode.CoordinatorRuntime;
console.error(error);
}
private async wait_for_requester_closing(view: Int32Array): Promise {
let timeout: ReturnType | undefined;
const closing = (async () => {
for (;;) {
const statusBefore = Atomics.load(view, DESTROY_REQUESTER_STATUS);
if (statusBefore === RequesterStatus.Closing) return;
const epoch = Atomics.load(view, DESTROY_WAKE_EPOCH);
const statusAfter = Atomics.load(view, DESTROY_REQUESTER_STATUS);
if (statusAfter === RequesterStatus.Closing) return;
if (statusAfter !== statusBefore) continue;
const { value } = Atomics.waitAsync(view, DESTROY_WAKE_EPOCH, epoch);
const result = value instanceof Promise ? await value : value;
if (result === "timed-out") throw new Error("atomic wait timed out");
}
})();
const deadline = new Promise((resolve) => {
timeout = this.schedule_timeout(resolve, 1_000);
});
await Promise.race([closing, deadline]);
if (timeout !== undefined) this.cancel_timeout(timeout);
}
private async teardown_inner(): Promise {
const lockView = new Int32Array(this.lock);
const destroyView = new Int32Array(this.destroy_status);
const requesterId = readRequesterAnimalId(destroyView);
const requester =
requesterId === undefined
? undefined
: (this.animal_workers.get(requesterId) ??
this.pending_animal_workers.get(requesterId));
const records = new Set();
for (const record of this.pending_retirements) records.add(record);
for (const record of this.workers) {
if (record !== undefined) records.add(record);
}
if (this.start_worker !== undefined) records.add(this.start_worker);
for (const record of this.pending_animal_workers.values()) {
records.add(record);
}
for (const record of this.animal_workers.values()) records.add(record);
this.teardown_requester = requester;
for (const record of records) {
const requestView = record.request_view;
record.request_view = undefined;
if (requestView !== undefined) {
try {
completeWorkerRequest(
requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
record.worker_id,
destroyView,
);
} catch (error) {
this.record_teardown_error(error);
}
}
try {
this.retire_record_inner(record, record !== requester);
} catch (error) {
this.record_teardown_error(error);
}
}
this.workers = [undefined];
this.start_worker = undefined;
this.animal_workers.clear();
this.pending_animal_workers.clear();
this.worker_animals.clear();
this.pending_retirements.clear();
try {
this.cancel_runtime();
} catch (error) {
this.record_teardown_error(error);
}
// The requester must be able to finish short updates before Closing.
releaseWorkerTermination(lockView);
if (requester !== undefined) {
Atomics.store(
destroyView,
DESTROY_REQUESTER_STATUS,
RequesterStatus.RequesterDrained,
);
wakeDestroyWaiters(destroyView);
try {
await this.wait_for_requester_closing(destroyView);
} catch (error) {
this.record_teardown_error(error);
}
while (await this.claim_command(lockView, true)) {
try {
this.cancel_claimed_command(new Int32Array(this.signature_input));
} finally {
this.acknowledge_command(lockView);
}
}
try {
this.terminate_worker(requester);
} catch (error) {
this.record_teardown_error(error);
} finally {
releaseWorkerTermination(lockView);
}
}
this.teardown_requester = undefined;
try {
if (this.teardown_failure === undefined) {
markDestroyed(destroyView);
} else {
markDestroyFailed(destroyView, this.teardown_failure);
}
} catch (error) {
this.teardown_failure ??= WorkerDestroyFailureCode.CoordinatorRuntime;
try {
if (readLifecycle(destroyView) === WorkerLifecycle.Running) {
beginDestroy(destroyView);
}
markDestroyFailed(destroyView, this.teardown_failure);
} catch (terminalError) {
console.error(error, terminalError);
}
}
globalThis.close();
}
private complete_request_ready(
record: WorkerRecord,
animalId: unknown,
): boolean {
if (!this.is_active_record(record)) {
this.retire_record(record);
return false;
}
const requestView = record.request_view;
const destroyView = new Int32Array(this.destroy_status);
const running =
readLifecycle(destroyView) === WorkerLifecycle.Running &&
!this.stopping_workers;
const validId = Number.isInteger(animalId) && animalId === record.animal_id;
const pending =
requestView !== undefined &&
Atomics.load(requestView, WORKER_REQUEST_STATE) ===
WorkerRequestState.Pending &&
this.pending_animal_workers.get(record.animal_id) === record;
if (!validId || !pending) {
if (requestView !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
} else {
this.retire_record(record);
}
return false;
}
if (!running) {
record.request_view = undefined;
completeWorkerRequest(
requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
record.worker_id,
destroyView,
);
this.retire_record(record);
return false;
}
this.pending_animal_workers.delete(record.animal_id);
this.animal_workers.set(record.animal_id, record);
this.worker_animals.set(record.worker_id, record.animal_id);
record.request_view = undefined;
const accepted = completeWorkerRequest(
requestView,
WorkerRequestState.Ready,
WorkerRequestFailure.None,
record.worker_id,
destroyView,
);
if (!accepted) {
this.retire_record(record);
return false;
}
return true;
}
private complete_request_failure(
record: WorkerRecord,
failure: WorkerRequestFailure,
): void {
if (!this.is_active_record(record)) {
this.retire_record(record);
return;
}
const requestView = record.request_view;
record.request_view = undefined;
try {
if (requestView !== undefined) {
completeWorkerRequest(
requestView,
WorkerRequestState.Failed,
failure,
record.worker_id,
new Int32Array(this.destroy_status),
);
}
} finally {
this.retire_record(record);
}
}
private publish_runtime_exit(code: number): void {
const completionView = new Int32Array(
this.lock,
WORKER_RUNTIME_COMPLETION_OFFSET_BYTES,
WORKER_RUNTIME_COMPLETION_WORDS,
);
if (!claimRuntimeCompletion(completionView)) return;
Atomics.store(completionView, 1, code);
finishRuntimeCompletion(
completionView,
RuntimeCompletion.Exit,
new Int32Array(this.destroy_status),
);
}
private publish_runtime_done(): void {
const completionView = new Int32Array(
this.lock,
WORKER_RUNTIME_COMPLETION_OFFSET_BYTES,
WORKER_RUNTIME_COMPLETION_WORDS,
);
if (!claimRuntimeCompletion(completionView)) return;
finishRuntimeCompletion(
completionView,
RuntimeCompletion.Done,
new Int32Array(this.destroy_status),
);
}
private publish_runtime_error(value: unknown): void {
const completionView = new Int32Array(
this.lock,
WORKER_RUNTIME_COMPLETION_OFFSET_BYTES,
WORKER_RUNTIME_COMPLETION_WORDS,
);
let allocation: [number, number] | undefined;
try {
const serialized = Serializer.serialize(asError(value));
const data = new TextEncoder().encode(JSON.stringify(serialized));
const allocatorView = new Int32Array(this.allocator.share_arrays_memory);
// A terminated worker may own this mutex. Never wait for its release.
if (Atomics.compareExchange(allocatorView, 0, 0, 1) !== 0) {
throw new Error("runtime error allocator is busy");
}
try {
allocation = this.allocator.write_inner(
data,
new SharedArrayBuffer(8),
0,
);
} finally {
Atomics.store(allocatorView, 0, 0);
Atomics.notify(allocatorView, 0, 1);
}
if (!claimRuntimeCompletion(completionView)) {
this.allocator.free(allocation[0], allocation[1]);
return;
}
Atomics.store(completionView, 1, allocation[0]);
Atomics.store(completionView, 2, allocation[1]);
finishRuntimeCompletion(
completionView,
RuntimeCompletion.Error,
new Int32Array(this.destroy_status),
);
} catch (error) {
if (allocation !== undefined) {
this.allocator.free(allocation[0], allocation[1]);
}
this.fail_runtime(error);
}
}
private take_allocation(pointer: number, length: number): ArrayBuffer {
return this.allocator.take_memory(pointer, length);
}
private read_command(signatureView: Int32Array): WorkerCommand {
const requestPointer = Atomics.load(
signatureView,
SIGNATURE_REQUEST_STATUS_PTR,
);
const requestLength = Atomics.load(
signatureView,
SIGNATURE_REQUEST_STATUS_LEN,
);
let requestView: Int32Array | undefined;
let requestError: unknown;
try {
requestView = createWorkerRequestView(
this.allocator.share_arrays_memory,
requestPointer,
requestLength,
);
} catch (error) {
requestError = error;
}
let urlBuffer: ArrayBuffer | undefined;
let objectBuffer: ArrayBuffer | undefined;
let payloadError: unknown;
try {
urlBuffer = this.take_allocation(
Atomics.load(signatureView, 1),
Atomics.load(signatureView, 2),
);
} catch (error) {
payloadError = error;
}
try {
objectBuffer = this.take_allocation(
Atomics.load(signatureView, 4),
Atomics.load(signatureView, 5),
);
} catch (error) {
payloadError ??= error;
}
if (requestError !== undefined) throw requestError;
if (payloadError !== undefined) {
completeWorkerRequest(
requestView!,
WorkerRequestState.Failed,
WorkerRequestFailure.Protocol,
0,
new Int32Array(this.destroy_status),
);
throw payloadError;
}
const url = new TextDecoder().decode(urlBuffer!);
try {
return {
obj: JSON.parse(new TextDecoder().decode(objectBuffer!)) as Record<
string,
unknown
>,
requestView: requestView!,
url,
};
} catch (error) {
completeWorkerRequest(
requestView!,
WorkerRequestState.Failed,
WorkerRequestFailure.Protocol,
0,
new Int32Array(this.destroy_status),
);
throw error;
}
}
private cancel_claimed_command(signatureView: Int32Array): void {
const opcode = Atomics.load(signatureView, 0);
if (opcode !== 1 && opcode !== 2) return;
let requestView: Int32Array | undefined;
try {
requestView = createWorkerRequestView(
this.allocator.share_arrays_memory,
Atomics.load(signatureView, SIGNATURE_REQUEST_STATUS_PTR),
Atomics.load(signatureView, SIGNATURE_REQUEST_STATUS_LEN),
);
} catch (error) {
this.teardown_failure ??= WorkerDestroyFailureCode.Protocol;
console.error(error);
}
for (const [pointerIndex, lengthIndex] of [
[1, 2],
[4, 5],
] as const) {
try {
this.take_allocation(
Atomics.load(signatureView, pointerIndex),
Atomics.load(signatureView, lengthIndex),
);
} catch (error) {
this.teardown_failure ??= WorkerDestroyFailureCode.Protocol;
console.error(error);
}
}
if (requestView !== undefined) {
try {
completeWorkerRequest(
requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
0,
new Int32Array(this.destroy_status),
);
} catch (error) {
this.record_teardown_error(error);
}
}
}
private acknowledge_command(lockView: Int32Array): void {
Atomics.store(lockView, WORKER_COMMAND_PHASE, WorkerCommandState.Idle);
wakeWorkerCommandActivity(lockView);
Atomics.store(lockView, WORKER_COMMAND_ACK, 0);
Atomics.notify(lockView, WORKER_COMMAND_ACK, 1);
}
private async claim_command(
lockView: Int32Array,
terminating = false,
): Promise {
for (;;) {
if (
terminating ||
this.pending_retirements.size > 0 ||
this.stopping_workers
)
reserveWorkerTermination(lockView);
const epoch = Atomics.load(lockView, WORKER_COMMAND_ACTIVITY_EPOCH);
const state = Atomics.load(lockView, WORKER_COMMAND_PHASE);
const mutex = Atomics.load(lockView, WORKER_COMMAND_MUTEX);
if (state === WorkerCommandState.Idle) {
if (mutex === WorkerTerminationGate.Coordinator) return false;
if (
mutex === WorkerTerminationGate.Reserved &&
Atomics.compareExchange(
lockView,
WORKER_COMMAND_MUTEX,
WorkerTerminationGate.Reserved,
WorkerTerminationGate.Coordinator,
) === WorkerTerminationGate.Reserved
) {
wakeWorkerCommandActivity(lockView);
return false;
}
if (mutex === WorkerTerminationGate.Free && !terminating) return false;
}
if (state === WorkerCommandState.Published) {
if (
Atomics.compareExchange(
lockView,
WORKER_COMMAND_PHASE,
WorkerCommandState.Published,
WorkerCommandState.Claimed,
) === WorkerCommandState.Published
) {
wakeWorkerCommandActivity(lockView);
return true;
}
continue;
}
if (
state !== WorkerCommandState.Idle &&
state !== WorkerCommandState.Preparing &&
state !== WorkerCommandState.Claimed
) {
throw new Error(`unknown worker command state: ${state}`);
}
if (
Atomics.load(lockView, WORKER_COMMAND_PHASE) !== state ||
Atomics.load(lockView, WORKER_COMMAND_MUTEX) !== mutex ||
Atomics.load(lockView, WORKER_COMMAND_ACTIVITY_EPOCH) !== epoch
) {
continue;
}
const { value } = Atomics.waitAsync(
lockView,
WORKER_COMMAND_ACTIVITY_EPOCH,
epoch,
);
const result = value instanceof Promise ? await value : value;
if (result === "timed-out") throw new Error("timed-out");
}
}
private async finish_listen_teardown(
lockView: Int32Array,
signatureView: Int32Array,
): Promise {
while (await this.claim_command(lockView, true)) {
try {
this.cancel_claimed_command(signatureView);
} finally {
this.acknowledge_command(lockView);
}
}
await this.teardown();
}
private register_handlers(record: WorkerRecord, isStart: boolean): void {
const worker = record.worker;
if (worker === undefined) return;
worker.onmessage = (event: MessageEvent) => {
const data = event.data;
if (!this.is_active_record(record)) {
if (this.teardown_requester === record) {
const message =
data !== null && typeof data === "object"
? (data as { animal_id?: number; msg?: string })
: undefined;
if (
message?.msg === "ready" &&
Number.isInteger(message.animal_id) &&
message.animal_id === record.animal_id &&
Atomics.load(
new Int32Array(this.destroy_status),
DESTROY_REQUESTER_STATUS,
) === RequesterStatus.RequesterDrained
) {
return;
}
if (message?.msg === "ready") {
this.mark_inactive_requester_closing(record);
return;
}
if (
(message?.msg === "error" || message?.msg === "exit") &&
Number.isInteger(message.animal_id) &&
message.animal_id === record.animal_id
) {
this.mark_inactive_requester_closing(record);
}
return;
}
this.retire_record(record);
return;
}
if (data === null || typeof data !== "object") {
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
}
this.fail_runtime(new Error("invalid worker message"));
return;
}
const message = data as {
animal_id?: number;
code?: number;
error?: unknown;
msg?: string;
stage?: unknown;
};
const isStandardMessage =
message.msg === "ready" ||
message.msg === "done" ||
message.msg === "error" ||
message.msg === "exit";
if (
isStandardMessage &&
(!Number.isInteger(message.animal_id) ||
message.animal_id !== record.animal_id)
) {
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
} else {
this.retire_record(record);
}
return;
}
if (message.msg === "ready") {
this.complete_request_ready(record, message.animal_id);
return;
}
if (message.msg === "done") {
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
} else {
this.retire_record(record);
}
if (isStart) {
this.terminate_registered_workers();
this.publish_runtime_done();
}
return;
}
if (message.msg === "error" || message.msg === "exit") {
if (record.request_view !== undefined) {
const failure =
message.msg === "error" && message.stage === "animal-construction"
? WorkerRequestFailure.AnimalConstruction
: message.msg === "error" &&
message.stage === "wasm-instantiation"
? WorkerRequestFailure.WasmInstantiation
: WorkerRequestFailure.Protocol;
this.complete_request_failure(record, failure);
return;
}
this.terminate_registered_workers();
if (message.msg === "error") {
this.publish_runtime_error(
message.error ?? new Error("worker error"),
);
} else {
this.publish_runtime_exit(message.code ?? 1);
}
return;
}
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
}
this.fail_runtime(new Error("unknown worker message"));
};
worker.onerror = (event) => {
// Retiring workers remain alive until the termination gate is acquired.
event.preventDefault();
if (!this.is_active_record(record)) {
if (this.mark_inactive_requester_closing(record)) return;
this.retire_record(record);
return;
}
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.ScriptLoad);
}
this.fail_runtime(new Error(event.message));
};
worker.onmessageerror = () => {
if (!this.is_active_record(record)) {
if (this.mark_inactive_requester_closing(record)) return;
this.retire_record(record);
return;
}
if (record.request_view !== undefined) {
this.complete_request_failure(record, WorkerRequestFailure.Protocol);
}
this.fail_runtime(new Error("worker message could not be decoded"));
};
}
private create_worker(signatureView: Int32Array): void {
let command: WorkerCommand;
try {
command = this.read_command(signatureView);
} catch (error) {
this.fail_runtime(error);
return;
}
if (
this.stopping_workers ||
readLifecycle(new Int32Array(this.destroy_status)) !==
WorkerLifecycle.Running
) {
completeWorkerRequest(
command.requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
0,
new Int32Array(this.destroy_status),
);
return;
}
let animalId: number;
try {
animalId = this.allocate_animal_id();
} catch (error) {
completeWorkerRequest(
command.requestView,
WorkerRequestState.Failed,
WorkerRequestFailure.AnimalConstruction,
0,
new Int32Array(this.destroy_status),
);
this.fail_runtime(error);
return;
}
const workerId = this.assign_worker_id();
const record: WorkerRecord = {
animal_id: animalId,
request_view: command.requestView,
retired: false,
worker_id: workerId,
};
this.workers[workerId] = record;
this.pending_animal_workers.set(animalId, record);
this.worker_animals.set(workerId, animalId);
let worker: Worker;
try {
worker = new Worker(command.url, {
type: Atomics.load(signatureView, 3) === 1 ? "module" : "classic",
});
} catch (error) {
this.complete_request_failure(record, WorkerRequestFailure.ScriptLoad);
this.fail_runtime(error);
return;
}
record.worker = worker;
this.register_handlers(record, false);
try {
worker.postMessage({
...this.override_object,
...command.obj,
animal_id: animalId,
worker_id: workerId,
worker_background_ref: this.ref(),
});
} catch (error) {
this.complete_request_failure(
record,
WorkerRequestFailure.AnimalConstruction,
);
this.fail_runtime(error);
}
}
private create_start_worker(signatureView: Int32Array): void {
let command: WorkerCommand;
try {
command = this.read_command(signatureView);
} catch (error) {
this.fail_runtime(error);
return;
}
if (
this.stopping_workers ||
readLifecycle(new Int32Array(this.destroy_status)) !==
WorkerLifecycle.Running
) {
completeWorkerRequest(
command.requestView,
WorkerRequestState.Cancelled,
WorkerRequestFailure.Destroyed,
0,
new Int32Array(this.destroy_status),
);
return;
}
if (this.start_worker_requested) {
completeWorkerRequest(
command.requestView,
WorkerRequestState.Failed,
WorkerRequestFailure.Protocol,
0,
new Int32Array(this.destroy_status),
);
return;
}
this.start_worker_requested = true;
let animalId: number;
try {
animalId = this.allocate_animal_id();
} catch (error) {
completeWorkerRequest(
command.requestView,
WorkerRequestState.Failed,
WorkerRequestFailure.AnimalConstruction,
0,
new Int32Array(this.destroy_status),
);
this.fail_runtime(error);
return;
}
const record: WorkerRecord = {
animal_id: animalId,
request_view: command.requestView,
retired: false,
worker_id: 0,
};
this.start_worker = record;
this.pending_animal_workers.set(animalId, record);
this.worker_animals.set(0, animalId);
let worker: Worker;
try {
worker = new Worker(command.url, {
type: Atomics.load(signatureView, 3) === 1 ? "module" : "classic",
});
} catch (error) {
this.complete_request_failure(record, WorkerRequestFailure.ScriptLoad);
this.fail_runtime(error);
return;
}
record.worker = worker;
this.register_handlers(record, true);
try {
worker.postMessage({
...this.override_object,
...command.obj,
animal_id: animalId,
worker_background_ref: this.ref(),
});
} catch (error) {
this.complete_request_failure(
record,
WorkerRequestFailure.WasmInstantiation,
);
this.fail_runtime(error);
return;
}
}
private kill_animal(animalId: number): void {
if (
readLifecycle(new Int32Array(this.destroy_status)) !==
WorkerLifecycle.Running
) {
return;
}
const record = this.animal_workers.get(animalId);
if (record === undefined) return;
if (record === this.start_worker) this.cancel_runtime();
this.retire_record(record);
}
private process_claimed_command(signatureView: Int32Array): void {
switch (Atomics.load(signatureView, 0)) {
case 1:
this.create_worker(signatureView);
break;
case 2:
this.create_start_worker(signatureView);
break;
case 3:
this.terminate_registered_workers();
break;
case 4: {
const animalId = Atomics.load(signatureView, 1);
this.kill_animal(animalId);
break;
}
default:
this.fail_runtime(
new Error("unknown worker background command"),
WorkerDestroyFailureCode.Protocol,
);
}
}
private async listen(): Promise {
const lockView = new Int32Array(this.lock);
const signatureView = new Int32Array(this.signature_input);
const destroyView = new Int32Array(this.destroy_status);
for (;;) {
if (readLifecycle(destroyView) !== WorkerLifecycle.Running) {
await this.finish_listen_teardown(lockView, signatureView);
return;
}
if (
Atomics.load(lockView, WORKER_COMMAND_MUTEX) ===
WorkerTerminationGate.Coordinator
) {
try {
this.drain_retirements();
} catch (error) {
this.fail_runtime(error);
} finally {
releaseWorkerTermination(lockView);
}
continue;
}
const phase = Atomics.load(lockView, WORKER_COMMAND_PHASE);
const mutex = Atomics.load(lockView, WORKER_COMMAND_MUTEX);
if (phase === WorkerCommandState.Idle && mutex === 0) {
const epoch = Atomics.load(lockView, WORKER_COMMAND_ACTIVITY_EPOCH);
if (
Atomics.load(lockView, WORKER_COMMAND_PHASE) !== phase ||
Atomics.load(lockView, WORKER_COMMAND_MUTEX) !== mutex ||
readLifecycle(destroyView) !== WorkerLifecycle.Running ||
Atomics.load(lockView, WORKER_COMMAND_ACTIVITY_EPOCH) !== epoch
) {
continue;
}
const { value } = Atomics.waitAsync(
lockView,
WORKER_COMMAND_ACTIVITY_EPOCH,
epoch,
);
const result = value instanceof Promise ? await value : value;
if (result === "timed-out") throw new Error("timed-out");
continue;
}
if (!(await this.claim_command(lockView))) continue;
if (readLifecycle(destroyView) !== WorkerLifecycle.Running) {
try {
this.cancel_claimed_command(signatureView);
} finally {
this.acknowledge_command(lockView);
}
continue;
}
try {
this.process_claimed_command(signatureView);
} catch (error) {
this.fail_runtime(error);
} finally {
this.acknowledge_command(lockView);
}
}
}
}
// biome-ignore lint/complexity/noUnusedVariables: keeps the coordinator alive.
let worker_background: WorkerBackground;
globalThis.onmessage = (event: MessageEvent) => {
globalThis.onmessage = () => undefined;
const { override_object, worker_background_ref_object } = event.data;
worker_background = WorkerBackground.init_self(
override_object,
worker_background_ref_object,
);
void worker_background;
postMessage("ready");
};