multi-tasks 3.0.1 → 3.0.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 +4 -0
- package/package.json +1 -1
- package/workers/Multithread.js +47 -1
- package/workers/TaskMgr.js +8 -0
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
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
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
|
+
|
|
5
7
|
### Install:
|
|
6
8
|
|
|
7
9
|
```javascript
|
|
@@ -40,6 +42,7 @@ multiTasks({
|
|
|
40
42
|
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
41
43
|
taskId: 'my-task',
|
|
42
44
|
numberOfWorkers: 3, //how many workers are working in parallel
|
|
45
|
+
//taskTimeout: 30000, //optional, in milliseconds, an overdue task is treated as failed with a timeout error
|
|
43
46
|
//autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
|
|
44
47
|
shouldTerminate:(info)=>{
|
|
45
48
|
//return true if you need to terminate the whole process
|
|
@@ -122,6 +125,7 @@ The usage patterns documented above are covered by automated tests (unit tests i
|
|
|
122
125
|
|
|
123
126
|
### Changelog:
|
|
124
127
|
|
|
128
|
+
- 3.0.2 Support taskTimeout: a task that does not finish in time is treated as failed with a timeout error
|
|
125
129
|
- 3.0.1 Versions 3.0.0 and above are maintained by AI
|
|
126
130
|
- 2.2.0 Support Resuming from an interrupted task
|
|
127
131
|
- 2.1.2 Update readme
|
package/package.json
CHANGED
package/workers/Multithread.js
CHANGED
|
@@ -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,45 @@ 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
|
+
TaskMgr.saveTaskResult(subid, true, [{type: '超时', timeout: config.taskTimeout, cost}]);
|
|
78
|
+
TaskMgr.mvTask(subid, 'running', 'finished_with_errors', ()=>{});
|
|
79
|
+
}
|
|
80
|
+
}catch(e){
|
|
81
|
+
console.warn('[Master]: failed to finalize timeout task', subid, e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
let onTaskTimeout = (worker)=>{
|
|
85
|
+
let rec = workerTaskTimers[worker.id];
|
|
86
|
+
if(!rec) return;
|
|
87
|
+
delete workerTaskTimers[worker.id];
|
|
88
|
+
let {subTask, startTimestamp} = rec;
|
|
89
|
+
let cost = new Date()*1 - startTimestamp;
|
|
90
|
+
console.warn(`[Master]: task "${subTask.subid}" timeout after ${cost}ms, killing worker pid=${worker.process.pid}`);
|
|
91
|
+
worker.kill();//不 disconnect: exit 事件会按崩溃补员
|
|
92
|
+
finalizeTimeoutTask(subTask, cost);
|
|
93
|
+
};
|
|
94
|
+
|
|
59
95
|
let initWorker = (worker)=>{
|
|
60
96
|
WorkerMgr.addWorker(worker);
|
|
61
97
|
worker.on('message', function(message) {
|
|
62
98
|
if(message === MSG_REQUEST_NEW_TASK){
|
|
99
|
+
clearTaskTimer(worker.id);//上一个任务已结束(无论成败), 撤销超时倒计时
|
|
63
100
|
queue.enqueue(async ()=>{
|
|
64
101
|
return TaskMgr.updateNewTasks();
|
|
65
102
|
});//use queue to make sure no I/O conflict
|
|
@@ -82,14 +119,22 @@ const start = (config)=>{
|
|
|
82
119
|
return process.exit(0);
|
|
83
120
|
}
|
|
84
121
|
//send new tasks to the worker
|
|
122
|
+
let startTimestamp = new Date()*1;
|
|
85
123
|
worker.send({
|
|
86
124
|
taskCount,
|
|
87
|
-
startTimestamp
|
|
125
|
+
startTimestamp,
|
|
88
126
|
config,
|
|
89
127
|
subTask
|
|
90
128
|
},()=>{}, {
|
|
91
129
|
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
92
130
|
});
|
|
131
|
+
if(typeof config.taskTimeout === 'number' && config.taskTimeout > 0){
|
|
132
|
+
workerTaskTimers[worker.id] = {
|
|
133
|
+
timer: setTimeout(()=>{ onTaskTimeout(worker); }, config.taskTimeout),
|
|
134
|
+
subTask,
|
|
135
|
+
startTimestamp
|
|
136
|
+
};
|
|
137
|
+
}
|
|
93
138
|
taskCount++;
|
|
94
139
|
}
|
|
95
140
|
});
|
|
@@ -108,6 +153,7 @@ const start = (config)=>{
|
|
|
108
153
|
}
|
|
109
154
|
});
|
|
110
155
|
cluster.on('exit', (worker, code, signal) => {
|
|
156
|
+
clearTaskTimer(worker.id);//worker 退出(含崩溃)时清掉其超时计时器
|
|
111
157
|
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
112
158
|
console.log('Worker died unexpectedly starting a new one...');
|
|
113
159
|
let worker = cluster.fork();
|
package/workers/TaskMgr.js
CHANGED
|
@@ -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;
|