@stonyx/cron 0.2.1-alpha.14 → 0.2.1-alpha.16

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/service.d.ts CHANGED
@@ -15,7 +15,8 @@ interface ExecuteResult {
15
15
  summary?: string;
16
16
  durationMs?: number;
17
17
  deleted?: boolean;
18
- reason?: string;
18
+ /** Only set when `status` is `'skipped'`. */
19
+ reason?: 'not due' | 'already running' | 'removed';
19
20
  }
20
21
  interface ServiceStatus {
21
22
  started: boolean;
@@ -97,12 +98,18 @@ export default class CronService {
97
98
  /**
98
99
  * Phase 1 - claim. Must be called while holding the lock.
99
100
  *
100
- * Returns false if the job is already running, so a second `run()` reports
101
- * "already running" instead of launching a concurrent invocation. Detaching
102
- * from the heap here (rather than relying on phase 3 to push a fresh entry)
103
- * is what keeps manual runs from permanently duplicating heap entries.
101
+ * Returns `null` on a successful claim, or the reason the claim was refused.
102
+ * "already running" is what makes a second `run()` report a skip instead of
103
+ * launching a concurrent invocation. "removed" covers the job being deleted
104
+ * between `run()`'s unlocked lookup and this lock turn - claiming then would
105
+ * `markRunning` an orphan and, worse, `removeFromHeap` an id that may now
106
+ * belong to a replacement.
107
+ *
108
+ * Detaching from the heap here (rather than relying on phase 3 to push a
109
+ * fresh entry) is what keeps manual runs from permanently duplicating heap
110
+ * entries.
104
111
  */
105
- claimJob(job: Job): boolean;
112
+ claimJob(job: Job): 'already running' | 'removed' | null;
106
113
  /**
107
114
  * Phase 3 - settle. Must be called while holding the lock.
108
115
  */
package/dist/service.js CHANGED
@@ -268,10 +268,20 @@ export default class CronService {
268
268
  async executeJob(job, alreadyClaimed = false) {
269
269
  // -- Phase 1: claim (locked) --
270
270
  if (!alreadyClaimed) {
271
- const claimed = await locked(() => this.claimJob(job));
272
- if (!claimed)
273
- return { status: 'skipped', reason: 'already running' };
271
+ const refusal = await locked(() => this.claimJob(job));
272
+ if (refusal)
273
+ return { status: 'skipped', reason: refusal };
274
274
  }
275
+ // Membership re-check. The claim and the invoke are no longer in the same
276
+ // critical section, and sibling callbacks run unlocked, so a `remove()` can
277
+ // land in between AND RESOLVE - it used to deadlock. A resolved `remove()`
278
+ // must keep meaning "this callback will not fire": settleJob's identity
279
+ // guard only cleans up afterwards, by which point the side effect has
280
+ // already happened. Identity, not id, so a removed-then-replaced key is
281
+ // caught too. Deliberately synchronous with the `onJobDue` call below -
282
+ // nothing can interleave between this check and the invocation.
283
+ if (this.jobs.get(job.id) !== job)
284
+ return { status: 'skipped', reason: 'removed' };
275
285
  const startMs = Date.now();
276
286
  let status = 'ok';
277
287
  let error;
@@ -310,17 +320,25 @@ export default class CronService {
310
320
  /**
311
321
  * Phase 1 - claim. Must be called while holding the lock.
312
322
  *
313
- * Returns false if the job is already running, so a second `run()` reports
314
- * "already running" instead of launching a concurrent invocation. Detaching
315
- * from the heap here (rather than relying on phase 3 to push a fresh entry)
316
- * is what keeps manual runs from permanently duplicating heap entries.
323
+ * Returns `null` on a successful claim, or the reason the claim was refused.
324
+ * "already running" is what makes a second `run()` report a skip instead of
325
+ * launching a concurrent invocation. "removed" covers the job being deleted
326
+ * between `run()`'s unlocked lookup and this lock turn - claiming then would
327
+ * `markRunning` an orphan and, worse, `removeFromHeap` an id that may now
328
+ * belong to a replacement.
329
+ *
330
+ * Detaching from the heap here (rather than relying on phase 3 to push a
331
+ * fresh entry) is what keeps manual runs from permanently duplicating heap
332
+ * entries.
317
333
  */
318
334
  claimJob(job) {
335
+ if (this.jobs.get(job.id) !== job)
336
+ return 'removed';
319
337
  if (job.state.runningAtMs)
320
- return false;
338
+ return 'already running';
321
339
  markRunning(job);
322
340
  this.removeFromHeap(job.id);
323
- return true;
341
+ return null;
324
342
  }
325
343
  /**
326
344
  * Phase 3 - settle. Must be called while holding the lock.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.2.1-alpha.14",
6
+ "version": "0.2.1-alpha.16",
7
7
  "description": "Cron/job scheduler for Stonyx framework",
8
8
  "main": "dist/main.js",
9
9
  "types": "dist/main.d.ts",