"use strict";
/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskManager = exports.DriverProcessFactory = exports.DriverProcess = exports.DriverThread = void 0;
const child_process_1 = require("child_process");
const os = __importStar(require("os"));
const pre_define_1 = require("../pre_define");
const logger_1 = require("../logger");
const worker_threads_1 = require("worker_threads");
const types_1 = require("../types");
const error_1 = require("./error");
const graph_1 = require("./graph");
class DriverThread {
    constructor(workerPath, ...args) {
        this.path = workerPath;
        this.args = args;
        this.thread = new worker_threads_1.Worker(workerPath, ...args);
    }
    on(msg, listener) {
        this.thread.on(msg, listener);
        return this;
    }
    send(msgType, data) {
        this.thread.postMessage({ type: msgType, data: data });
        return true;
    }
    stop() {
        let res = 0;
        (async () => {
            await this.thread.terminate().then((value) => {
                res = value;
            });
        })();
        return res;
    }
    getId() {
        return this.thread.threadId;
    }
    getWorkerPath() {
        return this.path;
    }
    spawnNewInstance() {
        return new DriverThread(this.path, ...this.args);
    }
}
exports.DriverThread = DriverThread;
class DriverProcess {
    constructor(workerPath, ...args) {
        this.path = workerPath;
        this.args = args;
        this.process = (0, child_process_1.fork)(workerPath, ...args);
    }
    on(msg, listener) {
        this.process.on(msg, listener);
        return this;
    }
    send(msgType, data) {
        return this.process.send({ type: msgType, data: data });
    }
    stop() {
        this.process.kill();
        return 0;
    }
    getId() {
        return this.process.pid;
    }
    getWorkerPath() {
        return this.path;
    }
    spawnNewInstance() {
        return new DriverProcess(this.path, ...this.args);
    }
}
exports.DriverProcess = DriverProcess;
class DriverProcessFactory {
    constructor(path, ...args) {
        this.path = path;
        this.args = args;
    }
    spawnWorker() {
        return new DriverProcess(this.path, ...this.args);
    }
}
exports.DriverProcessFactory = DriverProcessFactory;
class TaskManager {
    constructor(onWorkerExit, declgen = false, maxWorkers, taskTimeoutMs = 180000) {
        this.workers = [];
        this.idleWorkers = [];
        this.taskQueue = [];
        this.completedTasks = [];
        this.runningTasks = new Map();
        this.maxWorkers = pre_define_1.DEFAULT_WORKER_NUMS;
        this.buildGraph = new graph_1.Graph();
        this.logger = logger_1.Logger.getInstance();
        this.isDeclgen = declgen;
        this.onWorkerExit = onWorkerExit;
        this.taskTimeoutMs = taskTimeoutMs;
        if (maxWorkers !== undefined) {
            this.maxWorkers = Math.min(maxWorkers, Math.max(os.cpus().length - 1, 1));
        }
        this.logger.printInfo(`Available workers: ${this.maxWorkers}`);
    }
    tryDispatch() {
        while (this.taskQueue.length > 0 && this.idleWorkers.length > 0) {
            const task = this.taskQueue.shift();
            const workerInfo = this.idleWorkers.shift();
            this.assignTaskToWorker(task, workerInfo);
        }
        if (this.checkIfComplete()) {
            this.signalCompletion();
        }
    }
    checkIfComplete() {
        const noRunningTasks = this.runningTasks.size === 0;
        const noQueuedTasks = this.taskQueue.length === 0;
        const allWorkersIdle = this.idleWorkers.length === this.maxWorkers;
        return noRunningTasks && noQueuedTasks && allWorkersIdle;
    }
    signalCompletion() {
        const success = this.completedTasks.every(t => t.success === true);
        this.completionResolve?.(success);
    }
    assignTaskToWorker(task, workerInfo) {
        this.runningTasks.set(task.id, task);
        workerInfo.currentTaskId = task.id;
        task.timeoutTimer = setTimeout(() => {
            this.logger.printWarn(`Worker with id ${workerInfo.id} exceeded timeout. Stopping it...`);
            this.logger.printWarn(`Dropping task ${task.id}`);
            const logData = logger_1.LogDataFactory.newInstance(this.isDeclgen ?
                error_1.ErrorCode.BUILDSYSTEM_DECLGEN_FAILED_IN_WORKER :
                error_1.ErrorCode.BUILDSYSTEM_COMPILE_FAILED_IN_WORKER, `Task ${task.id} is not completed. Dropping it. Processed file is ${task.payload.fileInfo.input}`, `Worker ${workerInfo.id} exceeded timeout of ${this.taskTimeoutMs} ms`);
            this.logger.printError(logData);
            this.handleTaskTimeout(workerInfo);
        }, this.taskTimeoutMs);
        this.logger.printDebug(`Dispatch task with id ${task.id} to worker ${workerInfo.id}`);
        workerInfo.worker.send(types_1.WorkerMessageType.ASSIGN_TASK, {
            taskId: task.id,
            payload: task.payload
        });
    }
    handleTaskTimeout(workerInfo) {
        this.reconfigureWorker(workerInfo);
        this.tryDispatch();
    }
    handleWorkerMessage(workerInfo, message) {
        this.logger.printDebug(`WorkerMessage: ${JSON.stringify(message, null, 1)}`);
        switch (message.type) {
            case types_1.WorkerMessageType.ERROR_OCCURED:
                this.logErrorMessage(message);
                this.onTaskFailed(message.data.taskId, workerInfo);
                break;
            case types_1.WorkerMessageType.DECL_GENERATED:
                this.onDeclGenerated(message.data.taskId, workerInfo);
                break;
            case types_1.WorkerMessageType.ABC_COMPILED:
                this.onFileCompiled(message.data.taskId, workerInfo);
                break;
            default:
                break;
        }
    }
    onTaskFailed(taskId, workerInfo) {
        this.settleTask(taskId, true);
        workerInfo.currentTaskId = undefined;
        this.idleWorkers.push(workerInfo);
        this.tryDispatch();
    }
    onFileCompiled(taskId, workerInfo) {
        this.settleTask(taskId, false);
        workerInfo.currentTaskId = undefined;
        this.idleWorkers.push(workerInfo);
        this.tryDispatch();
    }
    onDeclGenerated(taskId, workerInfo) {
        // (1) Declgen-only mode: worker is now free for next task
        // (2) Compile mode: we can only release the worker until ABC compilation is done
        // in this case, declgen is only a signal to queue the next compilation task
        this.settleTask(taskId, false);
        if (this.isDeclgen) {
            // Declgen-only mode here
            workerInfo.currentTaskId = undefined;
            this.idleWorkers.push(workerInfo);
        }
        this.tryDispatch();
    }
    handleWorkerError(error) {
        this.logger.printDebug('handleWorkerError');
        const logData = logger_1.LogDataFactory.newInstance(error_1.ErrorCode.BUILDSYSTEM_COMPILE_FAILED_IN_WORKER, error.message);
        this.logger.printError(logData);
    }
    handleWorkerExit(workerInfo, code, signal) {
        this.logger.printDebug(`handleWorkerExit: code=${code}, signal=${signal}`);
        const taskId = workerInfo.currentTaskId;
        if (taskId) {
            const task = this.runningTasks.get(taskId);
            if (task) {
                this.logger.printError(this.onWorkerExit(workerInfo, task, code, signal));
                this.reconfigureWorker(workerInfo);
                this.tryDispatch();
            }
        }
    }
    startWorkers(workerFactory) {
        for (let i = 0; i < this.maxWorkers; i++) {
            const worker = workerFactory.spawnWorker();
            this.logger.printDebug(`Spawned worker with id ${worker.getId()}`);
            const workerInfo = { worker, id: worker.getId(), currentTaskId: undefined };
            worker.on('message', (message) => {
                this.logger.printDebug(`Got ${message.type} message from worker ${workerInfo.id}`);
                this.handleWorkerMessage(workerInfo, message);
            });
            worker.on('exit', (code, signal) => {
                this.handleWorkerExit(workerInfo, code, signal);
            });
            worker.on('error', (error) => {
                this.handleWorkerError(error);
                this.reconfigureWorker(workerInfo);
            });
            this.workers.push(workerInfo);
            this.idleWorkers.push(workerInfo);
        }
    }
    initTaskQueue() {
        this.buildGraph.nodes.forEach((node) => {
            if (node.predecessors.size === 0) {
                this.taskQueue.push({
                    id: node.id,
                    payload: node.data
                });
            }
        });
    }
    async finish() {
        const completionPromise = new Promise((resolve) => {
            this.completionResolve = resolve;
        });
        this.tryDispatch();
        const success = await completionPromise;
        this.logger.printInfo('All tasks were completed');
        this.shutdownWorkers();
        this.logger.printInfo('All workers were shutdown');
        this.logger.printDebug('TaskManager.compile exit');
        return success;
    }
    queueDependentTasks(taskId) {
        const graphNode = this.buildGraph.getNodeById(taskId);
        graphNode.descendants.forEach((descendant) => {
            const descendantNode = this.buildGraph.getNodeById(descendant);
            descendantNode.predecessors.delete(taskId);
            if (descendantNode.predecessors.size === 0) {
                this.taskQueue.push({
                    id: descendantNode.id,
                    payload: descendantNode.data
                });
                this.logger.printDebug(`[Declgen milestone] Added job ${descendant} to the queue`);
            }
            else {
                this.logger.printDebug(`[Declgen milestone] Job ${descendant} still has dependencies ${descendantNode.predecessors}`);
            }
        });
        this.logger.printDebug(`[Declgen milestone] Task [${taskId}] declgen completed, unlocked dependents`);
    }
    settleTask(completedTaskId, failed = false) {
        const task = this.runningTasks.get(completedTaskId);
        if (!task) {
            this.logger.printDebug(`Task [${completedTaskId}] has already been removed`);
            return;
        }
        if (task.timeoutTimer) {
            clearTimeout(task.timeoutTimer);
            task.timeoutTimer = undefined;
        }
        this.runningTasks.delete(completedTaskId);
        this.logger.printDebug(`Removed task [${completedTaskId}] from running tasks`);
        this.queueDependentTasks(completedTaskId);
        this.logger.printDebug(`Task [${completedTaskId}] is completed with status: ${!failed ? 'success' : 'failed'}`);
        task.success = !failed;
        this.completedTasks.push(task);
    }
    reconfigureWorker(workerInfo) {
        this.settleTask(workerInfo.currentTaskId, true);
        workerInfo.currentTaskId = undefined;
        const worker = workerInfo.worker;
        worker.stop();
        const newWorker = worker.spawnNewInstance();
        workerInfo.worker = newWorker;
        workerInfo.id = newWorker.getId();
        this.logger.printDebug(`Spawned new worker with id ${newWorker.getId()}`);
        newWorker.on('message', (message) => {
            this.logger.printDebug(`Got ${message.type} message from worker ${workerInfo.id}`);
            this.handleWorkerMessage(workerInfo, message);
        });
        newWorker.on('exit', (code, signal) => {
            this.handleWorkerExit(workerInfo, code, signal);
        });
        newWorker.on('error', (error) => {
            this.handleWorkerError(error);
            this.reconfigureWorker(workerInfo);
        });
        this.logger.printDebug(`Worker with id ${newWorker.getId()} is now idle`);
        this.idleWorkers.push(workerInfo);
    }
    logErrorMessage(message) {
        const err = message.data.error;
        if (!err) {
            return;
        }
        // Just to make LogData methods available
        const logData = new logger_1.LogData(err.code, err.description, err.cause, err.position, err.solutions, err.moreInfo);
        this.logger.printError(logData);
    }
    shutdownWorkers() {
        this.logger.printDebug('Shutdown workers...');
        this.workers.forEach((workerInfo) => {
            workerInfo.worker.stop();
        });
        this.workers = [];
        this.idleWorkers = [];
        this.runningTasks.clear();
        this.taskQueue = [];
    }
}
exports.TaskManager = TaskManager;
