import { Thread } from "@chainsafe/threads";
import { sleep } from "@lodestar/utils";
import { EventDirection, NetworkEvent } from "../network/events.js";
const NANO_TO_SECOND_CONVERSION = 1e9;
/**
 * Bridges events from worker to main thread
 * Each event can only have one direction:
 * - worker to main
 * - main to worker
 */
export function wireEventsOnWorkerThread(mainEventName, events, parentPort, metrics, isWorkerToMain) {
    // Subscribe to events from main thread
    parentPort.on("message", (data) => {
        if (typeof data === "object" &&
            data.type === mainEventName &&
            // This check is not necessary but added for safety in case of improper implemented events
            isWorkerToMain[data.event] === EventDirection.mainToWorker) {
            const [sec, nanoSec] = process.hrtime(data.posted);
            const networkWorkerLatency = sec + nanoSec / NANO_TO_SECOND_CONVERSION;
            metrics?.networkWorkerWireEventsOnWorkerThreadLatency.observe({ eventName: data.event }, networkWorkerLatency);
            events.emit(data.event, data.data);
        }
    });
    for (const eventName of Object.keys(isWorkerToMain)) {
        if (isWorkerToMain[eventName] === EventDirection.workerToMain) {
            // Pick one of the events to comply with StrictEventEmitter function signature
            events.on(eventName, (data) => {
                const workerEvent = {
                    type: mainEventName,
                    event: eventName,
                    posted: process.hrtime(),
                    data,
                };
                let transferList = undefined;
                if (eventName === NetworkEvent.pendingGossipsubMessage) {
                    const payload = data;
                    // Transfer the underlying ArrayBuffer to avoid copy for PendingGossipsubMessage
                    transferList = [payload.msg.data.buffer];
                }
                parentPort.postMessage(workerEvent, transferList);
            });
        }
    }
}
export function wireEventsOnMainThread(mainEventName, events, worker, metrics, isWorkerToMain) {
    // Subscribe to events from main thread
    worker.on("message", (data) => {
        if (typeof data === "object" &&
            data.type === mainEventName &&
            // This check is not necessary but added for safety in case of improper implemented events
            isWorkerToMain[data.event] === EventDirection.workerToMain) {
            const [sec, nanoSec] = process.hrtime(data.posted);
            const networkWorkerLatency = sec + nanoSec / NANO_TO_SECOND_CONVERSION;
            metrics?.networkWorkerWireEventsOnMainThreadLatency.observe({ eventName: data.event }, networkWorkerLatency);
            events.emit(data.event, data.data);
        }
    });
    for (const eventName of Object.keys(isWorkerToMain)) {
        if (isWorkerToMain[eventName] === EventDirection.mainToWorker) {
            // Pick one of the events to comply with StrictEventEmitter function signature
            events.on(eventName, (data) => {
                const workerEvent = {
                    type: mainEventName,
                    event: eventName,
                    posted: process.hrtime(),
                    data,
                };
                worker.postMessage(workerEvent);
            });
        }
    }
}
export async function terminateWorkerThread({ worker, retryMs, retryCount, logger, }) {
    const terminated = new Promise((resolve) => {
        Thread.events(worker).subscribe((event) => {
            if (event.type === "termination") {
                resolve(true);
            }
        });
    });
    for (let i = 0; i < retryCount; i++) {
        await Thread.terminate(worker);
        const result = await Promise.race([terminated, sleep(retryMs).then(() => false)]);
        if (result)
            return;
        logger?.warn("Worker thread failed to terminate, retrying...");
    }
    throw new Error(`Worker thread failed to terminate in ${retryCount * retryMs}ms.`);
}
//# sourceMappingURL=workerEvents.js.map