multi-tasks 3.0.1 → 3.0.3

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
@@ -1,160 +1,201 @@
1
- # multi-tasks
2
-
3
- Multi-tasks is a toolkit to manage long-term and large-scale parallel computing tasks. It manages progress and tasks based on the file system, which means that even if the host crashes, tasks can be resumed based on file records.
4
-
5
- ### Install:
6
-
7
- ```javascript
8
- npm install multi-tasks
9
- ```
10
-
11
- ### How to use:
12
-
13
- ```javascript
14
- //see examples/example0
15
- let multiTasks = require('multi-tasks').multiTasks;
16
-
17
- //Step1, create your tasks that need to be executed simultaneously as an array.
18
- let alltasks = [];
19
- for(let i=0;i<50;i++){
20
- alltasks.push({
21
- name: `task-${i}`,
22
- data: `This prop is for a subtask`
23
- });
24
- };
25
-
26
- //Step2, provide a function to process a certain sub-task and return the result data
27
- let processTask = (task, helper)=>{
28
- let {taskCount} = task;//get your task data
29
-
30
- //Run your processing logic here...
31
-
32
- //then return the result as a plain javascript object or an async promise
33
- return 'result data';
34
- };
35
-
36
- //Step3, run!
37
- multiTasks({
38
- initialTasks: alltasks,
39
- processTask,
40
- taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
41
- taskId: 'my-task',
42
- numberOfWorkers: 3, //how many workers are working in parallel
43
- //autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
44
- shouldTerminate:(info)=>{
45
- //return true if you need to terminate the whole process
46
- },
47
- onFinish: (report)=>{
48
- console.log('finish callback', report);
49
- }
50
- });
51
-
52
- ```
53
-
54
- ### More about the task processing function
55
-
56
- ```javascript
57
- //Example1, return a promise for async processes,
58
- // you can return a promise or a non-promise result,
59
- // all result data can be found in the results/succ folder
60
- let processTask = (task, helper)=>{
61
- let {taskCount} = task;
62
-
63
- return new Promise((resolve, reject)=>{
64
- resolve({
65
- data:`task${taskCount} complete`
66
- })
67
- })
68
- };
69
-
70
- //Example2, dynamically create a new task while processing
71
- let processTask = (task, helper)=>{
72
- let {taskCount} = task;
73
-
74
- if(taskCount % 2 === 0){
75
- //create a new task if needed
76
- helper.createNewTasks({
77
- msg:'a new task'
78
- });
79
- return;
80
- }
81
-
82
- return new Promise((resolve, reject)=>{
83
- resolve({
84
- data:`task${taskCount} complete`
85
- })
86
- })
87
- };
88
-
89
- //Example3, generate/throw exceptions in a task method
90
- let processTask = (task, helper)=>{
91
- let {taskCount} = task;
92
-
93
- //This is the demo of exceptions/errors, they will be captured and saved in the results/errors folder
94
- if(taskCount===3) throw 'exception';
95
- if(taskCount===4) return Promise.reject({err:'a test error'});//use Promise.reject method
96
- if(taskCount===5) aaa = bbb;//this undefined exception will be captured by multi-tasks
97
-
98
- return {data:'succ'};
99
- }
100
-
101
-
102
- ```
103
-
104
- ### Resuming
105
-
106
- Sometimes the task execution is interrupted due to some reasons (such as power outage), you can resume the execution like this
107
-
108
- ```javascript
109
-
110
- multiTasks({
111
- initialTasks: `/myworks/my_scan_tasks/`, //Point 'initialTasks' to the interrupted task directory, multi-tasks will read the tasks in the 'new' folder and initialize them to 'initialTasks' and then continue execution
112
- ...
113
- ...//Other configurations remain unchanged
114
- ...
115
- });
116
-
117
- ```
118
-
119
- ### Testing:
120
-
121
- The usage patterns documented above are covered by automated tests (unit tests in `test/`, end-to-end tests in `teste2e/`). The e2e suite runs the API in real child processes with a mixed workload — successful tasks (both promise and non-promise results), planned failures (rejected promises and thrown exceptions), dynamically created tasks via `helper.createNewTasks`, and workers that crash randomly — then resumes repeatedly and verifies that every task lands exactly one result in `results/succ` or `results/errors`.
122
-
123
- ### Changelog:
124
-
125
- - 3.0.1 Versions 3.0.0 and above are maintained by AI
126
- - 2.2.0 Support Resuming from an interrupted task
127
- - 2.1.2 Update readme
128
- - 2.1.1 Fix: recreate a new one when a worker collapsed unexpectedly.
129
- - 2.1.0 Add a new output directory "log" where you can view the process time of each subtask
130
- - 2.0.9 Update readme examples
131
- - 2.0.8 Update readme examples
132
- - 2.0.7 Update changelog
133
- - 2.0.6 Update readme, remove failed examples
134
- - 2.0.5 Update readme examples
135
- - 2.0.4 Support resume from a failed task
136
- - 2.0.3 Fix: mkdir bug on windows
137
- - 2.0.2 new feature: support shouldTerminate
138
- - 2.0.1 Avoid possible I/O conflicts.
139
- - 2.0.0 Rewritten with a new architecture to support dynamic tasks.
140
- - 1.2.8 Fix: create task folder failed on MacOS
141
- - 1.2.7 Small updates
142
- - 1.2.6 Support onFinish event
143
- - 1.2.5 Rename numberOfWorks to numberOfWorkers, the old one are still supported ;-)
144
- - 1.2.4 Fix: opt.numberOfWorkers not work
145
- - 1.2.3 Update README
146
- - 1.2.2 Update README and examples
147
- - 1.2.1 Handle exceptions and errors in subtasks
148
- - 1.2.0 Simplified usage by providing the function way and support return Promise
149
- - 1.1.4 Remove make-dir
150
- - 1.1.3 Simplified usage, see example0
151
- - 1.1.2
152
- - 1.1.1 Rename files, updated changelog
153
- - 1.1.0 Simplified the usage of a customized Consumer, see example0
154
- - 1.0.8 Fix examples
155
- - 1.0.7 Remove moment
156
- - 1.0.6 Performance optimization
157
-
158
- ### Gitee:
159
-
160
- [https://gitee.com/zhanglei923/multi-tasks](https://gitee.com/zhanglei923/multi-tasks)
1
+ # multi-tasks
2
+
3
+ Multi-tasks is a toolkit to manage long-term and large-scale parallel computing tasks. It manages progress and tasks based on the file system, which means that even if the host crashes, tasks can be resumed based on file records.
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`.
6
+
7
+ ### Install:
8
+
9
+ ```javascript
10
+ npm install multi-tasks
11
+ ```
12
+
13
+ ### API:
14
+
15
+ - `multiTasks(config)` run tasks; auto-resumes if the task folder already exists or `initialTasks` points to one.
16
+ - `multiTasks.start(config)` — always start a fresh run.
17
+ - `multiTasks.resume(config)` resume an interrupted run.
18
+ - `multiTasks.restart(config)` resume and also retry failed tasks (needs `config.taskFolder`).
19
+ - `helper.createNewTasks(tasks)` — create new tasks dynamically while processing.
20
+ - Config options:
21
+ - `initialTasks` — array of task objects, or a folder path string to resume from.
22
+ - `processTask(task, helper)` required; return a value or a Promise.
23
+ - `taskRootFolder` — root directory where progress and result files are stored.
24
+ - `taskId` — task folder name under `taskRootFolder`.
25
+ - `taskFolder` — full task folder path, required by `restart`.
26
+ - `numberOfWorkers` how many worker processes run in parallel.
27
+ - `taskTimeout` optional, milliseconds; an overdue task fails with a timeout error.
28
+ - `maxTaskRetries` optional, max times a failed task is auto-retried (sent back to the queue immediately); covers worker crashes, exceptions/rejections from `processTask`, and `taskTimeout` timeouts; the retry count is kept in the task file as `__retryCount` — it survives `resume` and is reset by `restart`.
29
+ - `autoCloseAfterCompletion` — set `false` if you create new tasks dynamically.
30
+ - `shouldTerminate(info)` return `true` to terminate the whole process.
31
+ - `onFinish(report)` — called once after all workers are done.
32
+
33
+ ### How to use:
34
+
35
+ ```javascript
36
+ //see examples/example0
37
+ let multiTasks = require('multi-tasks').multiTasks;
38
+
39
+ //Step1, create your tasks that need to be executed simultaneously as an array.
40
+ let alltasks = [];
41
+ for(let i=0;i<50;i++){
42
+ alltasks.push({
43
+ name: `task-${i}`,
44
+ data: `This prop is for a subtask`
45
+ });
46
+ };
47
+
48
+ //Step2, provide a function to process a certain sub-task and return the result data
49
+ let processTask = (task, helper)=>{
50
+ let {taskCount} = task;//get your task data
51
+
52
+ //Run your processing logic here...
53
+
54
+ //then return the result as a plain javascript object or an async promise
55
+ return 'result data';
56
+ };
57
+
58
+ //Step3, run!
59
+ multiTasks({
60
+ initialTasks: alltasks,
61
+ processTask,
62
+ taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
63
+ taskId: 'my-task',
64
+ numberOfWorkers: 3, //how many workers are working in parallel
65
+ //taskTimeout: 30000, //optional, in milliseconds, an overdue task is treated as failed with a timeout error
66
+ //maxTaskRetries: 2, //optional, auto-retry a failed task (worker crash, processTask error, or timeout); retried tasks go back to the queue
67
+ //autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
68
+ shouldTerminate:(info)=>{
69
+ //return true if you need to terminate the whole process
70
+ },
71
+ onFinish: (report)=>{
72
+ console.log('finish callback', report);
73
+ }
74
+ });
75
+
76
+ ```
77
+
78
+ ### More about the task processing function
79
+
80
+ ```javascript
81
+ //Example1, return a promise for async processes,
82
+ // you can return a promise or a non-promise result,
83
+ // all result data can be found in the results/succ folder
84
+ let processTask = (task, helper)=>{
85
+ let {taskCount} = task;
86
+
87
+ return new Promise((resolve, reject)=>{
88
+ resolve({
89
+ data:`task${taskCount} complete`
90
+ })
91
+ })
92
+ };
93
+
94
+ //Example2, dynamically create a new task while processing
95
+ let processTask = (task, helper)=>{
96
+ let {taskCount} = task;
97
+
98
+ if(taskCount % 2 === 0){
99
+ //create a new task if needed
100
+ helper.createNewTasks({
101
+ msg:'a new task'
102
+ });
103
+ return;
104
+ }
105
+
106
+ return new Promise((resolve, reject)=>{
107
+ resolve({
108
+ data:`task${taskCount} complete`
109
+ })
110
+ })
111
+ };
112
+
113
+ //Example3, generate/throw exceptions in a task method
114
+ let processTask = (task, helper)=>{
115
+ let {taskCount} = task;
116
+
117
+ //This is the demo of exceptions/errors, they will be captured and saved in the results/errors folder
118
+ if(taskCount===3) throw 'exception';
119
+ if(taskCount===4) return Promise.reject({err:'a test error'});//use Promise.reject method
120
+ if(taskCount===5) aaa = bbb;//this undefined exception will be captured by multi-tasks
121
+
122
+ return {data:'succ'};
123
+ }
124
+
125
+ //Example5, auto-retry a failed task:
126
+ // when a task fails - its worker process crashes, processTask throws
127
+ // or rejects, or it exceeds taskTimeout - it is sent back to the
128
+ // queue and retried, at most maxTaskRetries times; when the limit
129
+ // is reached it lands in finished_with_errors (crash:
130
+ // {type:'worker_crash'}, timeout: {type:'timeout'}, processTask
131
+ // error: the original error data)
132
+ multiTasks({
133
+ initialTasks: alltasks,
134
+ processTask,
135
+ taskRootFolder: `../examples-tmp-data/example-retry`,
136
+ taskId: 'my-task',
137
+ numberOfWorkers: 3,
138
+ maxTaskRetries: 2,
139
+ });
140
+
141
+ ```
142
+
143
+ ### Resuming
144
+
145
+ Sometimes the task execution is interrupted due to some reasons (such as power outage), you can resume the execution like this
146
+
147
+ ```javascript
148
+
149
+ multiTasks({
150
+ initialTasks: `/myworks/my_scan_tasks/`, //Point 'initialTasks' to the interrupted task directory, multi-tasks will read the tasks in the 'new' folder and initialize them to 'initialTasks' and then continue execution
151
+ ...
152
+ ...//Other configurations remain unchanged
153
+ ...
154
+ });
155
+
156
+ ```
157
+
158
+ ### Testing:
159
+
160
+ The usage patterns documented above are covered by automated tests (unit tests in `test/`, end-to-end tests in `teste2e/`). The e2e suite runs the API in real child processes with a mixed workload — successful tasks (both promise and non-promise results), planned failures (rejected promises and thrown exceptions), dynamically created tasks via `helper.createNewTasks`, and workers that crash randomly — then resumes repeatedly and verifies that every task lands exactly one result in `results/succ` or `results/errors`.
161
+
162
+ ### Changelog:
163
+
164
+ - 3.0.3 Support 'maxTaskRetries'
165
+ - 3.0.2 Support 'taskTimeout'
166
+ - 3.0.1 Versions 3.0.0 and above are maintained by AI
167
+ - 2.2.0 Support Resuming from an interrupted task
168
+ - 2.1.2 Update readme
169
+ - 2.1.1 Fix: recreate a new one when a worker collapsed unexpectedly.
170
+ - 2.1.0 Add a new output directory "log" where you can view the process time of each subtask
171
+ - 2.0.9 Update readme examples
172
+ - 2.0.8 Update readme examples
173
+ - 2.0.7 Update changelog
174
+ - 2.0.6 Update readme, remove failed examples
175
+ - 2.0.5 Update readme examples
176
+ - 2.0.4 Support resume from a failed task
177
+ - 2.0.3 Fix: mkdir bug on windows
178
+ - 2.0.2 new feature: support shouldTerminate
179
+ - 2.0.1 Avoid possible I/O conflicts.
180
+ - 2.0.0 Rewritten with a new architecture to support dynamic tasks.
181
+ - 1.2.8 Fix: create task folder failed on MacOS
182
+ - 1.2.7 Small updates
183
+ - 1.2.6 Support onFinish event
184
+ - 1.2.5 Rename numberOfWorks to numberOfWorkers, the old one are still supported ;-)
185
+ - 1.2.4 Fix: opt.numberOfWorkers not work
186
+ - 1.2.3 Update README
187
+ - 1.2.2 Update README and examples
188
+ - 1.2.1 Handle exceptions and errors in subtasks
189
+ - 1.2.0 Simplified usage by providing the function way and support return Promise
190
+ - 1.1.4 Remove make-dir
191
+ - 1.1.3 Simplified usage, see example0
192
+ - 1.1.2
193
+ - 1.1.1 Rename files, updated changelog
194
+ - 1.1.0 Simplified the usage of a customized Consumer, see example0
195
+ - 1.0.8 Fix examples
196
+ - 1.0.7 Remove moment
197
+ - 1.0.6 Performance optimization
198
+
199
+ ### Gitee:
200
+
201
+ [https://gitee.com/zhanglei923/multi-tasks](https://gitee.com/zhanglei923/multi-tasks)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-tasks",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
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": [
@@ -27,6 +27,8 @@ let USER_CONFIG;
27
27
 
28
28
  let taskCount = 0;
29
29
 
30
+ let workerTaskTimers = {};//worker.id -> {timer, subTask, startTimestamp}, master 端任务超时倒计时
31
+
30
32
  let killAllWorkers = ()=>{
31
33
  for (const id in cluster.workers) {
32
34
  if (cluster.workers.hasOwnProperty(id)) {
@@ -56,10 +58,70 @@ const start = (config)=>{
56
58
  const AsyncQueue = require(`../utils/asyncQueue`);
57
59
  const queue = new AsyncQueue();
58
60
 
61
+ let clearTaskTimer = (workerId)=>{
62
+ let rec = workerTaskTimers[workerId];
63
+ if(rec){
64
+ clearTimeout(rec.timer);
65
+ delete workerTaskTimers[workerId];
66
+ }
67
+ };
68
+ let finalizeTimeoutTask = (subTask, cost)=>{
69
+ let subid = subTask.subid;
70
+ try{
71
+ let resultType = TaskMgr.getSavedResultType(subid);
72
+ if(resultType === 'succ'){//超时边界: worker 已成功写盘, 视为已完成
73
+ TaskMgr.mvTask(subid, 'running', 'finished', ()=>{});
74
+ }else if(resultType === 'error'){//超时边界: worker 已自行记错误
75
+ TaskMgr.mvTask(subid, 'running', 'finished_with_errors', ()=>{});
76
+ }else{
77
+ if(typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0){
78
+ let retryCount = TaskMgr.incrementTaskRetry(subid);
79
+ if(retryCount <= config.maxTaskRetries){
80
+ console.warn(`[Master]: task "${subid}" timeout, retry (${retryCount}/${config.maxTaskRetries})`);
81
+ TaskMgr.mvTask(subid, 'running', 'new', ()=>{});
82
+ return;
83
+ };
84
+ };
85
+ TaskMgr.saveTaskResult(subid, true, [{type: 'timeout', timeout: config.taskTimeout, cost}]);
86
+ TaskMgr.mvTask(subid, 'running', 'finished_with_errors', ()=>{});
87
+ }
88
+ }catch(e){
89
+ console.warn('[Master]: failed to finalize timeout task', subid, e);
90
+ }
91
+ };
92
+ let onTaskTimeout = (worker)=>{
93
+ let rec = workerTaskTimers[worker.id];
94
+ if(!rec) return;
95
+ delete workerTaskTimers[worker.id];
96
+ let {subTask, startTimestamp} = rec;
97
+ let cost = new Date()*1 - startTimestamp;
98
+ console.warn(`[Master]: task "${subTask.subid}" timeout after ${cost}ms, killing worker pid=${worker.process.pid}`);
99
+ worker.kill();//不 disconnect: exit 事件会按崩溃补员
100
+ finalizeTimeoutTask(subTask, cost);
101
+ };
102
+ let handleCrashedTask = (subTask)=>{
103
+ if(!(typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0)) return;
104
+ let subid = subTask.subid;
105
+ try{
106
+ let retryCount = TaskMgr.incrementTaskRetry(subid);
107
+ if(retryCount <= config.maxTaskRetries){
108
+ console.warn(`[Master]: worker crashed, retry task "${subid}" (${retryCount}/${config.maxTaskRetries})`);
109
+ TaskMgr.mvTask(subid, 'running', 'new', ()=>{});
110
+ }else{
111
+ console.warn(`[Master]: task "${subid}" crashed ${retryCount} times, give up`);
112
+ TaskMgr.saveTaskResult(subid, true, [{type: 'worker_crash', retryCount: config.maxTaskRetries}]);
113
+ TaskMgr.mvTask(subid, 'running', 'finished_with_errors', ()=>{});
114
+ };
115
+ }catch(e){
116
+ console.warn('[Master]: failed to handle crashed task', subid, e);
117
+ }
118
+ };
119
+
59
120
  let initWorker = (worker)=>{
60
121
  WorkerMgr.addWorker(worker);
61
122
  worker.on('message', function(message) {
62
123
  if(message === MSG_REQUEST_NEW_TASK){
124
+ clearTaskTimer(worker.id);//上一个任务已结束(无论成败), 撤销超时倒计时
63
125
  queue.enqueue(async ()=>{
64
126
  return TaskMgr.updateNewTasks();
65
127
  });//use queue to make sure no I/O conflict
@@ -82,14 +144,24 @@ const start = (config)=>{
82
144
  return process.exit(0);
83
145
  }
84
146
  //send new tasks to the worker
147
+ let startTimestamp = new Date()*1;
85
148
  worker.send({
86
149
  taskCount,
87
- startTimestamp: new Date()*1,
150
+ startTimestamp,
88
151
  config,
89
152
  subTask
90
153
  },()=>{}, {
91
154
  keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
92
155
  });
156
+ let hasTimeout = typeof config.taskTimeout === 'number' && config.taskTimeout > 0;
157
+ let hasRetry = typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0;
158
+ if(hasTimeout || hasRetry){
159
+ workerTaskTimers[worker.id] = {
160
+ timer: hasTimeout ? setTimeout(()=>{ onTaskTimeout(worker); }, config.taskTimeout) : null,
161
+ subTask,
162
+ startTimestamp
163
+ };
164
+ }
93
165
  taskCount++;
94
166
  }
95
167
  });
@@ -107,9 +179,14 @@ const start = (config)=>{
107
179
  initWorker(worker);
108
180
  }
109
181
  });
110
- cluster.on('exit', (worker, code, signal) => {
182
+ cluster.on('exit', (worker, code, signal) => {
183
+ let rec = workerTaskTimers[worker.id];//先取再清: 崩溃重试要用 rec.subTask
184
+ clearTaskTimer(worker.id);//worker 退出(含崩溃)时清掉其超时计时器
111
185
  if (code !== 0 && !worker.exitedAfterDisconnect) {
112
186
  console.log('Worker died unexpectedly starting a new one...');
187
+ if(rec && rec.subTask){
188
+ handleCrashedTask(rec.subTask);
189
+ };
113
190
  let worker = cluster.fork();
114
191
  initWorker(worker);
115
192
  return;
@@ -206,7 +283,27 @@ function runTask(config, subTask, subTaskLog){
206
283
  };
207
284
  let hasError = errors.length > 0;
208
285
  if(hasError)console.warn('[Task Error]', subTask.subid, errors);
286
+ let retried = false;
287
+ if(hasError && typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0){
288
+ let retryCount = TaskMgr.incrementTaskRetry(subTask.subid);
289
+ if(retryCount <= config.maxTaskRetries){
290
+ console.warn(`[Task Retry] "${subTask.subid}" failed, retry (${retryCount}/${config.maxTaskRetries})`);
291
+ TaskMgr.mvTask(subTask.subid, 'running', 'new', ()=>{});
292
+ retried = true;
293
+ };
294
+ };
209
295
  TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
296
+ if(retried){
297
+ resolve({
298
+ startTimestamp,
299
+ endTimestamp,
300
+ cost
301
+ });
302
+ return;
303
+ };
304
+ if(!hasError){
305
+ TaskMgr.deleteSavedResult(subTask.subid);//清掉前序尝试可能残留的(超时竞争)错误结果
306
+ };
210
307
  TaskMgr.saveTaskResult(subTask.subid, hasError, hasError ? errors : resultData);
211
308
  TaskMgr.mvTask(subTask.subid, 'running',(hasError ? 'finished_with_errors':'finished'), (newPath)=>{
212
309
  resolve({
@@ -205,6 +205,14 @@ const TaskMgr = {
205
205
  };
206
206
  fs.writeFileSync(fpath, fcontent);
207
207
  },
208
+ getSavedResultType:(subid)=>{
209
+ let {taskFolderResult, taskFolderResultFailed} = main_config;
210
+ let succPath = pathutil.resolve(taskFolderResult, `${subid}.json`);
211
+ let errPath = pathutil.resolve(taskFolderResultFailed, `${subid}.json`);
212
+ if(fs.existsSync(succPath)) return 'succ';
213
+ if(fs.existsSync(errPath)) return 'error';
214
+ return null;
215
+ },
208
216
  saveTaskLog:(subid, log)=>{
209
217
  let {logsRootFolder} = main_config;
210
218
  let folder = logsRootFolder;
@@ -217,6 +225,30 @@ const TaskMgr = {
217
225
  fcontent = JSON.stringify(log);
218
226
  };
219
227
  fs.writeFileSync(fpath, fcontent);
220
- }
228
+ },
229
+ incrementTaskRetry:(subid)=>{
230
+ let {taskFolderRunning} = main_config;
231
+ let fpath = pathutil.resolve(taskFolderRunning, `./${subid}.task.json`);
232
+ let task = JSON.parse(fs.readFileSync(fpath, 'utf-8'));
233
+ task.__retryCount = (task.__retryCount || 0) + 1;
234
+ fs.writeFileSync(fpath, JSON.stringify(task));
235
+ return task.__retryCount;
236
+ },
237
+ deleteSavedResult:(subid)=>{
238
+ let {taskFolderResult, taskFolderResultFailed} = main_config;
239
+ [taskFolderResult, taskFolderResultFailed].forEach((folder)=>{
240
+ let fpath = pathutil.resolve(folder, `${subid}.json`);
241
+ if(fs.existsSync(fpath)){
242
+ fs.unlinkSync(fpath);
243
+ }
244
+ });
245
+ },
246
+ resetTaskRetry:(subid)=>{
247
+ let {taskFolderNew} = main_config;
248
+ let fpath = pathutil.resolve(taskFolderNew, `./${subid}.task.json`);
249
+ let task = JSON.parse(fs.readFileSync(fpath, 'utf-8'));
250
+ delete task.__retryCount;
251
+ fs.writeFileSync(fpath, JSON.stringify(task));
252
+ },
221
253
  };
222
254
  module.exports = TaskMgr;
@@ -32,7 +32,9 @@ const resume = (config)=>{
32
32
  if(config.__restart){
33
33
  errfiles.forEach((fname)=>{
34
34
  let subid = fname.replace(/\.task\.json$/, '');
35
- promises.push(TaskMgr.mvTaskPromise(subid, 'finished_with_errors', 'new'));
35
+ promises.push(TaskMgr.mvTaskPromise(subid, 'finished_with_errors', 'new').then(()=>{
36
+ TaskMgr.resetTaskRetry(subid);//restart 重跑失败任务时重置重试计数(resume 不重置)
37
+ }));
36
38
  });
37
39
  };
38
40
  Promise.all(promises).then(()=>{