@orion-js/dogs 4.6.8 → 4.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -883,7 +883,7 @@ function scheduleJobsInternal(jobs, nPartitions) {
883
883
 
884
884
  // src/services/WorkerService.ts
885
885
  var import_helpers2 = require("@orion-js/helpers");
886
- var import_logger4 = require("@orion-js/logger");
886
+ var import_logger5 = require("@orion-js/logger");
887
887
  var import_services4 = require("@orion-js/services");
888
888
 
889
889
  // src/services/Executor.ts
@@ -1350,6 +1350,76 @@ __decorateElement(_init4, 5, "jobsHistoryRepo", _jobsHistoryRepo_dec, Executor);
1350
1350
  Executor = __decorateElement(_init4, 0, "Executor", _Executor_decorators, Executor);
1351
1351
  __runInitializers(_init4, 1, Executor);
1352
1352
 
1353
+ // src/services/jobFilters.ts
1354
+ var import_logger4 = require("@orion-js/logger");
1355
+ var DEFAULT_JOBS_FILTER_TIMEOUT_MS = 5e3;
1356
+ var warnedUnknownJobNames = /* @__PURE__ */ new Set();
1357
+ async function withTimeout(promise, timeoutMs, name) {
1358
+ let timer;
1359
+ const timeout = new Promise((_, reject) => {
1360
+ timer = setTimeout(() => reject(new Error(`${name} timed out after ${timeoutMs}ms`)), timeoutMs);
1361
+ });
1362
+ try {
1363
+ return await Promise.race([promise, timeout]);
1364
+ } finally {
1365
+ clearTimeout(timer);
1366
+ }
1367
+ }
1368
+ async function runCheck(options, name, run) {
1369
+ if (options.pendingChecks.has(name)) {
1370
+ throw new Error(`${name} is still running from a previous claim`);
1371
+ }
1372
+ const promise = Promise.resolve().then(run);
1373
+ options.pendingChecks.set(name, promise);
1374
+ void promise.then(
1375
+ () => options.pendingChecks.delete(name),
1376
+ () => options.pendingChecks.delete(name)
1377
+ );
1378
+ return await withTimeout(promise, options.timeoutMs, name);
1379
+ }
1380
+ function warnUnknownJobNames(jobs, filter) {
1381
+ for (const jobName of [...filter.allow ?? [], ...filter.deny ?? []]) {
1382
+ if (jobs[jobName] || warnedUnknownJobNames.has(jobName)) continue;
1383
+ warnedUnknownJobNames.add(jobName);
1384
+ import_logger4.logger.warn(`getJobsFilter returned unknown job name "${jobName}"; it will be ignored.`);
1385
+ }
1386
+ }
1387
+ async function filterByJobsFilter(options, getJobsFilter) {
1388
+ try {
1389
+ const filter = await runCheck(options, "getJobsFilter", getJobsFilter) ?? {};
1390
+ warnUnknownJobNames(options.jobs, filter);
1391
+ return options.jobNames.filter((jobName) => {
1392
+ var _a;
1393
+ if (filter.allow && !filter.allow.includes(jobName)) return false;
1394
+ if ((_a = filter.deny) == null ? void 0 : _a.includes(jobName)) return false;
1395
+ return true;
1396
+ });
1397
+ } catch (error) {
1398
+ import_logger4.logger.error("Error in getJobsFilter; no jobs will be taken on this claim.", { error });
1399
+ return [];
1400
+ }
1401
+ }
1402
+ async function canTakeJob(options, jobName) {
1403
+ const canTakeJobs = options.jobs[jobName].canTakeJobs;
1404
+ if (!canTakeJobs) return true;
1405
+ try {
1406
+ return await runCheck(options, `canTakeJobs of "${jobName}"`, canTakeJobs) === true;
1407
+ } catch (error) {
1408
+ import_logger4.logger.error(`Error in canTakeJobs of job "${jobName}"; it will not be taken.`, { error });
1409
+ return false;
1410
+ }
1411
+ }
1412
+ async function getTakeableJobNames(inputOptions) {
1413
+ const options = {
1414
+ timeoutMs: DEFAULT_JOBS_FILTER_TIMEOUT_MS,
1415
+ pendingChecks: /* @__PURE__ */ new Map(),
1416
+ ...inputOptions
1417
+ };
1418
+ const jobNames = inputOptions.getJobsFilter ? await filterByJobsFilter(options, inputOptions.getJobsFilter) : options.jobNames;
1419
+ const checks = await Promise.all(jobNames.map((jobName) => canTakeJob(options, jobName)));
1420
+ return jobNames.filter((_, index) => checks[index]);
1421
+ }
1422
+
1353
1423
  // src/services/WorkerService.ts
1354
1424
  var FILTERED_JOB_NAMES_ACQUISITION_HINT = "byJobName";
1355
1425
  var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
@@ -1361,6 +1431,7 @@ var WorkerService = class {
1361
1431
  __publicField(this, "partitionReconcilerJitterMs", 30 * 1e3);
1362
1432
  __publicField(this, "partitionReconcilerBatchPauseMs", 250);
1363
1433
  __publicField(this, "partitionReconcilerBatchSize", 500);
1434
+ __publicField(this, "jobsFilterTimeoutMs", DEFAULT_JOBS_FILTER_TIMEOUT_MS);
1364
1435
  __publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
1365
1436
  __publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
1366
1437
  }
@@ -1419,13 +1490,13 @@ var WorkerService = class {
1419
1490
  };
1420
1491
  this.reserveJobExecution(workersInstance, jobToRun.name);
1421
1492
  workersInstance.activeExecutions.set(jobToRun.executionId, execution);
1422
- import_logger4.logger.debug(`Starting job execution "${jobToRun.executionId}"`, jobToRun);
1493
+ import_logger5.logger.debug(`Starting job execution "${jobToRun.executionId}"`, jobToRun);
1423
1494
  const executionPromise = this.executor.executeJob(config, jobToRun, () => {
1424
1495
  this.releaseExecutionCapacity(workersInstance, execution, "stale");
1425
1496
  });
1426
1497
  execution.promise = executionPromise;
1427
1498
  void executionPromise.catch((error) => {
1428
- import_logger4.logger.error(`Unhandled error executing job "${jobToRun.name}"`, { error });
1499
+ import_logger5.logger.error(`Unhandled error executing job "${jobToRun.name}"`, { error });
1429
1500
  }).finally(() => {
1430
1501
  if (execution.status === "running") {
1431
1502
  this.releaseExecutionCapacity(workersInstance, execution, "finished");
@@ -1466,18 +1537,33 @@ var WorkerService = class {
1466
1537
  executionGuardRetryDelayMs: Math.max(config.pollInterval, 1e3)
1467
1538
  } : {}
1468
1539
  };
1469
- import_logger4.logger.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1540
+ import_logger5.logger.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1470
1541
  let consecutivePartitionMisses = 0;
1471
1542
  while (workersInstance.running) {
1472
1543
  let didMiss = false;
1473
1544
  let allJobNamesAtCapacity = false;
1474
1545
  try {
1475
1546
  while (workersInstance.activeExecutions.size < config.workersCount) {
1476
- const availableJobNames = this.getAvailableJobNames(config, workersInstance, jobNames);
1477
- if (availableJobNames.length === 0) {
1547
+ const jobNamesWithCapacity = this.getAvailableJobNames(config, workersInstance, jobNames);
1548
+ if (jobNamesWithCapacity.length === 0) {
1478
1549
  allJobNamesAtCapacity = workersInstance.activeExecutions.size > 0;
1479
1550
  break;
1480
1551
  }
1552
+ const availableJobNames = await Promise.race([
1553
+ getTakeableJobNames({
1554
+ jobs: config.jobs,
1555
+ getJobsFilter: config.getJobsFilter,
1556
+ jobNames: jobNamesWithCapacity,
1557
+ timeoutMs: this.jobsFilterTimeoutMs,
1558
+ pendingChecks: workersInstance.pendingFilterChecks
1559
+ }),
1560
+ workersInstance.stopSignal.then(() => [])
1561
+ ]);
1562
+ if (!workersInstance.running) break;
1563
+ if (availableJobNames.length === 0) {
1564
+ didMiss = true;
1565
+ break;
1566
+ }
1481
1567
  const jobAcquisitionHint = availableJobNames.length < jobNames.length ? FILTERED_JOB_NAMES_ACQUISITION_HINT : workersInstance.jobAcquisitionHint;
1482
1568
  const partition = this.getNextPartition(config, workersInstance);
1483
1569
  const jobToRun = await this.jobsRepo.getJobAndLock(
@@ -1488,7 +1574,7 @@ var WorkerService = class {
1488
1574
  );
1489
1575
  if (!jobToRun) {
1490
1576
  consecutivePartitionMisses++;
1491
- import_logger4.logger.debug(`No job to run in partition ${partition}`);
1577
+ import_logger5.logger.debug(`No job to run in partition ${partition}`);
1492
1578
  if (consecutivePartitionMisses >= config.nPartitions) {
1493
1579
  consecutivePartitionMisses = 0;
1494
1580
  didMiss = true;
@@ -1501,7 +1587,7 @@ var WorkerService = class {
1501
1587
  if (!workersInstance.running) break;
1502
1588
  }
1503
1589
  } catch (error) {
1504
- import_logger4.logger.error("Error in job scheduler.", { error });
1590
+ import_logger5.logger.error("Error in job scheduler.", { error });
1505
1591
  consecutivePartitionMisses = 0;
1506
1592
  didMiss = true;
1507
1593
  }
@@ -1553,7 +1639,7 @@ var WorkerService = class {
1553
1639
  } catch (error) {
1554
1640
  this.resetPendingJobAcquisitionHint(workersInstance);
1555
1641
  if (workersInstance.running) {
1556
- import_logger4.logger.warn("Job acquisition hint probe failed; keeping the current hint.", { error });
1642
+ import_logger5.logger.warn("Job acquisition hint probe failed; keeping the current hint.", { error });
1557
1643
  }
1558
1644
  return;
1559
1645
  }
@@ -1566,12 +1652,12 @@ var WorkerService = class {
1566
1652
  workersInstance.hasCompletedInitialJobAcquisitionHintProbe = true;
1567
1653
  workersInstance.jobAcquisitionHint = winner;
1568
1654
  this.resetPendingJobAcquisitionHint(workersInstance);
1569
- import_logger4.logger.info(`Selected job acquisition hint "${winner}" after startup probe.`);
1655
+ import_logger5.logger.info(`Selected job acquisition hint "${winner}" after startup probe.`);
1570
1656
  return;
1571
1657
  }
1572
1658
  if (winner === currentHint) {
1573
1659
  this.resetPendingJobAcquisitionHint(workersInstance);
1574
- import_logger4.logger.debug(`Keeping job acquisition hint "${currentHint}" after probe.`);
1660
+ import_logger5.logger.debug(`Keeping job acquisition hint "${currentHint}" after probe.`);
1575
1661
  return;
1576
1662
  }
1577
1663
  if (workersInstance.pendingJobAcquisitionHint === winner) {
@@ -1581,14 +1667,14 @@ var WorkerService = class {
1581
1667
  workersInstance.pendingJobAcquisitionHintWins = 1;
1582
1668
  }
1583
1669
  if (workersInstance.pendingJobAcquisitionHintWins < 2) {
1584
- import_logger4.logger.debug(
1670
+ import_logger5.logger.debug(
1585
1671
  `Keeping job acquisition hint "${currentHint}"; "${winner}" has one consecutive probe win.`
1586
1672
  );
1587
1673
  return;
1588
1674
  }
1589
1675
  workersInstance.jobAcquisitionHint = winner;
1590
1676
  this.resetPendingJobAcquisitionHint(workersInstance);
1591
- import_logger4.logger.info(`Changed job acquisition hint from "${currentHint}" to "${winner}".`);
1677
+ import_logger5.logger.info(`Changed job acquisition hint from "${currentHint}" to "${winner}".`);
1592
1678
  }
1593
1679
  async waitForNextJobAcquisitionHintProbe(workersInstance) {
1594
1680
  await new Promise((resolve) => {
@@ -1625,7 +1711,7 @@ var WorkerService = class {
1625
1711
  batchSize: this.partitionReconcilerBatchSize
1626
1712
  });
1627
1713
  } catch (error) {
1628
- import_logger4.logger.warn("Job partition reconciliation failed; it will be retried.", { error });
1714
+ import_logger5.logger.warn("Job partition reconciliation failed; it will be retried.", { error });
1629
1715
  return 0;
1630
1716
  }
1631
1717
  }
@@ -1665,6 +1751,7 @@ var WorkerService = class {
1665
1751
  activeExecutions: /* @__PURE__ */ new Map(),
1666
1752
  staleExecutions: /* @__PURE__ */ new Map(),
1667
1753
  runningJobsByName: /* @__PURE__ */ new Map(),
1754
+ pendingFilterChecks: /* @__PURE__ */ new Map(),
1668
1755
  jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
1669
1756
  jobAcquisitionHintProbeCycle: 0,
1670
1757
  hasCompletedInitialJobAcquisitionHintProbe: false,
@@ -1683,7 +1770,7 @@ var WorkerService = class {
1683
1770
  if (workersInstance.stopPromise) return workersInstance.stopPromise;
1684
1771
  workersInstance.stopPromise = (async () => {
1685
1772
  var _a, _b;
1686
- import_logger4.logger.info("Stopping job scheduler...");
1773
+ import_logger5.logger.info("Stopping job scheduler...");
1687
1774
  workersInstance.running = false;
1688
1775
  workersInstance.signalStop();
1689
1776
  (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
@@ -1708,17 +1795,17 @@ var WorkerService = class {
1708
1795
  const jobs = this.getJobs(config.jobs);
1709
1796
  await Promise.all(
1710
1797
  jobs.filter((job) => job.type === "recurrent").map(async (job) => {
1711
- import_logger4.logger.debug(`Ensuring records for job "${job.name}"...`);
1798
+ import_logger5.logger.debug(`Ensuring records for job "${job.name}"...`);
1712
1799
  await this.jobsRepo.ensureJobRecord(job, getRandomPartition(config.nPartitions));
1713
1800
  })
1714
1801
  );
1715
1802
  }
1716
1803
  async runScheduler(config, workersInstance) {
1717
- import_logger4.logger.debug("Will ensure maxTriesReached retention and TTL index");
1718
- void this.jobsRepo.ensureMaxTriesReachedRetention(config.maxTriesReachedRetentionMs).catch((error) => import_logger4.logger.error("Error ensuring maxTriesReached retention", { error }));
1719
- import_logger4.logger.debug("Will ensure records for recurrent jobs");
1804
+ import_logger5.logger.debug("Will ensure maxTriesReached retention and TTL index");
1805
+ void this.jobsRepo.ensureMaxTriesReachedRetention(config.maxTriesReachedRetentionMs).catch((error) => import_logger5.logger.error("Error ensuring maxTriesReached retention", { error }));
1806
+ import_logger5.logger.debug("Will ensure records for recurrent jobs");
1720
1807
  await this.ensureRecords(config);
1721
- import_logger4.logger.info(`Starting job scheduler with concurrency ${config.workersCount}`);
1808
+ import_logger5.logger.info(`Starting job scheduler with concurrency ${config.workersCount}`);
1722
1809
  await this.runSchedulerLoop(config, workersInstance);
1723
1810
  }
1724
1811
  /**
@@ -1744,7 +1831,7 @@ var WorkerService = class {
1744
1831
  setNameToJobs(config.jobs, config.nPartitions);
1745
1832
  (_b = (_a = this.jobsRepo).configureJobPartitions) == null ? void 0 : _b.call(_a, this.getJobNames(config.jobs), config.nPartitions);
1746
1833
  const workersInstance = this.createWorkersInstanceDefinition(config);
1747
- import_logger4.logger.debug("Starting job scheduler", config);
1834
+ import_logger5.logger.debug("Starting job scheduler", config);
1748
1835
  workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
1749
1836
  config,
1750
1837
  workersInstance
@@ -1761,7 +1848,7 @@ var WorkerService = class {
1761
1848
  workersInstance.signalStop();
1762
1849
  (_a2 = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a2.call(workersInstance);
1763
1850
  (_b2 = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b2.call(workersInstance);
1764
- import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
1851
+ import_logger5.logger.error("Job scheduler stopped after an unexpected error.", { error });
1765
1852
  });
1766
1853
  return workersInstance;
1767
1854
  }