multi-tasks 3.2.0 → 3.2.2
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 +76 -51
- package/package.json +1 -1
- package/workers/Multithread.js +9 -4
- package/workers/index.js +65 -19
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# multi-tasks
|
|
2
2
|
|
|
3
|
-
Multi-tasks is a toolkit to manage long-term and large-scale parallel computing tasks. Progress and tasks are stored on the file system, so tasks can be resumed even if the host crashes.
|
|
4
|
-
|
|
5
|
-
**Zero dependencies:** multi-tasks has no runtime dependencies at all — it is built entirely on Node.js built-in modules (`cluster`, `fs`, `path`, `os`), so installing it adds nothing extra to your `node_modules`.
|
|
3
|
+
Multi-tasks is a toolkit to manage long-term and large-scale parallel computing tasks, with zero dependencies. Progress and tasks are stored on the file system, so tasks can be resumed even if the host crashes.
|
|
6
4
|
|
|
7
5
|
### Install:
|
|
8
6
|
|
|
@@ -12,59 +10,23 @@ npm install multi-tasks
|
|
|
12
10
|
|
|
13
11
|
Tip: monitor running tasks in real time — see [Real-Time Monitoring](#real-time-monitoring).
|
|
14
12
|
|
|
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
13
|
### How to use:
|
|
56
14
|
|
|
57
15
|
```javascript
|
|
58
16
|
//see examples/example0
|
|
59
17
|
let multiTasks = require('multi-tasks').multiTasks;
|
|
60
18
|
|
|
61
|
-
//Step1,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
alltasks
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
19
|
+
//Step1, provide the tasks as a function returning them (or a Promise of them);
|
|
20
|
+
//it runs only once, in the master process.
|
|
21
|
+
let initialTasks = async ()=>{
|
|
22
|
+
let alltasks = [];
|
|
23
|
+
for(let i=0;i<50;i++){
|
|
24
|
+
alltasks.push({
|
|
25
|
+
name: `task-${i}`,
|
|
26
|
+
data: `This prop is for a subtask`
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
return alltasks;
|
|
68
30
|
};
|
|
69
31
|
|
|
70
32
|
//Step2, provide a function that processes each sub-task and returns the result
|
|
@@ -79,7 +41,7 @@ let processTask = (task, helper)=>{
|
|
|
79
41
|
|
|
80
42
|
//Step3, run!
|
|
81
43
|
multiTasks({
|
|
82
|
-
initialTasks
|
|
44
|
+
initialTasks, //accepts array, function, or function returning a promise
|
|
83
45
|
processTask,
|
|
84
46
|
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and result files; you can check the progress here
|
|
85
47
|
taskId: 'my-task',
|
|
@@ -215,6 +177,27 @@ multiTasks({
|
|
|
215
177
|
|
|
216
178
|
```
|
|
217
179
|
|
|
180
|
+
### Performance: runs only once, in the master process
|
|
181
|
+
|
|
182
|
+
Every worker process re-runs your entire entry script — so top-level task-generation code (building a huge array, scanning folders, querying a database, ...) runs once per worker. Wrap it in a function for performance: it is called **only once, in the master process**, and workers never call it. `initialTasks` accepts three forms — pick one:
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
|
|
186
|
+
//Example8, the three forms of initialTasks
|
|
187
|
+
|
|
188
|
+
//form 1: a plain array — assign it directly
|
|
189
|
+
multiTasks({ initialTasks: alltasks, processTask, taskRootFolder, taskId });
|
|
190
|
+
|
|
191
|
+
//form 2: a function returning the array
|
|
192
|
+
multiTasks({ initialTasks: () => fetchTasksFromSomewhere(), processTask, taskRootFolder, taskId });
|
|
193
|
+
|
|
194
|
+
//form 3: a function returning a Promise of the array
|
|
195
|
+
multiTasks({ initialTasks: async () => await fetchTasksFromDb(), processTask, taskRootFolder, taskId });
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The (resolved) value must be an array, otherwise the master exits with an error. The resulting array — not the function — is stored in `task_config.json`, so the self-contained-function note under Resuming does not apply to it.
|
|
200
|
+
|
|
218
201
|
### Resuming
|
|
219
202
|
|
|
220
203
|
If the execution is interrupted (e.g. a power outage), the simplest way to resume is to just re-run the same code — `multiTasks(config)` detects the existing task folder and auto-resumes, re-running only the stuck tasks:
|
|
@@ -288,8 +271,50 @@ Quick start (requires multi-tasks >=3.2.0): once your multi-tasks run has starte
|
|
|
288
271
|
|
|
289
272
|
For installation, command-line options, and more, see the [multi-tasks-monitor](https://www.npmjs.com/package/multi-tasks-monitor) page.
|
|
290
273
|
|
|
274
|
+
### API:
|
|
275
|
+
|
|
276
|
+
The API has three parts: the entry functions, the config options, and the `helper` object injected into `processTask`.
|
|
277
|
+
|
|
278
|
+
**Entry functions:**
|
|
279
|
+
|
|
280
|
+
- **`multiTasks(config)`** — run tasks; auto-resumes if the task folder already exists or `initialTasks` points to one.
|
|
281
|
+
- **`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.
|
|
282
|
+
- **`multiTasks.retry_fails(config)`** — same as `resume`, and also retries the failed tasks.
|
|
283
|
+
- **`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.**
|
|
284
|
+
|
|
285
|
+
**Config options:**
|
|
286
|
+
|
|
287
|
+
- **`initialTasks`** — array of task objects, a function returning the array (or a Promise of it — called only once, in the master process; worker processes never call it; see [Performance: runs only once, in the master process](#performance-runs-only-once-in-the-master-process)), or a folder path string to resume from.
|
|
288
|
+
- **`processTask(task, helper)`** — required; return a value or a Promise.
|
|
289
|
+
- **`taskRootFolder`** — the task run folder (named by `taskId`) is created under this directory.
|
|
290
|
+
- **`taskId`** — task folder name under `taskRootFolder`.
|
|
291
|
+
- **`numberOfWorkers`** — how many worker processes run in parallel; a number, or a percentage string of CPU cores like `"50%"` (default).
|
|
292
|
+
- **`taskTimeout`** — optional, milliseconds; an overdue task fails with a timeout error.
|
|
293
|
+
- **`maxTaskRetries`** — optional, max times a failed task is auto-retried.
|
|
294
|
+
- **`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.
|
|
295
|
+
- **`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.
|
|
296
|
+
- **`shouldTerminate(info)`** — return `true` to terminate the whole process.
|
|
297
|
+
- **`setSysListener(event, payload, meta)`** — optional, handle custom system events (sent via `helper.emitSys`) on the master; `meta` has `fromWorkerId` and `fromWorkerPid`.
|
|
298
|
+
- **`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:
|
|
299
|
+
- **`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).
|
|
300
|
+
- **`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', ...}`).
|
|
301
|
+
- 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.
|
|
302
|
+
|
|
303
|
+
**Other options:**
|
|
304
|
+
|
|
305
|
+
- **`taskFolder`** — full task folder path; only used by `resume`/`retry_fails`/`restart`.
|
|
306
|
+
|
|
307
|
+
**Task helper** (the `helper` object passed as the second argument of `processTask`):
|
|
308
|
+
|
|
309
|
+
- **`helper.createNewTasks(tasks)`** — create new tasks dynamically while processing.
|
|
310
|
+
- **`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.
|
|
311
|
+
- **`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.
|
|
312
|
+
- **`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`.
|
|
313
|
+
|
|
291
314
|
### Changelog:
|
|
292
315
|
|
|
316
|
+
- 3.2.2 initialTasks accepts a function (sync or async) that is called only once, in the master process
|
|
317
|
+
- 3.2.1 Fix retry bug: user-defined taskCount/index no longer overwritten on task retries
|
|
293
318
|
- 3.2.0 Add progressBar (on by default); console output diverted to run.log; new startup banner
|
|
294
319
|
- 3.1.9 Print the multi-tasks-monitor command on startup
|
|
295
320
|
- 3.1.8 Small refinements
|
package/package.json
CHANGED
package/workers/Multithread.js
CHANGED
|
@@ -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
|
-
|
|
345
|
-
|
|
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
|
});
|
package/workers/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const pathUtil = require('path');
|
|
3
|
+
const cluster = require('cluster');
|
|
3
4
|
const Multithread = require('./Multithread');
|
|
4
5
|
const backwardv1 = require('../utils/backward-v1');
|
|
5
6
|
const { FUNCTION_CONFIG_KEYS, reviveConfig } = require('../utils/serializeFns');
|
|
@@ -52,19 +53,58 @@ const _computeSysLogFile = (config)=>{
|
|
|
52
53
|
};
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
//initialTasks 支持函数形式(惰性生成): 只在 master 进程求值一次, worker 进程重跑入口脚本时跳过,
|
|
57
|
+
//防生成逻辑(扫目录/查库等初始化脚本)在每个 worker 里各执行一遍。
|
|
58
|
+
//函数可同步返回数组, 也可返回 Promise(resolve 值同样必须是数组)——同步路径保持同步 throw,
|
|
59
|
+
//Promise 路径返回 then 链交给 _proceedAfterEval 等待。求值在入口统一完成(先于 _mergeSnapshotConfig
|
|
60
|
+
//与 Multithread.start): 求值后的数组才会参与合并并写入 task_config.json(函数本身会被
|
|
61
|
+
//JSON.stringify 丢弃, 不进存档、不参与 revive)。
|
|
62
|
+
const _evalInitialTasks = (config)=>{
|
|
63
|
+
if(typeof config.initialTasks !== 'function') return null;
|
|
64
|
+
if(!(cluster.isPrimary || cluster.isMaster)) return null;
|
|
65
|
+
let tasks = config.initialTasks();
|
|
66
|
+
let applyTasks = (resolved)=>{
|
|
67
|
+
if(!Array.isArray(resolved)){
|
|
68
|
+
throw new Error('FATAL: initialTasks function should return an array!');
|
|
69
|
+
};
|
|
70
|
+
config.initialTasks = resolved;
|
|
71
|
+
};
|
|
72
|
+
if(tasks && typeof tasks.then === 'function'){//Promise 形式: 异步求值, resolve 后同样校验数组(假设真 Promise, 即 .then 返回 Promise; 非标准 thenable 不支持)
|
|
73
|
+
return tasks.then(applyTasks);
|
|
74
|
+
};
|
|
75
|
+
applyTasks(tasks);
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//同步路径(pending 为 null)直接执行 proceed, 保持既有同步 throw 语义;
|
|
80
|
+
//Promise 路径等 resolve 后执行, resolve 非数组/reject/proceed 抛错时打 FATAL 并 exit(1)
|
|
81
|
+
//(与 init 异步失败一致; Promise 路径只发生在 master, 无同步 throw 对象)。
|
|
82
|
+
const _proceedAfterEval = (pending, proceed)=>{
|
|
83
|
+
if(!pending){
|
|
84
|
+
proceed();
|
|
85
|
+
return;
|
|
86
|
+
};
|
|
87
|
+
return pending.then(proceed).catch((e)=>{
|
|
88
|
+
console.error('[FATAL] failed to evaluate initialTasks:', e);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
55
93
|
const start = (config)=>{
|
|
56
94
|
config = backwardv1.v1tov2(config);
|
|
95
|
+
return _proceedAfterEval(_evalInitialTasks(config), ()=>{
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
config.taskRootFolder = _toAbsPath(config.taskRootFolder);
|
|
98
|
+
config.taskFolder = _toAbsPath(config.taskFolder);
|
|
99
|
+
_computeSysLogFile(config);
|
|
61
100
|
|
|
62
|
-
|
|
101
|
+
Multithread.start(config);
|
|
102
|
+
});
|
|
63
103
|
};
|
|
64
104
|
const resume = (config)=>{
|
|
65
105
|
config = _normalizeResumeArg('resume', config);
|
|
66
|
-
|
|
67
|
-
|
|
106
|
+
//先校验/推导 taskFolder 再求值 initialTasks(与 retry_fails/restart 一致):
|
|
107
|
+
//缺目录时同步 throw fail fast, 不让可能昂贵的生成逻辑白跑一遍
|
|
68
108
|
config.taskRootFolder = _toAbsPath(config.taskRootFolder);
|
|
69
109
|
config.taskFolder = _toAbsPath(config.taskFolder);
|
|
70
110
|
if(!config.taskFolder){
|
|
@@ -75,10 +115,12 @@ const resume = (config)=>{
|
|
|
75
115
|
};
|
|
76
116
|
config.taskRootFolder = pathUtil.dirname(config.taskFolder);
|
|
77
117
|
config.taskId = pathUtil.basename(config.taskFolder);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
config.__resume = true;
|
|
119
|
+
return _proceedAfterEval(_evalInitialTasks(config), ()=>{
|
|
120
|
+
_computeSysLogFile(config);
|
|
121
|
+
config = _mergeSnapshotConfig(config);
|
|
122
|
+
Multithread.start(config);
|
|
123
|
+
});
|
|
82
124
|
};
|
|
83
125
|
const retry_fails = (config)=>{
|
|
84
126
|
config = _normalizeResumeArg('retry_fails', config);
|
|
@@ -91,9 +133,11 @@ const retry_fails = (config)=>{
|
|
|
91
133
|
config.taskId = pathUtil.basename(taskFolder);
|
|
92
134
|
config.__resume = true;
|
|
93
135
|
config.__retryFails = true;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
136
|
+
return _proceedAfterEval(_evalInitialTasks(config), ()=>{
|
|
137
|
+
_computeSysLogFile(config);
|
|
138
|
+
config = _mergeSnapshotConfig(config);
|
|
139
|
+
Multithread.start(config);
|
|
140
|
+
});
|
|
97
141
|
};
|
|
98
142
|
const restart = (config)=>{
|
|
99
143
|
config = _normalizeResumeArg('restart', config);
|
|
@@ -105,9 +149,11 @@ const restart = (config)=>{
|
|
|
105
149
|
config.taskRootFolder = pathUtil.dirname(taskFolder);
|
|
106
150
|
config.taskId = pathUtil.basename(taskFolder);
|
|
107
151
|
config.__restart = true;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
return _proceedAfterEval(_evalInitialTasks(config), ()=>{
|
|
153
|
+
_computeSysLogFile(config);
|
|
154
|
+
config = _mergeSnapshotConfig(config);
|
|
155
|
+
Multithread.start(config);
|
|
156
|
+
});
|
|
111
157
|
};
|
|
112
158
|
|
|
113
159
|
const multiTasks = (config)=>{
|
|
@@ -126,7 +172,7 @@ const multiTasks = (config)=>{
|
|
|
126
172
|
config.taskRootFolder = pathUtil.dirname(folder);
|
|
127
173
|
}
|
|
128
174
|
}
|
|
129
|
-
if(!isResume) throw new Error('FATAL: initialTasks should be an array or a task root folder!');
|
|
175
|
+
if(!isResume) throw new Error('FATAL: initialTasks should be an array, a function or a task root folder!');
|
|
130
176
|
}else{
|
|
131
177
|
let taskFolder = pathUtil.resolve(taskRootFolder, taskId);
|
|
132
178
|
if(fs.existsSync(taskFolder)){
|
|
@@ -135,9 +181,9 @@ const multiTasks = (config)=>{
|
|
|
135
181
|
}
|
|
136
182
|
|
|
137
183
|
if(isResume){
|
|
138
|
-
resume(config);
|
|
184
|
+
return resume(config);
|
|
139
185
|
}else{
|
|
140
|
-
start(config);
|
|
186
|
+
return start(config);
|
|
141
187
|
}
|
|
142
188
|
|
|
143
189
|
};
|