@genroc/eval-node 0.21.0 → 0.23.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.
package/README.md CHANGED
@@ -89,7 +89,7 @@ The worker connects to genroc, not the other way round, so it only needs outboun
89
89
  |---|---|---|
90
90
  | `GENROC_SERVER` | `http://localhost:8448` | the genroc server |
91
91
  | `GENROC_TOKEN` / `GENROC_TOKEN_FILE` | *(none)* | token, or a file holding it, when the server uses `--auth token` |
92
- | `PROCESS` / `TASK` | `script-node` / `eval_node` | claim only this process / task id; the defaults are script-node's. Set `PROCESS` for a renamed copy, or `""` to drop a filter; **with both `""`, the worker claims every external task.** |
92
+ | `PROCESS` / `TASK` | `script-node` / `eval_node` | the process and task id this worker claims; the defaults are script-node's. Set `PROCESS` for a renamed copy. Neither may be empty. |
93
93
  | `CONCURRENCY` | `4` | scripts run at once |
94
94
  | `LEASE_MS` | `30000` | claim lease, renewed every third of it |
95
95
  | `POLL_MS` | `250` | idle poll interval |
package/dist/worker.js CHANGED
@@ -22,10 +22,14 @@ const RENEW_MS = Math.max(1_000, Math.floor(LEASE_MS / 3));
22
22
  // The answer has to land before the task's deadline fires, or a script that overran surfaces as
23
23
  // external.timeout ("nobody answered") rather than as its own `timeout`.
24
24
  const ANSWER_MARGIN_MS = 250;
25
- // script-node's names. A renamed copy needs PROCESS set; an explicit "" drops that filter, and
26
- // both "" claims every external task, other runtimes' included.
25
+ // script-node's names; a renamed copy sets PROCESS. The claim requires both: this worker answers
26
+ // one task's contract, so it must never be handed another's.
27
27
  const PROCESS_FILTER = process.env.PROCESS ?? "script-node";
28
28
  const TASK_FILTER = process.env.TASK ?? "eval_node";
29
+ if (!PROCESS_FILTER || !TASK_FILTER) {
30
+ console.error("PROCESS and TASK must both name the queue this worker serves; neither may be empty.");
31
+ process.exit(1);
32
+ }
29
33
  // Large values arrive as refs; a bundle is one object per definition version, fetched once.
30
34
  // Refs are content hashes, so the cache never invalidates.
31
35
  const objectCache = new Map();
@@ -117,8 +121,8 @@ async function claim(n) {
117
121
  worker_id: WORKER_ID,
118
122
  limit: n,
119
123
  lease_ms: LEASE_MS,
120
- ...(PROCESS_FILTER ? { process: PROCESS_FILTER } : {}),
121
- ...(TASK_FILTER ? { task: TASK_FILTER } : {}),
124
+ process: PROCESS_FILTER,
125
+ task: TASK_FILTER,
122
126
  });
123
127
  if (!ok) {
124
128
  // A credential problem is not transient, and polling through it looks like a healthy idle
@@ -162,9 +166,9 @@ async function answer(token, outcome) {
162
166
  await release(token);
163
167
  }
164
168
  async function run(job, signal) {
165
- // The task's own timeout is the script's budget. Taken on arrival, because fetching the
166
- // objects below spends the same deadline.
167
- const due = job.deadline_in_ms === undefined ? undefined : performance.now() + job.deadline_in_ms;
169
+ // The task's own timeout is the script's budget, moved onto the monotonic clock so a wall-clock
170
+ // step mid-run cannot stretch it. Fetching the objects below spends the same deadline.
171
+ const due = job.deadline === undefined ? undefined : performance.now() + (Date.parse(job.deadline) - Date.now());
168
172
  let resolved;
169
173
  try {
170
174
  resolved = await resolveObjects(job);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genroc/eval-node",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
package/worker.ts CHANGED
@@ -25,10 +25,14 @@ const RENEW_MS = Math.max(1_000, Math.floor(LEASE_MS / 3));
25
25
  // The answer has to land before the task's deadline fires, or a script that overran surfaces as
26
26
  // external.timeout ("nobody answered") rather than as its own `timeout`.
27
27
  const ANSWER_MARGIN_MS = 250;
28
- // script-node's names. A renamed copy needs PROCESS set; an explicit "" drops that filter, and
29
- // both "" claims every external task, other runtimes' included.
28
+ // script-node's names; a renamed copy sets PROCESS. The claim requires both: this worker answers
29
+ // one task's contract, so it must never be handed another's.
30
30
  const PROCESS_FILTER = process.env.PROCESS ?? "script-node";
31
31
  const TASK_FILTER = process.env.TASK ?? "eval_node";
32
+ if (!PROCESS_FILTER || !TASK_FILTER) {
33
+ console.error("PROCESS and TASK must both name the queue this worker serves; neither may be empty.");
34
+ process.exit(1);
35
+ }
32
36
 
33
37
  type ObjectEntry = { path: (string | number)[]; ref: string; size: number };
34
38
 
@@ -38,8 +42,7 @@ type QueueTask = {
38
42
  task: string;
39
43
  external_input: unknown;
40
44
  objects?: ObjectEntry[];
41
- raises?: Record<string, unknown>;
42
- deadline_in_ms?: number;
45
+ deadline?: string;
43
46
  };
44
47
 
45
48
  // Large values arrive as refs; a bundle is one object per definition version, fetched once.
@@ -135,8 +138,8 @@ async function claim(n: number): Promise<QueueTask[]> {
135
138
  worker_id: WORKER_ID,
136
139
  limit: n,
137
140
  lease_ms: LEASE_MS,
138
- ...(PROCESS_FILTER ? { process: PROCESS_FILTER } : {}),
139
- ...(TASK_FILTER ? { task: TASK_FILTER } : {}),
141
+ process: PROCESS_FILTER,
142
+ task: TASK_FILTER,
140
143
  });
141
144
  if (!ok) {
142
145
  // A credential problem is not transient, and polling through it looks like a healthy idle
@@ -185,9 +188,9 @@ async function answer(token: string, outcome: Record<string, unknown>): Promise<
185
188
  }
186
189
 
187
190
  async function run(job: QueueTask, signal: AbortSignal): Promise<void> {
188
- // The task's own timeout is the script's budget. Taken on arrival, because fetching the
189
- // objects below spends the same deadline.
190
- const due = job.deadline_in_ms === undefined ? undefined : performance.now() + job.deadline_in_ms;
191
+ // The task's own timeout is the script's budget, moved onto the monotonic clock so a wall-clock
192
+ // step mid-run cannot stretch it. Fetching the objects below spends the same deadline.
193
+ const due = job.deadline === undefined ? undefined : performance.now() + (Date.parse(job.deadline) - Date.now());
191
194
  let resolved: unknown;
192
195
  try {
193
196
  resolved = await resolveObjects(job);