multi-tasks 2.0.9 → 2.1.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 +8 -4
- package/examples/example0/run-readme.js +3 -3
- package/examples/example0/run.js +7 -1
- package/examples/example0/runm.mjs +53 -0
- package/package.json +1 -1
- package/test/test_resume/step1_start_and_crash.js +1 -1
- package/test/test_resume/test.sh +1 -0
- package/workers/Multithread.js +77 -50
- package/workers/TaskMgr.js +24 -8
package/README.md
CHANGED
|
@@ -14,8 +14,7 @@ How to use:
|
|
|
14
14
|
let multiTasks = require('multi-tasks').multiTasks;
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
//Step1,
|
|
18
|
-
// provide them as an array, multi-tasks will automatically split them and execute.
|
|
17
|
+
//Step1, create your tasks that need to be executed simultaneously as an array.
|
|
19
18
|
let alltasks = [];
|
|
20
19
|
for(let i=0;i<50;i++){
|
|
21
20
|
alltasks.push({
|
|
@@ -26,9 +25,12 @@ for(let i=0;i<50;i++){
|
|
|
26
25
|
|
|
27
26
|
//Step2, provide a function to process a certain sub-task and return the result data
|
|
28
27
|
let processTask = (task, helper)=>{
|
|
29
|
-
let {taskCount} = task
|
|
28
|
+
let {taskCount} = task;//get your task data
|
|
29
|
+
|
|
30
|
+
//Run your processing logic here...
|
|
30
31
|
|
|
31
|
-
return
|
|
32
|
+
//then return the result as a plain javascript object or an async promise
|
|
33
|
+
return 'result data';
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
//Step3, run!
|
|
@@ -109,6 +111,8 @@ let processTask = (task, helper)=>{
|
|
|
109
111
|
|
|
110
112
|
Changelog:
|
|
111
113
|
|
|
114
|
+
- 2.1.1 Fixbug: recreate a new one when a worker collapsed unexpectedly.
|
|
115
|
+
- 2.1.0 Add a new output directory "log" where you can view the process time of each subtask
|
|
112
116
|
- 2.0.9 Update readme examples
|
|
113
117
|
- 2.0.8 Update readme examples
|
|
114
118
|
- 2.0.7 Update changelog
|
|
@@ -13,7 +13,7 @@ for(let i=0;i<50;i++){
|
|
|
13
13
|
let processTask = (task, helper)=>{
|
|
14
14
|
let {taskCount} = task;
|
|
15
15
|
|
|
16
|
-
return '
|
|
16
|
+
return 'result data';
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
//Step2.1, or return a promise
|
|
@@ -76,10 +76,10 @@ let processTask = (task, helper)=>{
|
|
|
76
76
|
|
|
77
77
|
//Step3, run!
|
|
78
78
|
multiTasks.start({
|
|
79
|
-
|
|
79
|
+
initialTasks: alltasks,
|
|
80
80
|
processTask,
|
|
81
81
|
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
82
|
-
taskId: 'my-task',
|
|
82
|
+
taskId: 'my-readme-task',
|
|
83
83
|
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
84
84
|
//autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
|
|
85
85
|
onFinish: (report)=>{
|
package/examples/example0/run.js
CHANGED
|
@@ -25,6 +25,12 @@ let processTask = (task, helper)=>{
|
|
|
25
25
|
if(taskCount===5) aaa = bbb;
|
|
26
26
|
|
|
27
27
|
if(taskCount===6) return 'a result which is not a promise'; //Return a non-promise result is also OK
|
|
28
|
+
|
|
29
|
+
if(taskCount===7) {return new Promise((resolve, reject)=>{//async exception
|
|
30
|
+
setTimeout(()=>{
|
|
31
|
+
a=beforeAll;
|
|
32
|
+
}, 10 * Math.random())
|
|
33
|
+
});}
|
|
28
34
|
|
|
29
35
|
//by default, you should return a promise, all returned data can be found in the results/succ folder
|
|
30
36
|
return new Promise((resolve, reject)=>{
|
|
@@ -44,7 +50,7 @@ multiTasks({
|
|
|
44
50
|
initialTasks: alltasks,
|
|
45
51
|
processTask,
|
|
46
52
|
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
47
|
-
taskId: 'my-task',
|
|
53
|
+
taskId: 'my-task' + Math.random(),
|
|
48
54
|
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
49
55
|
//autoCloseAfterCompletion: true,
|
|
50
56
|
onFinish: (report)=>{
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {multiTasks} from '../../index.js';
|
|
2
|
+
|
|
3
|
+
//Step1, If you have tasks that need to be executed simultaneously, please provide them as an array, multi-tasks will automatically split them and execute.
|
|
4
|
+
let alltasks = [];
|
|
5
|
+
for(let i=0;i<50;i++){
|
|
6
|
+
let task_props = {
|
|
7
|
+
index: i,
|
|
8
|
+
name: `task-${i}`,
|
|
9
|
+
description: `This prop is for a subtask`
|
|
10
|
+
};
|
|
11
|
+
alltasks.push(task_props);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//Step2, Provide a process function to handle a certain sub-task and return the result data
|
|
15
|
+
let processTask = (task, helper)=>{
|
|
16
|
+
//console.log('this is the subtask data you created above:', task);
|
|
17
|
+
let {taskCount} = task;
|
|
18
|
+
|
|
19
|
+
console.log(taskCount)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
//This is the demo of exceptions/errors, they will be captured and saved in the results/errors folder
|
|
23
|
+
if(taskCount===3) throw 'exception';
|
|
24
|
+
if(taskCount===4) return Promise.reject({err:'a test error'});
|
|
25
|
+
if(taskCount===5) aaa = bbb;
|
|
26
|
+
|
|
27
|
+
if(taskCount===6) return 'a result which is not a promise'; //Return a non-promise result is also OK
|
|
28
|
+
|
|
29
|
+
//by default, you should return a promise, all returned data can be found in the results/succ folder
|
|
30
|
+
return new Promise((resolve, reject)=>{
|
|
31
|
+
setTimeout(()=>{
|
|
32
|
+
if(taskCount % 2 === 0)helper.createNewTasks({//
|
|
33
|
+
msg:'a new task'
|
|
34
|
+
});
|
|
35
|
+
taskCount === 2 ? reject('a rejected error') : resolve({
|
|
36
|
+
message: 'this is a result from a demo, random data=' + Math.random()
|
|
37
|
+
});
|
|
38
|
+
}, 10 * Math.random())
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//Step3, run!
|
|
43
|
+
multiTasks({
|
|
44
|
+
initialTasks: alltasks,
|
|
45
|
+
processTask,
|
|
46
|
+
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
47
|
+
taskId: 'my-task',
|
|
48
|
+
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
49
|
+
//autoCloseAfterCompletion: true,
|
|
50
|
+
onFinish: (report)=>{
|
|
51
|
+
console.log('finish callback', report);
|
|
52
|
+
}
|
|
53
|
+
});
|
package/package.json
CHANGED
package/test/test_resume/test.sh
CHANGED
package/workers/Multithread.js
CHANGED
|
@@ -61,58 +61,68 @@ const start = (config)=>{
|
|
|
61
61
|
const AsyncQueue = require(`../utils/asyncQueue`);
|
|
62
62
|
const queue = new AsyncQueue();
|
|
63
63
|
|
|
64
|
+
let initWorker = (worker)=>{
|
|
65
|
+
WorkerMgr.addWorker(worker);
|
|
66
|
+
worker.on('message', function(message) {
|
|
67
|
+
if(message === MSG_REQUEST_NEW_TASK){
|
|
68
|
+
queue.enqueue(async ()=>{
|
|
69
|
+
return TaskMgr.updateNewTasks();
|
|
70
|
+
});//use queue to make sure no I/O conflict
|
|
71
|
+
queue.enqueue(async () => {
|
|
72
|
+
return TaskMgr.popNewTask().then((subTask)=>{
|
|
73
|
+
if(!subTask){
|
|
74
|
+
//no tasks left
|
|
75
|
+
console.log('[Master]: no tasks left!');
|
|
76
|
+
//console.log('workers', cluster.workers);
|
|
77
|
+
if(config.autoCloseAfterCompletion){
|
|
78
|
+
killWorker(worker);
|
|
79
|
+
}else{//waiting for new tasks
|
|
80
|
+
worker.send(MSG_NO_TASK_FOUND,()=>{}, {
|
|
81
|
+
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}else{
|
|
85
|
+
let terminateData = {taskCount,subTask,config, id: worker.id};
|
|
86
|
+
if(USER_CONFIG.shouldTerminate && USER_CONFIG.shouldTerminate(terminateData)===true) {
|
|
87
|
+
killAllWorkers();
|
|
88
|
+
return process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
//send new tasks to the worker
|
|
91
|
+
worker.send({
|
|
92
|
+
taskCount,
|
|
93
|
+
startTimestamp: new Date()*1,
|
|
94
|
+
config,
|
|
95
|
+
subTask
|
|
96
|
+
},()=>{}, {
|
|
97
|
+
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
98
|
+
});
|
|
99
|
+
taskCount++;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}else
|
|
104
|
+
if(message.MSG_SET_GLOBAL_PARAMS){
|
|
105
|
+
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxx', message)
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
64
110
|
let initPromise = config.__resume ? asMaster.resume(config) : asMaster.init(config);
|
|
65
111
|
initPromise.then((updated_config)=>{
|
|
66
112
|
console.log(` new tasks=${TaskMgr.getTasks('new').length}`);
|
|
67
113
|
config = updated_config;
|
|
68
114
|
for (let i = 0; i < numOfWorkers; i++) {
|
|
69
115
|
let worker = cluster.fork();
|
|
70
|
-
|
|
71
|
-
worker.on('message', function(message) {
|
|
72
|
-
if(message === MSG_REQUEST_NEW_TASK){
|
|
73
|
-
queue.enqueue(async ()=>{
|
|
74
|
-
return TaskMgr.updateNewTasks();
|
|
75
|
-
});//use queue to make sure no I/O conflict
|
|
76
|
-
queue.enqueue(async () => {
|
|
77
|
-
return TaskMgr.popNewTask().then((subTask)=>{
|
|
78
|
-
if(!subTask){
|
|
79
|
-
//no tasks left
|
|
80
|
-
console.log('[Master]: no tasks left!');
|
|
81
|
-
//console.log('workers', cluster.workers);
|
|
82
|
-
if(config.autoCloseAfterCompletion){
|
|
83
|
-
killWorker(worker);
|
|
84
|
-
}else{//waiting for new tasks
|
|
85
|
-
worker.send(MSG_NO_TASK_FOUND,()=>{}, {
|
|
86
|
-
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}else{
|
|
90
|
-
let terminateData = {taskCount,subTask,config, id: worker.id};
|
|
91
|
-
if(USER_CONFIG.shouldTerminate && USER_CONFIG.shouldTerminate(terminateData)===true) {
|
|
92
|
-
killAllWorkers();
|
|
93
|
-
return process.exit(0);
|
|
94
|
-
}
|
|
95
|
-
//send new tasks to the worker
|
|
96
|
-
worker.send({
|
|
97
|
-
taskCount,
|
|
98
|
-
startTimestamp: new Date()*1,
|
|
99
|
-
config,
|
|
100
|
-
subTask
|
|
101
|
-
},()=>{}, {
|
|
102
|
-
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
103
|
-
});
|
|
104
|
-
taskCount++;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}else
|
|
109
|
-
if(message.MSG_SET_GLOBAL_PARAMS){
|
|
110
|
-
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxx', message)
|
|
111
|
-
}
|
|
112
|
-
});
|
|
116
|
+
initWorker(worker);
|
|
113
117
|
}
|
|
114
118
|
});
|
|
115
|
-
cluster.on('exit', (worker, code, signal) => {
|
|
119
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
120
|
+
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
121
|
+
console.log('Worker died unexpectedly starting a new one...');
|
|
122
|
+
let worker = cluster.fork();
|
|
123
|
+
initWorker(worker);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
116
126
|
let allkilled = true;
|
|
117
127
|
for (const worker of Object.values(cluster.workers)) {
|
|
118
128
|
allkilled = allkilled && worker.killed;
|
|
@@ -156,9 +166,12 @@ function subWorker(){
|
|
|
156
166
|
let sendMsg = (msg)=>{
|
|
157
167
|
worker.send(msg);
|
|
158
168
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
let subTaskLog = {
|
|
170
|
+
taskCount,
|
|
171
|
+
startTimestamp
|
|
172
|
+
};
|
|
173
|
+
runTask(config, subTask, subTaskLog, sendMsg).then((tasklog)=>{
|
|
174
|
+
let {cost, startTimestamp, endTimestamp} = tasklog;
|
|
162
175
|
|
|
163
176
|
console.log(`[Task Finished] "${config.masterTaskId}"."${subTask.subid}".`, getDateTimeTxt(), `cost=${cost}ms`)
|
|
164
177
|
}).then(()=>{
|
|
@@ -173,7 +186,7 @@ function subWorker(){
|
|
|
173
186
|
worker.send(MSG_REQUEST_NEW_TASK);//request new task
|
|
174
187
|
|
|
175
188
|
};
|
|
176
|
-
function runTask(config, subTask, sendMsg){
|
|
189
|
+
function runTask(config, subTask, subTaskLog, sendMsg){
|
|
177
190
|
return new Promise((resolve)=>{
|
|
178
191
|
let resultPromise = Promise.resolve({});
|
|
179
192
|
let resultData;
|
|
@@ -185,6 +198,7 @@ function runTask(config, subTask, sendMsg){
|
|
|
185
198
|
params
|
|
186
199
|
});
|
|
187
200
|
};
|
|
201
|
+
TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
|
|
188
202
|
try{
|
|
189
203
|
resultPromise = USER_CONFIG.processTask(subTask, helper);//rewrite this method in subclass
|
|
190
204
|
}catch(e){
|
|
@@ -198,11 +212,24 @@ function runTask(config, subTask, sendMsg){
|
|
|
198
212
|
if(typeof ex !== 'string') ex = ex.toString();
|
|
199
213
|
errors.push({ex});
|
|
200
214
|
}).finally(()=>{
|
|
215
|
+
let {startTimestamp} = subTaskLog;
|
|
216
|
+
let endTimestamp = new Date()*1;
|
|
217
|
+
let cost = endTimestamp - startTimestamp;
|
|
218
|
+
subTaskLog = {
|
|
219
|
+
...subTaskLog,
|
|
220
|
+
endTimestamp,
|
|
221
|
+
cost
|
|
222
|
+
};
|
|
201
223
|
let hasError = errors.length > 0;
|
|
202
|
-
if(hasError)console.log('hasError=====', hasError, errors)
|
|
224
|
+
if(hasError)console.log('hasError=====', hasError, errors);
|
|
225
|
+
TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
|
|
203
226
|
TaskMgr.saveTaskResult(subTask.subid, hasError, hasError ? errors : resultData);
|
|
204
227
|
TaskMgr.mvTask(subTask.subid, 'running',(hasError ? 'finished_with_errors':'finished'), (newPath)=>{
|
|
205
|
-
resolve(
|
|
228
|
+
resolve({
|
|
229
|
+
startTimestamp,
|
|
230
|
+
endTimestamp,
|
|
231
|
+
cost
|
|
232
|
+
});
|
|
206
233
|
});
|
|
207
234
|
});
|
|
208
235
|
});
|
package/workers/TaskMgr.js
CHANGED
|
@@ -43,19 +43,21 @@ const TaskMgr = {
|
|
|
43
43
|
};
|
|
44
44
|
//let taskfolder = pathutil.parse(__filename).dir;
|
|
45
45
|
//let taskFolder = pathutil.resolve(taskfolder, `./tasks/${masterTaskId}`);
|
|
46
|
-
progressRootFolder = pathutil.resolve(taskFolder, './progress');
|
|
47
|
-
resultsRootFolder = pathutil.resolve(taskFolder, './results');
|
|
46
|
+
const progressRootFolder = pathutil.resolve(taskFolder, './progress');
|
|
47
|
+
const resultsRootFolder = pathutil.resolve(taskFolder, './results');
|
|
48
|
+
const logsRootFolder = pathutil.resolve(taskFolder, './logs');
|
|
48
49
|
|
|
49
|
-
taskFolderNew = pathutil.resolve(progressRootFolder, './new');
|
|
50
|
-
taskFolderRunning = pathutil.resolve(progressRootFolder, './running');
|
|
51
|
-
taskFolderFinished = pathutil.resolve(progressRootFolder, './finished');
|
|
52
|
-
taskFolderFailed= pathutil.resolve(progressRootFolder, './finished_with_errors');
|
|
50
|
+
const taskFolderNew = pathutil.resolve(progressRootFolder, './new');
|
|
51
|
+
const taskFolderRunning = pathutil.resolve(progressRootFolder, './running');
|
|
52
|
+
const taskFolderFinished = pathutil.resolve(progressRootFolder, './finished');
|
|
53
|
+
const taskFolderFailed= pathutil.resolve(progressRootFolder, './finished_with_errors');
|
|
53
54
|
|
|
54
|
-
taskFolderResult = pathutil.resolve(resultsRootFolder, './succ');
|
|
55
|
-
taskFolderResultFailed = pathutil.resolve(resultsRootFolder, './errors');
|
|
55
|
+
const taskFolderResult = pathutil.resolve(resultsRootFolder, './succ');
|
|
56
|
+
const taskFolderResultFailed = pathutil.resolve(resultsRootFolder, './errors');
|
|
56
57
|
//let mainRptFolder = pathutil.resolve(taskfolder, './report');
|
|
57
58
|
|
|
58
59
|
//makeDir.sync(mainRptFolder);
|
|
60
|
+
makeDir.sync(logsRootFolder);
|
|
59
61
|
makeDir.sync(taskFolderNew);
|
|
60
62
|
makeDir.sync(taskFolderRunning);
|
|
61
63
|
makeDir.sync(taskFolderFinished);
|
|
@@ -68,6 +70,7 @@ const TaskMgr = {
|
|
|
68
70
|
taskFolder,
|
|
69
71
|
progressRootFolder,
|
|
70
72
|
resultsRootFolder,
|
|
73
|
+
logsRootFolder,
|
|
71
74
|
|
|
72
75
|
taskFolderNew,
|
|
73
76
|
taskFolderRunning,
|
|
@@ -208,6 +211,19 @@ const TaskMgr = {
|
|
|
208
211
|
fcontent = JSON.stringify(data);
|
|
209
212
|
};
|
|
210
213
|
fs.writeFileSync(fpath, fcontent);
|
|
214
|
+
},
|
|
215
|
+
saveTaskLog:(subid, log)=>{
|
|
216
|
+
let {logsRootFolder} = main_config;
|
|
217
|
+
let folder = logsRootFolder;
|
|
218
|
+
let fpath = pathutil.resolve(folder, `${subid}.json`);
|
|
219
|
+
let fcontent;
|
|
220
|
+
log.subid = subid;
|
|
221
|
+
if(typeof log === 'undefined') {
|
|
222
|
+
fcontent = '';
|
|
223
|
+
}else{
|
|
224
|
+
fcontent = JSON.stringify(log);
|
|
225
|
+
};
|
|
226
|
+
fs.writeFileSync(fpath, fcontent);
|
|
211
227
|
}
|
|
212
228
|
};
|
|
213
229
|
module.exports = TaskMgr;
|