UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

118 lines 5.64 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StaleTaskService = void 0; const common_1 = require("@nestjs/common"); const cron_time_generator_1 = __importDefault(require("cron-time-generator")); const croner_1 = require("croner"); const instrument_decorator_1 = require("../../common/instrument-decorator"); const config_1 = require("../../config"); const transactional_connection_1 = require("../../connection/transactional-connection"); const constants_1 = require("./constants"); const scheduled_task_record_entity_1 = require("./scheduled-task-record.entity"); /** * @description * This service finds and cleans stale locks, which can occur if a worker instance is * non-gracefully shut down while a task is ongoing, and the lock is never released. * * Failure to clean these stale locks will prevent the task from ever running again, since * workers will assume it is locked by another processes and will ignore it. */ let StaleTaskService = class StaleTaskService { constructor(connection) { this.connection = connection; // Cache the interval for each taskId this.taskIntervalMap = new Map(); } /** * @description * Cleans stale task locks from the database. */ async cleanStaleLocksForTask(task) { const now = new Date(); try { const lockedTask = await this.fetchLockedTask(task.id); if (!lockedTask) { return; } const intervalMs = this.getScheduleIntervalMs(task); if (this.isStale(lockedTask, now, intervalMs)) { await this.clearStaleLock(lockedTask); } } catch (error) { config_1.Logger.error(`Error cleaning up stale task locks: ${error instanceof Error ? error.message : String(error)}`, constants_1.loggerCtx); throw error; } } async fetchLockedTask(taskId) { return this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .select(['task.taskId', 'task.lockedAt']) .where('task.lockedAt IS NOT NULL') .andWhere('task.taskId = :taskId', { taskId }) .getOne(); } /** * Returns the interval in ms between one run of the task, and the next. */ getScheduleIntervalMs(task) { const cachedInterval = this.taskIntervalMap.get(task.id); if (cachedInterval) { return cachedInterval; } const schedule = task.options.schedule; const scheduleString = typeof schedule === 'function' ? schedule(cron_time_generator_1.default) : schedule; const cron = new croner_1.Cron(scheduleString); const nextFn = typeof cron.nextRun === 'function' ? cron.nextRun.bind(cron) : cron.next.bind(cron); const next1 = nextFn(); if (!next1) { throw new Error('Could not compute next run times'); } const next2 = nextFn(next1); if (!next2) { throw new Error('Could not compute next run times'); } const interval = next2.getTime() - next1.getTime(); this.taskIntervalMap.set(task.id, interval); return interval; } isStale(task, now, intervalMs) { if (!task.lockedAt) { return false; } return now.getTime() - task.lockedAt.getTime() > intervalMs; } async clearStaleLock(staleTask) { var _a, _b, _c; const repo = this.connection.rawConnection.getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord); const result = await repo.update({ taskId: staleTask.taskId, lockedAt: (_a = staleTask.lockedAt) !== null && _a !== void 0 ? _a : undefined }, { lockedAt: null }); if (result.affected && result.affected > 0) { config_1.Logger.verbose(`Successfully cleaned stale task locks for task "${staleTask.taskId}", which was locked at ${(_c = (_b = staleTask.lockedAt) === null || _b === void 0 ? void 0 : _b.toISOString()) !== null && _c !== void 0 ? _c : 'unknown'}`, constants_1.loggerCtx); } else { config_1.Logger.debug(`Skipped clearing lock for task "${staleTask.taskId}" because the lock has changed since observation`, constants_1.loggerCtx); } } }; exports.StaleTaskService = StaleTaskService; exports.StaleTaskService = StaleTaskService = __decorate([ (0, common_1.Injectable)(), (0, instrument_decorator_1.Instrument)(), __metadata("design:paramtypes", [transactional_connection_1.TransactionalConnection]) ], StaleTaskService); //# sourceMappingURL=stale-task.service.js.map