agenda 5.0.0 → 6.0.0

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.
Files changed (358) hide show
  1. package/LICENSE.md +4 -0
  2. package/README.md +222 -1063
  3. package/dist/Job.d.ts +190 -0
  4. package/dist/Job.js +568 -0
  5. package/dist/Job.js.map +1 -0
  6. package/dist/JobProcessingQueue.d.ts +47 -0
  7. package/dist/JobProcessingQueue.js +101 -0
  8. package/dist/JobProcessingQueue.js.map +1 -0
  9. package/dist/JobProcessor.d.ts +70 -0
  10. package/dist/JobProcessor.js +458 -0
  11. package/dist/JobProcessor.js.map +1 -0
  12. package/dist/backends/index.d.ts +18 -0
  13. package/dist/backends/index.js +20 -0
  14. package/dist/backends/index.js.map +1 -0
  15. package/dist/decorators/Define.d.ts +33 -0
  16. package/dist/decorators/Define.js +44 -0
  17. package/dist/decorators/Define.js.map +1 -0
  18. package/dist/decorators/Every.d.ts +43 -0
  19. package/dist/decorators/Every.js +55 -0
  20. package/dist/decorators/Every.js.map +1 -0
  21. package/dist/decorators/JobsController.d.ts +43 -0
  22. package/dist/decorators/JobsController.js +37 -0
  23. package/dist/decorators/JobsController.js.map +1 -0
  24. package/dist/decorators/index.d.ts +41 -0
  25. package/dist/decorators/index.js +46 -0
  26. package/dist/decorators/index.js.map +1 -0
  27. package/dist/decorators/metadata.d.ts +50 -0
  28. package/dist/decorators/metadata.js +35 -0
  29. package/dist/decorators/metadata.js.map +1 -0
  30. package/dist/decorators/register.d.ts +52 -0
  31. package/dist/decorators/register.js +139 -0
  32. package/dist/decorators/register.js.map +1 -0
  33. package/dist/index.d.ts +359 -8
  34. package/dist/index.js +532 -20
  35. package/dist/index.js.map +1 -1
  36. package/dist/notifications/BaseNotificationChannel.d.ts +36 -0
  37. package/dist/notifications/BaseNotificationChannel.js +80 -0
  38. package/dist/notifications/BaseNotificationChannel.js.map +1 -0
  39. package/dist/notifications/InMemoryNotificationChannel.d.ts +12 -0
  40. package/dist/notifications/InMemoryNotificationChannel.js +26 -0
  41. package/dist/notifications/InMemoryNotificationChannel.js.map +1 -0
  42. package/dist/notifications/index.d.ts +2 -0
  43. package/dist/notifications/index.js +3 -0
  44. package/dist/notifications/index.js.map +1 -0
  45. package/dist/types/AgendaBackend.d.ts +53 -0
  46. package/dist/types/AgendaBackend.js +2 -0
  47. package/dist/types/AgendaBackend.js.map +1 -0
  48. package/dist/types/AgendaConfig.d.ts +9 -0
  49. package/dist/types/AgendaConfig.js +2 -0
  50. package/dist/types/AgendaConfig.js.map +1 -0
  51. package/dist/types/AgendaStatus.d.ts +32 -0
  52. package/dist/types/AgendaStatus.js +2 -0
  53. package/dist/types/AgendaStatus.js.map +1 -0
  54. package/dist/types/DbOptions.d.ts +4 -0
  55. package/dist/types/DbOptions.js +2 -0
  56. package/dist/types/DbOptions.js.map +1 -0
  57. package/dist/types/JobDefinition.d.ts +40 -0
  58. package/dist/types/JobDefinition.js +2 -0
  59. package/dist/types/JobDefinition.js.map +1 -0
  60. package/dist/types/JobParameters.d.ts +106 -0
  61. package/dist/types/JobParameters.js +17 -0
  62. package/dist/types/JobParameters.js.map +1 -0
  63. package/dist/types/JobQuery.d.ts +79 -0
  64. package/dist/types/JobQuery.js +33 -0
  65. package/dist/types/JobQuery.js.map +1 -0
  66. package/dist/types/JobRepository.d.ts +96 -0
  67. package/dist/types/JobRepository.js +2 -0
  68. package/dist/types/JobRepository.js.map +1 -0
  69. package/dist/types/NotificationChannel.d.ts +71 -0
  70. package/dist/types/NotificationChannel.js +2 -0
  71. package/dist/types/NotificationChannel.js.map +1 -0
  72. package/dist/utils/backoff.d.ts +152 -0
  73. package/dist/utils/backoff.js +167 -0
  74. package/dist/utils/backoff.js.map +1 -0
  75. package/dist/utils/dateConstraints.d.ts +48 -0
  76. package/dist/utils/dateConstraints.js +128 -0
  77. package/dist/utils/dateConstraints.js.map +1 -0
  78. package/dist/utils/isValidDate.d.ts +1 -0
  79. package/dist/utils/isValidDate.js +5 -0
  80. package/dist/utils/isValidDate.js.map +1 -0
  81. package/dist/utils/nextRunAt.d.ts +10 -0
  82. package/dist/utils/nextRunAt.js +110 -0
  83. package/dist/utils/nextRunAt.js.map +1 -0
  84. package/dist/utils/priority.d.ts +13 -0
  85. package/dist/utils/priority.js +20 -0
  86. package/dist/utils/priority.js.map +1 -0
  87. package/dist/utils/processEvery.d.ts +1 -0
  88. package/dist/utils/processEvery.js +7 -0
  89. package/dist/utils/processEvery.js.map +1 -0
  90. package/dist/utils/stack.d.ts +1 -0
  91. package/dist/utils/stack.js +18 -0
  92. package/dist/utils/stack.js.map +1 -0
  93. package/package.json +40 -76
  94. package/History.md +0 -617
  95. package/dist/agenda/cancel.d.ts +0 -11
  96. package/dist/agenda/cancel.d.ts.map +0 -1
  97. package/dist/agenda/cancel.js +0 -41
  98. package/dist/agenda/cancel.js.map +0 -1
  99. package/dist/agenda/close.d.ts +0 -17
  100. package/dist/agenda/close.d.ts.map +0 -1
  101. package/dist/agenda/close.js +0 -47
  102. package/dist/agenda/close.js.map +0 -1
  103. package/dist/agenda/create.d.ts +0 -11
  104. package/dist/agenda/create.d.ts.map +0 -1
  105. package/dist/agenda/create.js +0 -27
  106. package/dist/agenda/create.js.map +0 -1
  107. package/dist/agenda/database.d.ts +0 -20
  108. package/dist/agenda/database.d.ts.map +0 -1
  109. package/dist/agenda/database.js +0 -56
  110. package/dist/agenda/database.js.map +0 -1
  111. package/dist/agenda/db-init.d.ts +0 -11
  112. package/dist/agenda/db-init.d.ts.map +0 -1
  113. package/dist/agenda/db-init.js +0 -40
  114. package/dist/agenda/db-init.js.map +0 -1
  115. package/dist/agenda/default-concurrency.d.ts +0 -9
  116. package/dist/agenda/default-concurrency.d.ts.map +0 -1
  117. package/dist/agenda/default-concurrency.js +0 -21
  118. package/dist/agenda/default-concurrency.js.map +0 -1
  119. package/dist/agenda/default-lock-lifetime.d.ts +0 -10
  120. package/dist/agenda/default-lock-lifetime.d.ts.map +0 -1
  121. package/dist/agenda/default-lock-lifetime.js +0 -22
  122. package/dist/agenda/default-lock-lifetime.js.map +0 -1
  123. package/dist/agenda/default-lock-limit.d.ts +0 -10
  124. package/dist/agenda/default-lock-limit.d.ts.map +0 -1
  125. package/dist/agenda/default-lock-limit.js +0 -22
  126. package/dist/agenda/default-lock-limit.js.map +0 -1
  127. package/dist/agenda/define.d.ts +0 -45
  128. package/dist/agenda/define.d.ts.map +0 -1
  129. package/dist/agenda/define.js +0 -44
  130. package/dist/agenda/define.js.map +0 -1
  131. package/dist/agenda/disable.d.ts +0 -11
  132. package/dist/agenda/disable.d.ts.map +0 -1
  133. package/dist/agenda/disable.js +0 -42
  134. package/dist/agenda/disable.js.map +0 -1
  135. package/dist/agenda/drain.d.ts +0 -9
  136. package/dist/agenda/drain.d.ts.map +0 -1
  137. package/dist/agenda/drain.js +0 -46
  138. package/dist/agenda/drain.js.map +0 -1
  139. package/dist/agenda/enable.d.ts +0 -12
  140. package/dist/agenda/enable.d.ts.map +0 -1
  141. package/dist/agenda/enable.js +0 -43
  142. package/dist/agenda/enable.js.map +0 -1
  143. package/dist/agenda/every.d.ts +0 -15
  144. package/dist/agenda/every.d.ts.map +0 -1
  145. package/dist/agenda/every.js +0 -77
  146. package/dist/agenda/every.js.map +0 -1
  147. package/dist/agenda/find-and-lock-next-job.d.ts +0 -13
  148. package/dist/agenda/find-and-lock-next-job.d.ts.map +0 -1
  149. package/dist/agenda/find-and-lock-next-job.js +0 -75
  150. package/dist/agenda/find-and-lock-next-job.js.map +0 -1
  151. package/dist/agenda/has-mongo-protocol.d.ts +0 -7
  152. package/dist/agenda/has-mongo-protocol.d.ts.map +0 -1
  153. package/dist/agenda/has-mongo-protocol.js +0 -13
  154. package/dist/agenda/has-mongo-protocol.js.map +0 -1
  155. package/dist/agenda/index.d.ts +0 -130
  156. package/dist/agenda/index.d.ts.map +0 -1
  157. package/dist/agenda/index.js +0 -128
  158. package/dist/agenda/index.js.map +0 -1
  159. package/dist/agenda/job-processing-queue.d.ts +0 -18
  160. package/dist/agenda/job-processing-queue.d.ts.map +0 -1
  161. package/dist/agenda/job-processing-queue.js +0 -76
  162. package/dist/agenda/job-processing-queue.js.map +0 -1
  163. package/dist/agenda/jobs.d.ts +0 -15
  164. package/dist/agenda/jobs.d.ts.map +0 -1
  165. package/dist/agenda/jobs.js +0 -36
  166. package/dist/agenda/jobs.js.map +0 -1
  167. package/dist/agenda/lock-limit.d.ts +0 -9
  168. package/dist/agenda/lock-limit.d.ts.map +0 -1
  169. package/dist/agenda/lock-limit.js +0 -22
  170. package/dist/agenda/lock-limit.js.map +0 -1
  171. package/dist/agenda/max-concurrency.d.ts +0 -10
  172. package/dist/agenda/max-concurrency.d.ts.map +0 -1
  173. package/dist/agenda/max-concurrency.js +0 -22
  174. package/dist/agenda/max-concurrency.js.map +0 -1
  175. package/dist/agenda/mongo.d.ts +0 -12
  176. package/dist/agenda/mongo.d.ts.map +0 -1
  177. package/dist/agenda/mongo.js +0 -18
  178. package/dist/agenda/mongo.js.map +0 -1
  179. package/dist/agenda/name.d.ts +0 -9
  180. package/dist/agenda/name.d.ts.map +0 -1
  181. package/dist/agenda/name.js +0 -21
  182. package/dist/agenda/name.js.map +0 -1
  183. package/dist/agenda/now.d.ts +0 -11
  184. package/dist/agenda/now.d.ts.map +0 -1
  185. package/dist/agenda/now.js +0 -41
  186. package/dist/agenda/now.js.map +0 -1
  187. package/dist/agenda/process-every.d.ts +0 -9
  188. package/dist/agenda/process-every.d.ts.map +0 -1
  189. package/dist/agenda/process-every.js +0 -23
  190. package/dist/agenda/process-every.js.map +0 -1
  191. package/dist/agenda/purge.d.ts +0 -9
  192. package/dist/agenda/purge.d.ts.map +0 -1
  193. package/dist/agenda/purge.js +0 -33
  194. package/dist/agenda/purge.js.map +0 -1
  195. package/dist/agenda/save-job.d.ts +0 -11
  196. package/dist/agenda/save-job.d.ts.map +0 -1
  197. package/dist/agenda/save-job.js +0 -156
  198. package/dist/agenda/save-job.js.map +0 -1
  199. package/dist/agenda/schedule.d.ts +0 -14
  200. package/dist/agenda/schedule.d.ts.map +0 -1
  201. package/dist/agenda/schedule.js +0 -62
  202. package/dist/agenda/schedule.js.map +0 -1
  203. package/dist/agenda/sort.d.ts +0 -10
  204. package/dist/agenda/sort.d.ts.map +0 -1
  205. package/dist/agenda/sort.js +0 -22
  206. package/dist/agenda/sort.js.map +0 -1
  207. package/dist/agenda/start.d.ts +0 -10
  208. package/dist/agenda/start.d.ts.map +0 -1
  209. package/dist/agenda/start.js +0 -39
  210. package/dist/agenda/start.js.map +0 -1
  211. package/dist/agenda/stop.d.ts +0 -9
  212. package/dist/agenda/stop.d.ts.map +0 -1
  213. package/dist/agenda/stop.js +0 -57
  214. package/dist/agenda/stop.js.map +0 -1
  215. package/dist/cjs.d.ts +0 -2
  216. package/dist/cjs.d.ts.map +0 -1
  217. package/dist/cjs.js +0 -7
  218. package/dist/cjs.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/job/compute-next-run-at.d.ts +0 -8
  221. package/dist/job/compute-next-run-at.d.ts.map +0 -1
  222. package/dist/job/compute-next-run-at.js +0 -162
  223. package/dist/job/compute-next-run-at.js.map +0 -1
  224. package/dist/job/disable.d.ts +0 -8
  225. package/dist/job/disable.d.ts.map +0 -1
  226. package/dist/job/disable.js +0 -14
  227. package/dist/job/disable.js.map +0 -1
  228. package/dist/job/enable.d.ts +0 -8
  229. package/dist/job/enable.d.ts.map +0 -1
  230. package/dist/job/enable.js +0 -14
  231. package/dist/job/enable.js.map +0 -1
  232. package/dist/job/fail.d.ts +0 -9
  233. package/dist/job/fail.d.ts.map +0 -1
  234. package/dist/job/fail.js +0 -28
  235. package/dist/job/fail.js.map +0 -1
  236. package/dist/job/index.d.ts +0 -142
  237. package/dist/job/index.d.ts.map +0 -1
  238. package/dist/job/index.js +0 -82
  239. package/dist/job/index.js.map +0 -1
  240. package/dist/job/is-running.d.ts +0 -12
  241. package/dist/job/is-running.d.ts.map +0 -1
  242. package/dist/job/is-running.js +0 -27
  243. package/dist/job/is-running.js.map +0 -1
  244. package/dist/job/priority.d.ts +0 -7
  245. package/dist/job/priority.d.ts.map +0 -1
  246. package/dist/job/priority.js +0 -14
  247. package/dist/job/priority.js.map +0 -1
  248. package/dist/job/remove.d.ts +0 -8
  249. package/dist/job/remove.d.ts.map +0 -1
  250. package/dist/job/remove.js +0 -24
  251. package/dist/job/remove.js.map +0 -1
  252. package/dist/job/repeat-at.d.ts +0 -9
  253. package/dist/job/repeat-at.d.ts.map +0 -1
  254. package/dist/job/repeat-at.js +0 -15
  255. package/dist/job/repeat-at.js.map +0 -1
  256. package/dist/job/repeat-every.d.ts +0 -17
  257. package/dist/job/repeat-every.d.ts.map +0 -1
  258. package/dist/job/repeat-every.js +0 -32
  259. package/dist/job/repeat-every.js.map +0 -1
  260. package/dist/job/run.d.ts +0 -8
  261. package/dist/job/run.d.ts.map +0 -1
  262. package/dist/job/run.js +0 -99
  263. package/dist/job/run.js.map +0 -1
  264. package/dist/job/save.d.ts +0 -9
  265. package/dist/job/save.d.ts.map +0 -1
  266. package/dist/job/save.js +0 -25
  267. package/dist/job/save.js.map +0 -1
  268. package/dist/job/schedule.d.ts +0 -9
  269. package/dist/job/schedule.d.ts.map +0 -1
  270. package/dist/job/schedule.js +0 -21
  271. package/dist/job/schedule.js.map +0 -1
  272. package/dist/job/set-shouldsaveresult.d.ts +0 -7
  273. package/dist/job/set-shouldsaveresult.d.ts.map +0 -1
  274. package/dist/job/set-shouldsaveresult.js +0 -13
  275. package/dist/job/set-shouldsaveresult.js.map +0 -1
  276. package/dist/job/to-json.d.ts +0 -9
  277. package/dist/job/to-json.d.ts.map +0 -1
  278. package/dist/job/to-json.js +0 -36
  279. package/dist/job/to-json.js.map +0 -1
  280. package/dist/job/touch.d.ts +0 -8
  281. package/dist/job/touch.d.ts.map +0 -1
  282. package/dist/job/touch.js +0 -25
  283. package/dist/job/touch.js.map +0 -1
  284. package/dist/job/unique.d.ts +0 -12
  285. package/dist/job/unique.d.ts.map +0 -1
  286. package/dist/job/unique.js +0 -17
  287. package/dist/job/unique.js.map +0 -1
  288. package/dist/utils/create-job.d.ts +0 -10
  289. package/dist/utils/create-job.d.ts.map +0 -1
  290. package/dist/utils/create-job.js +0 -16
  291. package/dist/utils/create-job.js.map +0 -1
  292. package/dist/utils/index.d.ts +0 -4
  293. package/dist/utils/index.d.ts.map +0 -1
  294. package/dist/utils/index.js +0 -16
  295. package/dist/utils/index.js.map +0 -1
  296. package/dist/utils/parse-priority.d.ts +0 -6
  297. package/dist/utils/parse-priority.d.ts.map +0 -1
  298. package/dist/utils/parse-priority.js +0 -35
  299. package/dist/utils/parse-priority.js.map +0 -1
  300. package/dist/utils/process-jobs.d.ts +0 -8
  301. package/dist/utils/process-jobs.d.ts.map +0 -1
  302. package/dist/utils/process-jobs.js +0 -308
  303. package/dist/utils/process-jobs.js.map +0 -1
  304. package/es.js +0 -1
  305. package/lib/agenda/cancel.ts +0 -28
  306. package/lib/agenda/close.ts +0 -37
  307. package/lib/agenda/create.ts +0 -22
  308. package/lib/agenda/database.ts +0 -63
  309. package/lib/agenda/db-init.ts +0 -45
  310. package/lib/agenda/default-concurrency.ts +0 -19
  311. package/lib/agenda/default-lock-lifetime.ts +0 -17
  312. package/lib/agenda/default-lock-limit.ts +0 -17
  313. package/lib/agenda/define.ts +0 -85
  314. package/lib/agenda/disable.ts +0 -28
  315. package/lib/agenda/drain.ts +0 -30
  316. package/lib/agenda/enable.ts +0 -29
  317. package/lib/agenda/every.ts +0 -86
  318. package/lib/agenda/find-and-lock-next-job.ts +0 -78
  319. package/lib/agenda/has-mongo-protocol.ts +0 -8
  320. package/lib/agenda/index.ts +0 -227
  321. package/lib/agenda/job-processing-queue.ts +0 -99
  322. package/lib/agenda/jobs.ts +0 -31
  323. package/lib/agenda/lock-limit.ts +0 -17
  324. package/lib/agenda/max-concurrency.ts +0 -20
  325. package/lib/agenda/mongo.ts +0 -21
  326. package/lib/agenda/name.ts +0 -16
  327. package/lib/agenda/now.ts +0 -31
  328. package/lib/agenda/process-every.ts +0 -18
  329. package/lib/agenda/purge.ts +0 -19
  330. package/lib/agenda/save-job.ts +0 -185
  331. package/lib/agenda/schedule.ts +0 -79
  332. package/lib/agenda/sort.ts +0 -17
  333. package/lib/agenda/start.ts +0 -30
  334. package/lib/agenda/stop.ts +0 -49
  335. package/lib/cjs.ts +0 -5
  336. package/lib/index.ts +0 -11
  337. package/lib/job/compute-next-run-at.ts +0 -191
  338. package/lib/job/disable.ts +0 -11
  339. package/lib/job/enable.ts +0 -11
  340. package/lib/job/fail.ts +0 -29
  341. package/lib/job/index.ts +0 -221
  342. package/lib/job/is-running.ts +0 -29
  343. package/lib/job/priority.ts +0 -11
  344. package/lib/job/remove.ts +0 -10
  345. package/lib/job/repeat-at.ts +0 -12
  346. package/lib/job/repeat-every.ts +0 -40
  347. package/lib/job/run.ts +0 -125
  348. package/lib/job/save.ts +0 -11
  349. package/lib/job/schedule.ts +0 -15
  350. package/lib/job/set-shouldsaveresult.ts +0 -10
  351. package/lib/job/to-json.ts +0 -36
  352. package/lib/job/touch.ts +0 -11
  353. package/lib/job/unique.ts +0 -18
  354. package/lib/utils/create-job.ts +0 -13
  355. package/lib/utils/index.ts +0 -3
  356. package/lib/utils/parse-priority.ts +0 -37
  357. package/lib/utils/process-jobs.ts +0 -379
  358. package/tsconfig.json +0 -72
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @class
3
+ */
4
+ export class JobProcessingQueue {
5
+ agenda;
6
+ _queue;
7
+ constructor(agenda) {
8
+ this.agenda = agenda;
9
+ this._queue = [];
10
+ }
11
+ get length() {
12
+ return this._queue.length;
13
+ }
14
+ getQueue() {
15
+ return this._queue;
16
+ }
17
+ /**
18
+ * Pops and returns last queue element (next job to be processed) without checking concurrency.
19
+ * @returns {Job} Next Job to be processed
20
+ */
21
+ pop() {
22
+ return this._queue.pop();
23
+ }
24
+ /**
25
+ * Inserts job in first queue position
26
+ * @param {Job} job job to add to queue
27
+ * @returns {undefined}
28
+ */
29
+ /*
30
+ push(job: Job): void {
31
+ this._queue.push(job);
32
+ } */
33
+ remove(job) {
34
+ let removeJobIndex = this._queue.indexOf(job);
35
+ if (removeJobIndex === -1) {
36
+ // lookup by id
37
+ removeJobIndex = this._queue.findIndex(j => j.attrs._id?.toString() === job.attrs._id?.toString());
38
+ }
39
+ if (removeJobIndex === -1) {
40
+ throw new Error(`cannot find job ${job.attrs._id} in processing queue?`);
41
+ }
42
+ this._queue.splice(removeJobIndex, 1);
43
+ }
44
+ /**
45
+ * Inserts job in queue where it will be order from left to right in decreasing
46
+ * order of nextRunAt and priority (in case of same nextRunAt), if all values
47
+ * are even the first jobs to be introduced will have priority
48
+ * @param {Job} job job to add to queue
49
+ * @returns {undefined}
50
+ */
51
+ insert(job) {
52
+ const matchIndex = this._queue.findIndex(element => {
53
+ if (element.attrs.nextRunAt &&
54
+ job.attrs.nextRunAt &&
55
+ element.attrs.nextRunAt.getTime() <= job.attrs.nextRunAt.getTime()) {
56
+ if (element.attrs.nextRunAt.getTime() === job.attrs.nextRunAt.getTime()) {
57
+ if (element.attrs.priority >= job.attrs.priority) {
58
+ return true;
59
+ }
60
+ }
61
+ else {
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ });
67
+ if (matchIndex === -1) {
68
+ // put on left side of the queue
69
+ this._queue.unshift(job);
70
+ }
71
+ else {
72
+ this._queue.splice(matchIndex, 0, job);
73
+ }
74
+ }
75
+ /**
76
+ * Returns (does not pop, element remains in queue) first element (always from the right)
77
+ * that can be processed (not blocked by concurrency execution)
78
+ * @param {Object} jobStatus current status of jobs
79
+ * @returns {Job} Next Job to be processed
80
+ */
81
+ returnNextConcurrencyFreeJob(jobStatus, handledJobs) {
82
+ const next = Object.keys(this._queue).reverse().find(i => {
83
+ const def = this.agenda.definitions[this._queue[i].attrs.name];
84
+ const status = jobStatus[this._queue[i].attrs.name];
85
+ // check if we have a definition
86
+ // if there is no status available, we are good to go
87
+ // if there is no max concurrency defined (0), we are also good to go
88
+ // and if concurrency limit is not reached yet (actual running jobs is lower than max concurrency)
89
+ if (def &&
90
+ !handledJobs.includes(this._queue[i].attrs._id) &&
91
+ (!status || !def.concurrency || status.running < def.concurrency)) {
92
+ return true;
93
+ }
94
+ return false;
95
+ });
96
+ return next !== undefined
97
+ ? this._queue[next]
98
+ : undefined;
99
+ }
100
+ }
101
+ //# sourceMappingURL=JobProcessingQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobProcessingQueue.js","sourceRoot":"","sources":["../src/JobProcessingQueue.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAGV;IAFZ,MAAM,CAAQ;IAEtB,YAAoB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;QACjC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,QAAQ;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;IAED;;;OAGG;IACH,GAAG;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED;;;;OAIG;IACH;;;QAGI;IAEJ,MAAM,CAAC,GAAQ;QACd,IAAI,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,eAAe;YACf,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CACrC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,CAC1D,CAAC;QACH,CAAC;QACD,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,KAAK,CAAC,GAAG,uBAAuB,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,GAAQ;QACd,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;YAClD,IACC,OAAO,CAAC,KAAK,CAAC,SAAS;gBACvB,GAAG,CAAC,KAAK,CAAC,SAAS;gBACnB,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,EACjE,CAAC;gBACF,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;oBACzE,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;wBAClD,OAAO,IAAI,CAAC;oBACb,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,OAAO,IAAI,CAAC;gBACb,CAAC;YACF,CAAC;YAED,OAAO,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACvB,gCAAgC;YAChC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACH,4BAA4B,CAC3B,SAMC,EACD,WAAmC;QAEnC,MAAM,IAAI,GAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAyB,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;YACjF,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEpD,gCAAgC;YAChC,qDAAqD;YACrD,qEAAqE;YACrE,kGAAkG;YAClG,IACC,GAAG;gBACH,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;gBAC/C,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,EAChE,CAAC;gBACF,OAAO,IAAI,CAAC;YACb,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,KAAK,SAAS;YACxB,CAAC,CAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAgE;YACnF,CAAC,CAAC,SAAS,CAAC;IACd,CAAC;CACD"}
@@ -0,0 +1,70 @@
1
+ import type { AgendaStatus } from './types/AgendaStatus.js';
2
+ import type { Agenda, JobWithId } from './index.js';
3
+ import type { NotificationChannel } from './types/NotificationChannel.js';
4
+ /**
5
+ * @class
6
+ * Process methods for jobs
7
+ */
8
+ export declare class JobProcessor {
9
+ private agenda;
10
+ private maxConcurrency;
11
+ private totalLockLimit;
12
+ private processEvery;
13
+ private jobStatus;
14
+ private localQueueProcessing;
15
+ getStatus(fullDetails?: boolean): Promise<AgendaStatus>;
16
+ private nextScanAt;
17
+ private jobQueue;
18
+ private runningJobs;
19
+ private lockedJobs;
20
+ private isJobQueueFilling;
21
+ private isRunning;
22
+ private processInterval?;
23
+ private notificationChannel?;
24
+ private notificationUnsubscribe?;
25
+ constructor(agenda: Agenda, maxConcurrency: number, totalLockLimit: number, processEvery: number, notificationChannel?: NotificationChannel);
26
+ stop(): JobWithId[];
27
+ /**
28
+ * Waits for all currently running jobs to finish, then stops the processor.
29
+ * Unlike stop(), this method does not immediately return - it waits for graceful completion.
30
+ * @returns Promise that resolves when all running jobs have completed
31
+ */
32
+ drain(): Promise<void>;
33
+ /**
34
+ * Handle incoming job notification - triggers immediate processing
35
+ */
36
+ private handleNotification;
37
+ process(): Promise<void>;
38
+ /**
39
+ * Returns true if a job of the specified name can be locked.
40
+ * Considers maximum locked jobs at any time if self._lockLimit is > 0
41
+ * Considers maximum locked jobs of the specified name at any time if jobDefinition.lockLimit is > 0
42
+ * @param {String} name name of job to check if we should lock or not
43
+ * @returns {boolean} whether or not you should lock job
44
+ */
45
+ shouldLock(name: string): boolean;
46
+ /**
47
+ * Internal method that adds a job to be processed to the local queue
48
+ * @param job - Job to enqueue
49
+ */
50
+ private enqueueJob;
51
+ private findAndLockNextJob;
52
+ /**
53
+ * Internal method used to fill a queue with jobs that can be run
54
+ * @param {String} name fill a queue with specific job name
55
+ * @returns {undefined}
56
+ */
57
+ private jobQueueFilling;
58
+ /**
59
+ * Internal method that processes any jobs in the local queue (array)
60
+ * handledJobs keeps list of already processed jobs
61
+ * @returns {undefined}
62
+ */
63
+ private jobProcessing;
64
+ /**
65
+ * Internal method that tries to run a job and if it fails, retries again!
66
+ * @returns {boolean} processed a job or not
67
+ */
68
+ private runOrRetry;
69
+ private updateStatus;
70
+ }
@@ -0,0 +1,458 @@
1
+ import debug from 'debug';
2
+ import packageJson from '../package.json' with { type: 'json' };
3
+ import { Job } from './Job.js';
4
+ import { JobProcessingQueue } from './JobProcessingQueue.js';
5
+ const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
6
+ const log = debug('agenda:jobProcessor');
7
+ const agendaVersion = packageJson.version;
8
+ const MAX_SAFE_32BIT_INTEGER = 2 ** 31; // Math.pow(2,31);
9
+ /**
10
+ * @class
11
+ * Process methods for jobs
12
+ */
13
+ export class JobProcessor {
14
+ agenda;
15
+ maxConcurrency;
16
+ totalLockLimit;
17
+ processEvery;
18
+ jobStatus = {};
19
+ localQueueProcessing = 0;
20
+ async getStatus(fullDetails = false) {
21
+ const jobStatus = Object.keys(this.jobStatus).reduce((obj, key) => {
22
+ const status = this.jobStatus[key];
23
+ obj[key] = {
24
+ running: status?.running ?? 0,
25
+ locked: status?.locked ?? 0,
26
+ config: this.agenda.definitions[key]
27
+ };
28
+ return obj;
29
+ }, {});
30
+ return {
31
+ version: agendaVersion,
32
+ queueName: this.agenda.attrs.name,
33
+ totalQueueSizeDB: await this.agenda.db.getQueueSize(),
34
+ config: {
35
+ totalLockLimit: this.totalLockLimit,
36
+ maxConcurrency: this.maxConcurrency,
37
+ processEvery: this.processEvery
38
+ },
39
+ backend: {
40
+ // Placeholder - will be overwritten by Agenda.getRunningStats()
41
+ name: 'unknown',
42
+ hasNotificationChannel: false
43
+ },
44
+ internal: {
45
+ localQueueProcessing: this.localQueueProcessing
46
+ },
47
+ jobStatus,
48
+ queuedJobs: !fullDetails
49
+ ? this.jobQueue.length
50
+ : this.jobQueue.getQueue().map(job => ({
51
+ ...job.toJson(),
52
+ canceled: job.getCanceledMessage()
53
+ })),
54
+ runningJobs: !fullDetails
55
+ ? this.runningJobs.length
56
+ : this.runningJobs.map(job => ({
57
+ ...job.toJson(),
58
+ canceled: job.getCanceledMessage()
59
+ })),
60
+ lockedJobs: !fullDetails
61
+ ? this.lockedJobs.length
62
+ : this.lockedJobs.map(job => ({
63
+ ...job.toJson(),
64
+ canceled: job.getCanceledMessage()
65
+ }))
66
+ };
67
+ }
68
+ nextScanAt = new Date();
69
+ jobQueue;
70
+ runningJobs = [];
71
+ lockedJobs = [];
72
+ isJobQueueFilling = new Map();
73
+ isRunning = true;
74
+ processInterval;
75
+ notificationChannel;
76
+ notificationUnsubscribe;
77
+ constructor(agenda, maxConcurrency, totalLockLimit, processEvery, notificationChannel) {
78
+ this.agenda = agenda;
79
+ this.maxConcurrency = maxConcurrency;
80
+ this.totalLockLimit = totalLockLimit;
81
+ this.processEvery = processEvery;
82
+ this.notificationChannel = notificationChannel;
83
+ this.jobQueue = new JobProcessingQueue(this.agenda);
84
+ log('creating interval to call processJobs every [%dms]', processEvery);
85
+ this.processInterval = setInterval(() => this.process(), processEvery);
86
+ this.process();
87
+ // Subscribe to job notifications for real-time processing
88
+ if (this.notificationChannel) {
89
+ log('subscribing to notification channel');
90
+ this.notificationUnsubscribe = this.notificationChannel.subscribe(this.handleNotification.bind(this));
91
+ }
92
+ }
93
+ stop() {
94
+ log.extend('stop')('stop job processor', this.isRunning);
95
+ this.isRunning = false;
96
+ if (this.processInterval) {
97
+ clearInterval(this.processInterval);
98
+ this.processInterval = undefined;
99
+ }
100
+ // Unsubscribe from notifications
101
+ if (this.notificationUnsubscribe) {
102
+ log.extend('stop')('unsubscribing from notification channel');
103
+ this.notificationUnsubscribe();
104
+ this.notificationUnsubscribe = undefined;
105
+ }
106
+ // Return both locked and running jobs so they can all be unlocked
107
+ // Running jobs are also "locked" in the database (they have lockedAt set),
108
+ // they just moved from lockedJobs to runningJobs when processing started
109
+ return [...this.lockedJobs, ...this.runningJobs];
110
+ }
111
+ /**
112
+ * Waits for all currently running jobs to finish, then stops the processor.
113
+ * Unlike stop(), this method does not immediately return - it waits for graceful completion.
114
+ * @returns Promise that resolves when all running jobs have completed
115
+ */
116
+ async drain() {
117
+ log.extend('drain')('drain called, clearing interval for processJobs()');
118
+ // Stop accepting new jobs
119
+ if (this.processInterval) {
120
+ clearInterval(this.processInterval);
121
+ this.processInterval = undefined;
122
+ }
123
+ // Unsubscribe from notifications to stop receiving new job notifications
124
+ if (this.notificationUnsubscribe) {
125
+ log.extend('drain')('unsubscribing from notification channel');
126
+ this.notificationUnsubscribe();
127
+ this.notificationUnsubscribe = undefined;
128
+ }
129
+ // If no jobs are running, resolve immediately
130
+ if (this.runningJobs.length === 0) {
131
+ log.extend('drain')('no running jobs, resolving immediately');
132
+ this.isRunning = false;
133
+ return;
134
+ }
135
+ log.extend('drain')('waiting for %d running jobs to finish', this.runningJobs.length);
136
+ // Wait for all running jobs to complete
137
+ return new Promise(resolve => {
138
+ const checkComplete = () => {
139
+ if (this.runningJobs.length === 0) {
140
+ log.extend('drain')('all jobs completed, resolving');
141
+ this.isRunning = false;
142
+ resolve();
143
+ }
144
+ };
145
+ // Listen for 'complete' events from the agenda
146
+ const completeListener = () => {
147
+ // Running jobs are removed after the event is emitted,
148
+ // so we check on next tick
149
+ setImmediate(checkComplete);
150
+ };
151
+ this.agenda.on('complete', completeListener);
152
+ // Also check immediately in case jobs finished between our check and listener setup
153
+ checkComplete();
154
+ });
155
+ }
156
+ /**
157
+ * Handle incoming job notification - triggers immediate processing
158
+ */
159
+ async handleNotification(notification) {
160
+ if (!this.isRunning) {
161
+ log.extend('handleNotification')('JobProcessor not running, ignoring notification');
162
+ return;
163
+ }
164
+ // Only process notifications for jobs we have definitions for
165
+ if (!this.agenda.definitions[notification.jobName]) {
166
+ log.extend('handleNotification')('no definition for job [%s], ignoring notification', notification.jobName);
167
+ return;
168
+ }
169
+ // Check if the job should run soon (within the next scan interval)
170
+ if (notification.nextRunAt && notification.nextRunAt > this.nextScanAt) {
171
+ log.extend('handleNotification')('job [%s:%s] nextRunAt is after nextScanAt, will be picked up by regular scan', notification.jobName, notification.jobId);
172
+ return;
173
+ }
174
+ log.extend('handleNotification')('received notification for job [%s:%s], triggering immediate queue fill', notification.jobName, notification.jobId);
175
+ // Fill the queue for this job type and process
176
+ await this.jobQueueFilling(notification.jobName);
177
+ this.jobProcessing();
178
+ }
179
+ // processJobs
180
+ async process() {
181
+ // Make sure an interval has actually been set
182
+ // Prevents race condition with 'Agenda.stop' and already scheduled run
183
+ if (!this.isRunning) {
184
+ log.extend('process')('JobProcessor got stopped already, returning');
185
+ return;
186
+ }
187
+ log.extend('process')('starting to process jobs');
188
+ // Go through each jobName set in 'Agenda.process' and fill the queue with the next jobs
189
+ await Promise.all(Object.keys(this.agenda.definitions).map(async (jobName) => {
190
+ log.extend('process')('queuing up job to process: [%s]', jobName);
191
+ await this.jobQueueFilling(jobName);
192
+ }));
193
+ this.jobProcessing();
194
+ }
195
+ /**
196
+ * Returns true if a job of the specified name can be locked.
197
+ * Considers maximum locked jobs at any time if self._lockLimit is > 0
198
+ * Considers maximum locked jobs of the specified name at any time if jobDefinition.lockLimit is > 0
199
+ * @param {String} name name of job to check if we should lock or not
200
+ * @returns {boolean} whether or not you should lock job
201
+ */
202
+ shouldLock(name) {
203
+ const jobDefinition = this.agenda.definitions[name];
204
+ let shouldLock = true;
205
+ // global lock limit
206
+ if (this.totalLockLimit && this.lockedJobs.length >= this.totalLockLimit) {
207
+ shouldLock = false;
208
+ }
209
+ // job specific lock limit
210
+ const status = this.jobStatus[name];
211
+ if (jobDefinition.lockLimit && status && status.locked >= jobDefinition.lockLimit) {
212
+ shouldLock = false;
213
+ }
214
+ log.extend('shouldLock')('job [%s] lock status: shouldLock = %s', name, shouldLock, `${status?.locked} >= ${jobDefinition?.lockLimit}`, `${this.lockedJobs.length} >= ${this.totalLockLimit}`);
215
+ return shouldLock;
216
+ }
217
+ /**
218
+ * Internal method that adds a job to be processed to the local queue
219
+ * @param job - Job to enqueue
220
+ */
221
+ enqueueJob(job) {
222
+ this.jobQueue.insert(job);
223
+ }
224
+ async findAndLockNextJob(jobName, definition) {
225
+ const lockDeadline = new Date(Date.now().valueOf() - definition.lockLifetime);
226
+ log.extend('findAndLockNextJob')(`looking for lockable jobs for ${jobName} (lock dead line = ${lockDeadline})`);
227
+ // Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed
228
+ const result = await this.agenda.db.getNextJobToRun(jobName, this.nextScanAt, lockDeadline, undefined, { lastModifiedBy: this.agenda.attrs.name || undefined });
229
+ if (result) {
230
+ log.extend('findAndLockNextJob')('found a job available to lock, creating a new job on Agenda with id [%s]', result._id);
231
+ return new Job(this.agenda, result, true);
232
+ }
233
+ return undefined;
234
+ }
235
+ /**
236
+ * Internal method used to fill a queue with jobs that can be run
237
+ * @param {String} name fill a queue with specific job name
238
+ * @returns {undefined}
239
+ */
240
+ async jobQueueFilling(name) {
241
+ this.isJobQueueFilling.set(name, true);
242
+ try {
243
+ // Don't lock because of a limit we have set (lockLimit, etc)
244
+ if (!this.shouldLock(name)) {
245
+ log.extend('jobQueueFilling')('lock limit reached in queue filling for [%s]', name);
246
+ return;
247
+ }
248
+ // Set the date of the next time we are going to run _processEvery function
249
+ const now = new Date();
250
+ this.nextScanAt = new Date(now.valueOf() + this.processEvery);
251
+ // For this job name, find the next job to run and lock it!
252
+ const job = await this.findAndLockNextJob(name, this.agenda.definitions[name]);
253
+ // Still have the job?
254
+ // 1. Add it to lock list
255
+ // 2. Add count of locked jobs
256
+ // 3. Queue the job to actually be run now that it is locked
257
+ // 4. Recursively run this same method we are in to check for more available jobs of same type!
258
+ if (job) {
259
+ if (job.attrs.name !== name) {
260
+ throw new Error(`got different job name: ${job.attrs.name} (actual) !== ${name} (expected)`);
261
+ }
262
+ // Before en-queing job make sure we haven't exceed our lock limits
263
+ if (!this.shouldLock(name)) {
264
+ log.extend('jobQueueFilling')('lock limit reached before job was returned. Releasing lock on [%s]', name);
265
+ this.agenda.db.unlockJob(job.attrs);
266
+ return;
267
+ }
268
+ log.extend('jobQueueFilling')('[%s:%s] job locked while filling queue', name, job.attrs._id);
269
+ this.updateStatus(name, 'locked', +1);
270
+ this.lockedJobs.push(job);
271
+ this.enqueueJob(job);
272
+ await this.jobQueueFilling(name);
273
+ }
274
+ else {
275
+ log.extend('jobQueueFilling')('Cannot lock job [%s]', name);
276
+ }
277
+ }
278
+ catch (error) {
279
+ log.extend('jobQueueFilling')('[%s] job lock failed while filling queue', name, error);
280
+ this.agenda.emit('error', error);
281
+ }
282
+ finally {
283
+ this.isJobQueueFilling.delete(name);
284
+ }
285
+ }
286
+ /**
287
+ * Internal method that processes any jobs in the local queue (array)
288
+ * handledJobs keeps list of already processed jobs
289
+ * @returns {undefined}
290
+ */
291
+ async jobProcessing(handledJobs = []) {
292
+ // Ensure we have jobs
293
+ if (this.jobQueue.length === 0) {
294
+ return;
295
+ }
296
+ this.localQueueProcessing += 1;
297
+ try {
298
+ const now = new Date();
299
+ // Check if there is any job that is not blocked by concurrency
300
+ const job = this.jobQueue.returnNextConcurrencyFreeJob(this.jobStatus, handledJobs);
301
+ if (!job) {
302
+ log.extend('jobProcessing')('[%s:%s] there is no job to process');
303
+ return;
304
+ }
305
+ this.jobQueue.remove(job);
306
+ if (!(await job.isExpired())) {
307
+ // check if job has expired (and therefore probably got picked up again by another queue in the meantime)
308
+ // before it even has started to run
309
+ log.extend('jobProcessing')('[%s:%s] there is a job to process (priority = %d)', job.attrs.name, job.attrs._id, job.attrs.priority, job.gotTimerToExecute);
310
+ // If the 'nextRunAt' time is older than the current time, run the job
311
+ // Otherwise, setTimeout that gets called at the time of 'nextRunAt'
312
+ if (!job.attrs.nextRunAt || job.attrs.nextRunAt <= now) {
313
+ log.extend('jobProcessing')('[%s:%s] nextRunAt is in the past, run the job immediately', job.attrs.name, job.attrs._id);
314
+ this.runOrRetry(job);
315
+ }
316
+ else {
317
+ const runIn = job.attrs.nextRunAt.getTime() - now.getTime();
318
+ if (runIn > this.processEvery) {
319
+ // this job is not in the near future, remove it (it will be picked up later)
320
+ log.extend('runOrRetry')('[%s:%s] job is too far away, freeing it up', job.attrs.name, job.attrs._id);
321
+ let lockedJobIndex = this.lockedJobs.indexOf(job);
322
+ if (lockedJobIndex === -1) {
323
+ // lookup by id
324
+ lockedJobIndex = this.lockedJobs.findIndex(j => j.attrs._id?.toString() === job.attrs._id?.toString());
325
+ }
326
+ if (lockedJobIndex === -1) {
327
+ throw new Error(`cannot find job ${job.attrs._id} in locked jobs queue?`);
328
+ }
329
+ this.lockedJobs.splice(lockedJobIndex, 1);
330
+ this.updateStatus(job.attrs.name, 'locked', -1);
331
+ }
332
+ else {
333
+ log.extend('jobProcessing')('[%s:%s] nextRunAt is in the future, calling setTimeout(%d)', job.attrs.name, job.attrs._id, runIn);
334
+ // re add to queue (puts it at the right position in the queue)
335
+ this.jobQueue.insert(job);
336
+ // ensure every job gets a timer to run at the near future time (but also ensure this time is set only once)
337
+ if (!job.gotTimerToExecute) {
338
+ job.gotTimerToExecute = true;
339
+ setTimeout(() => {
340
+ this.jobProcessing();
341
+ }, runIn > MAX_SAFE_32BIT_INTEGER ? MAX_SAFE_32BIT_INTEGER : runIn); // check if runIn is higher than unsined 32 bit int, if so, use this time to recheck,
342
+ // because setTimeout will run in an overflow otherwise and reprocesses immediately
343
+ }
344
+ }
345
+ }
346
+ }
347
+ handledJobs.push(job.attrs._id);
348
+ if (job && this.localQueueProcessing < this.maxConcurrency) {
349
+ // additionally run again and check if there are more jobs that we can process right now (as long concurrency not reached)
350
+ setImmediate(() => this.jobProcessing(handledJobs));
351
+ }
352
+ }
353
+ finally {
354
+ this.localQueueProcessing -= 1;
355
+ }
356
+ }
357
+ /**
358
+ * Internal method that tries to run a job and if it fails, retries again!
359
+ * @returns {boolean} processed a job or not
360
+ */
361
+ async runOrRetry(job) {
362
+ if (!this.isRunning) {
363
+ // const a = new Error();
364
+ // console.log('STACK', a.stack);
365
+ log.extend('runOrRetry')('JobProcessor got stopped already while calling runOrRetry, returning!');
366
+ return;
367
+ }
368
+ const jobDefinition = this.agenda.definitions[job.attrs.name];
369
+ const status = this.jobStatus[job.attrs.name];
370
+ if ((!jobDefinition.concurrency || !status || status.running < jobDefinition.concurrency) &&
371
+ this.runningJobs.length < this.maxConcurrency) {
372
+ // Add to local "running" queue
373
+ this.runningJobs.push(job);
374
+ this.updateStatus(job.attrs.name, 'running', 1);
375
+ let jobIsRunning = true;
376
+ try {
377
+ log.extend('runOrRetry')('[%s:%s] processing job', job.attrs.name, job.attrs._id);
378
+ // check if the job is still alive
379
+ const checkIfJobIsStillAlive = async () => {
380
+ // check every "this.agenda.definitions[job.attrs.name].lockLifetime / 2"" (or at mininum every processEvery)
381
+ const timeout = Math.max(this.processEvery / 2, this.agenda.definitions[job.attrs.name].lockLifetime / 2);
382
+ do {
383
+ await delay(timeout);
384
+ // when job is not running anymore, just finish
385
+ if (!jobIsRunning) {
386
+ log.extend('runOrRetry')('[%s:%s] checkIfJobIsStillAlive detected job is not running anymore. stopping check.', job.attrs.name, job.attrs._id);
387
+ return;
388
+ }
389
+ if (await job.isExpired()) {
390
+ log.extend('runOrRetry')('[%s:%s] checkIfJobIsStillAlive detected an expired job, killing it.', job.attrs.name, job.attrs._id);
391
+ throw new Error(`execution of '${job.attrs.name}' canceled, execution took more than ${this.agenda.definitions[job.attrs.name].lockLifetime}ms. Call touch() for long running jobs to keep them alive.`);
392
+ }
393
+ if (!job.attrs.lockedAt) {
394
+ log.extend('runOrRetry')('[%s:%s] checkIfJobIsStillAlive detected a job without a lockedAt value, killing it.', job.attrs.name, job.attrs._id);
395
+ throw new Error(`execution of '${job.attrs.name}' canceled, no lockedAt date found. Ensure to call touch() for long running jobs to keep them alive.`);
396
+ }
397
+ } while (jobIsRunning);
398
+ };
399
+ // CALL THE ACTUAL METHOD TO PROCESS THE JOB!!!
400
+ await Promise.race([job.run(), checkIfJobIsStillAlive()]);
401
+ log.extend('runOrRetry')('[%s:%s] processing job successfull', job.attrs.name, job.attrs._id);
402
+ // Job isn't in running jobs so throw an error
403
+ if (!this.runningJobs.includes(job)) {
404
+ log.extend('runOrRetry')('[%s] callback was called, job must have been marked as complete already', job.attrs._id);
405
+ throw new Error(`callback already called - job ${job.attrs.name} already marked complete`);
406
+ }
407
+ }
408
+ catch (error) {
409
+ job.cancel(error instanceof Error ? error : undefined);
410
+ log.extend('runOrRetry')('[%s:%s] processing job failed', job.attrs.name, job.attrs._id, error);
411
+ this.agenda.emit('error', error instanceof Error ? error : new Error(String(error)));
412
+ }
413
+ finally {
414
+ jobIsRunning = false;
415
+ // Remove the job from the running queue
416
+ let runningJobIndex = this.runningJobs.indexOf(job);
417
+ if (runningJobIndex === -1) {
418
+ // lookup by id
419
+ runningJobIndex = this.runningJobs.findIndex(j => j.attrs._id?.toString() === job.attrs._id?.toString());
420
+ }
421
+ if (runningJobIndex === -1) {
422
+ // eslint-disable-next-line no-unsafe-finally
423
+ throw new Error(`cannot find job ${job.attrs._id} in running jobs queue?`);
424
+ }
425
+ this.runningJobs.splice(runningJobIndex, 1);
426
+ this.updateStatus(job.attrs.name, 'running', -1);
427
+ // Remove the job from the locked queue
428
+ let lockedJobIndex = this.lockedJobs.indexOf(job);
429
+ if (lockedJobIndex === -1) {
430
+ // lookup by id
431
+ lockedJobIndex = this.lockedJobs.findIndex(j => j.attrs._id?.toString() === job.attrs._id?.toString());
432
+ }
433
+ if (lockedJobIndex === -1) {
434
+ // eslint-disable-next-line no-unsafe-finally
435
+ throw new Error(`cannot find job ${job.attrs._id} in locked jobs queue?`);
436
+ }
437
+ this.lockedJobs.splice(lockedJobIndex, 1);
438
+ this.updateStatus(job.attrs.name, 'locked', -1);
439
+ }
440
+ // Re-process jobs now that one has finished
441
+ setImmediate(() => this.jobProcessing());
442
+ return;
443
+ }
444
+ // Run the job later
445
+ log.extend('runOrRetry')('[%s:%s] concurrency preventing immediate run, pushing job to top of queue', job.attrs.name, job.attrs._id);
446
+ this.enqueueJob(job);
447
+ }
448
+ updateStatus(name, key, number) {
449
+ if (!this.jobStatus[name]) {
450
+ this.jobStatus[name] = {
451
+ locked: 0,
452
+ running: 0
453
+ };
454
+ }
455
+ this.jobStatus[name][key] += number;
456
+ }
457
+ }
458
+ //# sourceMappingURL=JobProcessor.js.map