multi-tasks 3.2.0 → 3.2.1

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
@@ -12,46 +12,6 @@ npm install multi-tasks
12
12
 
13
13
  Tip: monitor running tasks in real time — see [Real-Time Monitoring](#real-time-monitoring).
14
14
 
15
- ### API:
16
-
17
- The API has three parts: the entry functions, the config options, and the `helper` object injected into `processTask`.
18
-
19
- **Entry functions:**
20
-
21
- - **`multiTasks(config)`** — run tasks; auto-resumes if the task folder already exists or `initialTasks` points to one.
22
- - **`multiTasks.resume(config)`** — usually you don't need this: the simplest way to resume is to just run `multiTasks(config)` again, it auto-resumes when the task folder already exists. This method resumes an interrupted run from the task folder alone; pass a config object with `taskFolder` (the full task folder path). The whole config (including `processTask`) is restored from `task_config.json` in that folder; any field you pass overrides the snapshot, and overrides are written back into it.
23
- - **`multiTasks.retry_fails(config)`** — same as `resume`, and also retries the failed tasks.
24
- - **`multiTasks.restart(config)`** — wipe the task folder's progress and re-run from scratch; pass a config object with `taskFolder`. The config (including `processTask` and `initialTasks`) is restored from `task_config.json`; any field you pass overrides the snapshot, and overrides are written back into it. **All previous progress and results are deleted.**
25
-
26
- **Config options:**
27
-
28
- - **`initialTasks`** — array of task objects, or a folder path string to resume from.
29
- - **`processTask(task, helper)`** — required; return a value or a Promise.
30
- - **`taskRootFolder`** — the task run folder (named by `taskId`) is created under this directory.
31
- - **`taskId`** — task folder name under `taskRootFolder`.
32
- - **`numberOfWorkers`** — how many worker processes run in parallel; a number, or a percentage string of CPU cores like `"50%"` (default).
33
- - **`taskTimeout`** — optional, milliseconds; an overdue task fails with a timeout error.
34
- - **`maxTaskRetries`** — optional, max times a failed task is auto-retried.
35
- - **`progressBar`** — `true` by default: shows a live single-line progress bar in the terminal (set `false` to disable); logs (including your own `console.*` calls) always go to `<taskFolder>/.sys/run.log`, and print to the console as well only when the bar is off or stdout is not a TTY.
36
- - **`autoCloseAfterCompletion`** — set `true` to kill workers and exit the process when no tasks are left; default is `false`, i.e. workers stay alive waiting for dynamically created tasks.
37
- - **`shouldTerminate(info)`** — return `true` to terminate the whole process.
38
- - **`setSysListener(event, payload, meta)`** — optional, handle custom system events (sent via `helper.emitSys`) on the master; `meta` has `fromWorkerId` and `fromWorkerPid`.
39
- - **`onFinish(report, helper)`** — called once after all workers exit (only when `autoCloseAfterCompletion` is `true`). `report` summarizes the run: `{ taskId, taskFolder, startTimestamp, endTimestamp, cost, total, succeeded, failed, failedTasks, workers }` — `cost` is the run duration in ms (a resumed run measures only the current round); `failedTasks` lists the failed tasks' subids; `workers` is `{ count, crashed }` — the configured worker count and how many workers crashed and were replaced. `helper` lets you walk every persisted task result:
40
- - **`helper.foreachSuccResult((count, task, result) => {})`** — iterate all succeeded results (`results/succ`); `count` is a 0-based index, `task` is the original task object (`null` if its task file is missing or unparseable), `result` is the value returned by `processTask` (`undefined` when the task returned nothing).
41
- - **`helper.foreachErrorResult((count, task, err) => {})`** — iterate all failed results (`results/errors`); `err` is the persisted error object as-is (`{ex}` / `{subTask, exception}` / `{type: 'timeout', ...}` / `{type: 'worker_crash', ...}`).
42
- - Iteration follows filesystem order (not sorted). `onFinish` runs synchronously at the very end and the master process exits shortly after it returns — don't `await` inside it.
43
-
44
- **Other options:**
45
-
46
- - **`taskFolder`** — full task folder path; only used by `resume`/`retry_fails`/`restart`.
47
-
48
- **Task helper** (the `helper` object passed as the second argument of `processTask`):
49
-
50
- - **`helper.createNewTasks(tasks)`** — create new tasks dynamically while processing.
51
- - **`helper.emit(event, payload, option?)`** — broadcast an event to all workers (relayed by the master, best-effort); `option.includingMe` defaults to `true` — pass `{includingMe: false}` to exclude the sender. Payload must be JSON-serializable.
52
- - **`helper.setListener(event, handler)`** — listen for broadcast events; one handler per event per worker process (re-registering replaces it), so calling it inside `processTask` is always safe; `handler(payload, meta)` gets `meta.fromWorkerId` and `meta.fromWorkerPid`. Broadcasts are runtime-only messages — not persisted, not replayed on resume.
53
- - **`helper.emitSys(event, payload?)`** — send a system event to the master (consumed by the master itself, not relayed). Built-in event: `'TERMINATE_ALL_WORKERS'` force-kills all workers and exits the master; any other event goes to `config.setSysListener`.
54
-
55
15
  ### How to use:
56
16
 
57
17
  ```javascript
@@ -288,8 +248,49 @@ Quick start (requires multi-tasks >=3.2.0): once your multi-tasks run has starte
288
248
 
289
249
  For installation, command-line options, and more, see the [multi-tasks-monitor](https://www.npmjs.com/package/multi-tasks-monitor) page.
290
250
 
251
+ ### API:
252
+
253
+ The API has three parts: the entry functions, the config options, and the `helper` object injected into `processTask`.
254
+
255
+ **Entry functions:**
256
+
257
+ - **`multiTasks(config)`** — run tasks; auto-resumes if the task folder already exists or `initialTasks` points to one.
258
+ - **`multiTasks.resume(config)`** — usually you don't need this: the simplest way to resume is to just run `multiTasks(config)` again, it auto-resumes when the task folder already exists. This method resumes an interrupted run from the task folder alone; pass a config object with `taskFolder` (the full task folder path). The whole config (including `processTask`) is restored from `task_config.json` in that folder; any field you pass overrides the snapshot, and overrides are written back into it.
259
+ - **`multiTasks.retry_fails(config)`** — same as `resume`, and also retries the failed tasks.
260
+ - **`multiTasks.restart(config)`** — wipe the task folder's progress and re-run from scratch; pass a config object with `taskFolder`. The config (including `processTask` and `initialTasks`) is restored from `task_config.json`; any field you pass overrides the snapshot, and overrides are written back into it. **All previous progress and results are deleted.**
261
+
262
+ **Config options:**
263
+
264
+ - **`initialTasks`** — array of task objects, or a folder path string to resume from.
265
+ - **`processTask(task, helper)`** — required; return a value or a Promise.
266
+ - **`taskRootFolder`** — the task run folder (named by `taskId`) is created under this directory.
267
+ - **`taskId`** — task folder name under `taskRootFolder`.
268
+ - **`numberOfWorkers`** — how many worker processes run in parallel; a number, or a percentage string of CPU cores like `"50%"` (default).
269
+ - **`taskTimeout`** — optional, milliseconds; an overdue task fails with a timeout error.
270
+ - **`maxTaskRetries`** — optional, max times a failed task is auto-retried.
271
+ - **`progressBar`** — `true` by default: shows a live single-line progress bar in the terminal (set `false` to disable); logs (including your own `console.*` calls) always go to `<taskFolder>/.sys/run.log`, and print to the console as well only when the bar is off or stdout is not a TTY.
272
+ - **`autoCloseAfterCompletion`** — set `true` to kill workers and exit the process when no tasks are left; default is `false`, i.e. workers stay alive waiting for dynamically created tasks.
273
+ - **`shouldTerminate(info)`** — return `true` to terminate the whole process.
274
+ - **`setSysListener(event, payload, meta)`** — optional, handle custom system events (sent via `helper.emitSys`) on the master; `meta` has `fromWorkerId` and `fromWorkerPid`.
275
+ - **`onFinish(report, helper)`** — called once after all workers exit (only when `autoCloseAfterCompletion` is `true`). `report` summarizes the run: `{ taskId, taskFolder, startTimestamp, endTimestamp, cost, total, succeeded, failed, failedTasks, workers }` — `cost` is the run duration in ms (a resumed run measures only the current round); `failedTasks` lists the failed tasks' subids; `workers` is `{ count, crashed }` — the configured worker count and how many workers crashed and were replaced. `helper` lets you walk every persisted task result:
276
+ - **`helper.foreachSuccResult((count, task, result) => {})`** — iterate all succeeded results (`results/succ`); `count` is a 0-based index, `task` is the original task object (`null` if its task file is missing or unparseable), `result` is the value returned by `processTask` (`undefined` when the task returned nothing).
277
+ - **`helper.foreachErrorResult((count, task, err) => {})`** — iterate all failed results (`results/errors`); `err` is the persisted error object as-is (`{ex}` / `{subTask, exception}` / `{type: 'timeout', ...}` / `{type: 'worker_crash', ...}`).
278
+ - Iteration follows filesystem order (not sorted). `onFinish` runs synchronously at the very end and the master process exits shortly after it returns — don't `await` inside it.
279
+
280
+ **Other options:**
281
+
282
+ - **`taskFolder`** — full task folder path; only used by `resume`/`retry_fails`/`restart`.
283
+
284
+ **Task helper** (the `helper` object passed as the second argument of `processTask`):
285
+
286
+ - **`helper.createNewTasks(tasks)`** — create new tasks dynamically while processing.
287
+ - **`helper.emit(event, payload, option?)`** — broadcast an event to all workers (relayed by the master, best-effort); `option.includingMe` defaults to `true` — pass `{includingMe: false}` to exclude the sender. Payload must be JSON-serializable.
288
+ - **`helper.setListener(event, handler)`** — listen for broadcast events; one handler per event per worker process (re-registering replaces it), so calling it inside `processTask` is always safe; `handler(payload, meta)` gets `meta.fromWorkerId` and `meta.fromWorkerPid`. Broadcasts are runtime-only messages — not persisted, not replayed on resume.
289
+ - **`helper.emitSys(event, payload?)`** — send a system event to the master (consumed by the master itself, not relayed). Built-in event: `'TERMINATE_ALL_WORKERS'` force-kills all workers and exits the master; any other event goes to `config.setSysListener`.
290
+
291
291
  ### Changelog:
292
292
 
293
+ - 3.2.1 Fix retry bug: user-defined taskCount/index no longer overwritten on task retries
293
294
  - 3.2.0 Add progressBar (on by default); console output diverted to run.log; new startup banner
294
295
  - 3.1.9 Print the multi-tasks-monitor command on startup
295
296
  - 3.1.8 Small refinements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-tasks",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Multi-process task scheduling based on Node.js cluster, with crash resume and failed-task restart support",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -341,8 +341,10 @@ function subWorker(config){
341
341
  if(message.subTask){//new task
342
342
  let {config, subTask, startTimestamp, taskCount} = message;
343
343
  TaskMgr.load(config);
344
- subTask.index = taskCount;//for v1 backward
345
- subTask.taskCount = taskCount;
344
+ //用户任务自带的 index/taskCount 保留原值(重试/续跑后任务身份不变),
345
+ //未提供时才填全局派发计数器(v1 兼容)
346
+ if(typeof subTask.index === 'undefined') subTask.index = taskCount;
347
+ if(typeof subTask.taskCount === 'undefined') subTask.taskCount = taskCount;
346
348
  subTask.workerId = worker.id;
347
349
  subTask.workerPid = process.pid;
348
350
  runLog.write(`[Task Start]: "${subTask.subid}"`, `pid=${process.pid}`, getDateTimeTxt());
@@ -353,6 +355,7 @@ function subWorker(config){
353
355
  startTimestamp
354
356
  };
355
357
  runTask(config, subTask, subTaskLog).then((tasklog)=>{
358
+ if(tasklog.retried) return;//重试任务已回 new 等重跑, 未完成, 不打 [Task Finished]
356
359
  let {cost, startTimestamp, endTimestamp} = tasklog;
357
360
 
358
361
  runLog.write(`[Task Finished] "${config.masterTaskId}"."${subTask.subid}".`, getDateTimeTxt(), `cost=${cost}ms`)
@@ -422,7 +425,8 @@ function runTask(config, subTask, subTaskLog){
422
425
  resolve({
423
426
  startTimestamp,
424
427
  endTimestamp,
425
- cost
428
+ cost,
429
+ retried: true
426
430
  });
427
431
  return;
428
432
  };
@@ -434,7 +438,8 @@ function runTask(config, subTask, subTaskLog){
434
438
  resolve({
435
439
  startTimestamp,
436
440
  endTimestamp,
437
- cost
441
+ cost,
442
+ retried: false
438
443
  });
439
444
  });
440
445
  });