"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LockManager = void 0;
const node_abort_controller_1 = require("node-abort-controller");
const enums_1 = require("../enums");
/**
 * Manages lock renewal for BullMQ workers.
 * It periodically extends locks for active jobs to prevent them from being
 * considered stalled by other workers.
 */
class LockManager {
    constructor(worker, opts) {
        this.worker = worker;
        this.opts = opts;
        // Maps job ids with their tokens, timestamps, and abort controllers
        this.trackedJobs = new Map();
        this.closed = false;
    }
    /**
     * Starts the lock manager timers for lock renewal.
     */
    start() {
        if (this.closed) {
            return;
        }
        // Start lock renewal timer if not disabled
        if (this.opts.lockRenewTime > 0) {
            this.startLockExtenderTimer();
        }
    }
    async extendLocks(jobIds) {
        await this.worker.trace(enums_1.SpanKind.INTERNAL, 'extendLocks', this.worker.name, async (span) => {
            span === null || span === void 0 ? void 0 : span.setAttributes({
                [enums_1.TelemetryAttributes.WorkerId]: this.opts.workerId,
                [enums_1.TelemetryAttributes.WorkerName]: this.opts.workerName,
                [enums_1.TelemetryAttributes.WorkerJobsToExtendLocks]: jobIds,
            });
            try {
                const jobTokens = jobIds.map(id => { var _a; return ((_a = this.trackedJobs.get(id)) === null || _a === void 0 ? void 0 : _a.token) || ''; });
                const erroredJobIds = await this.worker.extendJobLocks(jobIds, jobTokens, this.opts.lockDuration);
                if (erroredJobIds.length > 0) {
                    this.worker.emit('lockRenewalFailed', erroredJobIds);
                    for (const jobId of erroredJobIds) {
                        this.worker.emit('error', new Error(`could not renew lock for job ${jobId}`));
                    }
                }
                const succeededJobIds = jobIds.filter(id => !erroredJobIds.includes(id));
                if (succeededJobIds.length > 0) {
                    this.worker.emit('locksRenewed', {
                        count: succeededJobIds.length,
                        jobIds: succeededJobIds,
                    });
                }
            }
            catch (err) {
                this.worker.emit('error', err);
            }
        });
    }
    startLockExtenderTimer() {
        clearTimeout(this.lockRenewalTimer);
        if (!this.closed) {
            this.lockRenewalTimer = setTimeout(async () => {
                // Get all the jobs whose locks expire in less than 1/2 of the lockRenewTime
                const now = Date.now();
                const jobsToExtend = [];
                for (const jobId of this.trackedJobs.keys()) {
                    const tracked = this.trackedJobs.get(jobId);
                    const { ts, token, abortController } = tracked;
                    if (!ts) {
                        this.trackedJobs.set(jobId, { token, ts: now, abortController });
                        continue;
                    }
                    if (ts + this.opts.lockRenewTime / 2 < now) {
                        this.trackedJobs.set(jobId, { token, ts: now, abortController });
                        jobsToExtend.push(jobId);
                    }
                }
                if (jobsToExtend.length) {
                    await this.extendLocks(jobsToExtend);
                }
                this.startLockExtenderTimer();
            }, this.opts.lockRenewTime / 2);
        }
    }
    /**
     * Stops the lock manager and clears all timers.
     */
    async close() {
        if (this.closed) {
            return;
        }
        this.closed = true;
        if (this.lockRenewalTimer) {
            clearTimeout(this.lockRenewalTimer);
            this.lockRenewalTimer = undefined;
        }
        this.trackedJobs.clear();
    }
    /**
     * Adds a job to be tracked for lock renewal.
     * Returns an AbortController if shouldCreateController is true, undefined otherwise.
     */
    trackJob(jobId, token, ts, shouldCreateController = false) {
        const abortController = shouldCreateController
            ? new node_abort_controller_1.AbortController()
            : undefined;
        if (!this.closed && jobId) {
            this.trackedJobs.set(jobId, { token, ts, abortController });
        }
        return abortController;
    }
    /**
     * Removes a job from lock renewal tracking.
     */
    untrackJob(jobId) {
        this.trackedJobs.delete(jobId);
    }
    /**
     * Gets the number of jobs currently being tracked.
     */
    getActiveJobCount() {
        return this.trackedJobs.size;
    }
    /**
     * Checks if the lock manager is running.
     */
    isRunning() {
        return !this.closed && this.lockRenewalTimer !== undefined;
    }
    /**
     * Cancels a specific job by aborting its signal.
     * @param jobId - The ID of the job to cancel
     * @param reason - Optional reason for the cancellation
     * @returns true if the job was found and cancelled, false otherwise
     */
    cancelJob(jobId, reason) {
        const tracked = this.trackedJobs.get(jobId);
        if (tracked === null || tracked === void 0 ? void 0 : tracked.abortController) {
            tracked.abortController.abort(reason);
            return true;
        }
        return false;
    }
    /**
     * Cancels all tracked jobs by aborting their signals.
     * @param reason - Optional reason for the cancellation
     */
    cancelAllJobs(reason) {
        for (const tracked of this.trackedJobs.values()) {
            if (tracked.abortController) {
                tracked.abortController.abort(reason);
            }
        }
    }
    /**
     * Gets a list of all tracked job IDs.
     * @returns Array of job IDs currently being tracked
     */
    getTrackedJobIds() {
        return Array.from(this.trackedJobs.keys());
    }
}
exports.LockManager = LockManager;
//# sourceMappingURL=lock-manager.js.map