@stonyx/cron 0.2.1-alpha.24 → 0.2.1-alpha.26

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
@@ -71,6 +71,25 @@ export default class CronService {
71
71
  remove(id: string): Promise<void>;
72
72
  /**
73
73
  * Manually trigger a job.
74
+ *
75
+ * Returns `{ status: 'skipped', reason }` without invoking the callback when
76
+ * the job is not due (`mode: 'due'`), is already in flight
77
+ * (`'already running'`), or was removed before the claim landed
78
+ * (`'removed'`). Before the phase split a forced run against an in-flight job
79
+ * launched a second concurrent invocation; refusing it is AC4 of #34.
80
+ *
81
+ * CONCURRENCY: the same job is bounded to one in-flight invocation on every
82
+ * path, and the timer path invokes due jobs one at a time. `run()` fan-out
83
+ * across DIFFERENT jobs is deliberately unbounded - N concurrent `run()`
84
+ * calls produce N concurrent consumer callbacks. Before the phase split
85
+ * these serialized behind the module-global lock; that serialization was the
86
+ * bug, not the feature (one hung callback wedged every other caller), so it
87
+ * is not being restored here. The fan-out is caller-driven: it is bounded by
88
+ * how many times the consumer chooses to call `run()`, exactly like any other
89
+ * async API, and the scheduler never produces it on its own. A consumer that
90
+ * exposes `run()` over HTTP or a CLI owns that bound the same way it owns
91
+ * request concurrency for every other handler. A per-invoke bound inside the
92
+ * service is tracked separately (#35).
74
93
  */
75
94
  run(id: string, mode?: 'due' | 'force'): Promise<ExecuteResult>;
76
95
  /**
@@ -101,7 +120,8 @@ export default class CronService {
101
120
  */
102
121
  executeJob(job: Job): Promise<ExecuteResult>;
103
122
  /**
104
- * Phase 1 - claim. Must be called while holding the lock.
123
+ * Phase 1 - claim. Must be called while holding the lock (`locked()`, whose
124
+ * chain is module-global and therefore shared across CronService instances).
105
125
  *
106
126
  * Returns `null` on a successful claim, or the reason the claim was refused.
107
127
  * "already running" is what makes a second `run()` report a skip instead of
package/dist/service.js CHANGED
@@ -154,6 +154,25 @@ export default class CronService {
154
154
  }
155
155
  /**
156
156
  * Manually trigger a job.
157
+ *
158
+ * Returns `{ status: 'skipped', reason }` without invoking the callback when
159
+ * the job is not due (`mode: 'due'`), is already in flight
160
+ * (`'already running'`), or was removed before the claim landed
161
+ * (`'removed'`). Before the phase split a forced run against an in-flight job
162
+ * launched a second concurrent invocation; refusing it is AC4 of #34.
163
+ *
164
+ * CONCURRENCY: the same job is bounded to one in-flight invocation on every
165
+ * path, and the timer path invokes due jobs one at a time. `run()` fan-out
166
+ * across DIFFERENT jobs is deliberately unbounded - N concurrent `run()`
167
+ * calls produce N concurrent consumer callbacks. Before the phase split
168
+ * these serialized behind the module-global lock; that serialization was the
169
+ * bug, not the feature (one hung callback wedged every other caller), so it
170
+ * is not being restored here. The fan-out is caller-driven: it is bounded by
171
+ * how many times the consumer chooses to call `run()`, exactly like any other
172
+ * async API, and the scheduler never produces it on its own. A consumer that
173
+ * exposes `run()` over HTTP or a CLI owns that bound the same way it owns
174
+ * request concurrency for every other handler. A per-invoke bound inside the
175
+ * service is tracked separately (#35).
157
176
  */
158
177
  async run(id, mode = 'force') {
159
178
  const job = this.jobs.get(id);
@@ -292,6 +311,12 @@ export default class CronService {
292
311
  // already happened. Identity, not id, so a removed-then-replaced key is
293
312
  // caught too. Deliberately synchronous with the `onJobDue` call below -
294
313
  // nothing can interleave between this check and the invocation.
314
+ //
315
+ // Returning here without a settle is NOT the claim-without-settle hazard
316
+ // the try/finally below exists for: the job is already out of `this.jobs`,
317
+ // so the object holding `runningAtMs` is unreachable, its heap entry was
318
+ // removed by `remove()`, and re-inserting or run-logging it is exactly the
319
+ // resurrection `settleJob`'s identity guard refuses.
295
320
  if (this.jobs.get(job.id) !== job)
296
321
  return { status: 'skipped', reason: 'removed' };
297
322
  const startMs = Date.now();
@@ -330,7 +355,8 @@ export default class CronService {
330
355
  return settled;
331
356
  }
332
357
  /**
333
- * Phase 1 - claim. Must be called while holding the lock.
358
+ * Phase 1 - claim. Must be called while holding the lock (`locked()`, whose
359
+ * chain is module-global and therefore shared across CronService instances).
334
360
  *
335
361
  * Returns `null` on a successful claim, or the reason the claim was refused.
336
362
  * "already running" is what makes a second `run()` report a skip instead of
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.2.1-alpha.24",
6
+ "version": "0.2.1-alpha.26",
7
7
  "description": "Cron/job scheduler for Stonyx framework",
8
8
  "main": "dist/main.js",
9
9
  "types": "dist/main.d.ts",