multi-tasks 2.2.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 +11 -6
- package/index.js +1 -1
- package/package.json +17 -4
- package/utils/asyncQueue.js +18 -17
- package/utils/backward-v1.js +2 -1
- package/utils/makedir.js +1 -7
- package/workers/Multithread.js +79 -53
- package/workers/TaskMgr.js +26 -25
- package/workers/WorkerMgr.js +4 -10
- package/workers/asMaster.js +30 -49
- package/workers/helper.js +3 -6
- package/workers/index.js +10 -3
- package/cmd.js +0 -42
- package/examples/example0/run-readme.js +0 -88
- package/examples/example0/run.js +0 -59
- package/examples/example0/runm.mjs +0 -53
- package/monitors/cli.js +0 -28
- package/test/splitter.test.js +0 -17
- package/test/test_resume/step1_start_and_crash.js +0 -56
- package/test/test_resume/step2_resume.js +0 -22
- package/test/test_resume/test.sh +0 -5
- package/todo.txt +0 -4
- package/utils/test/test_asyncqueue.js +0 -116
- package/utils/test_makedir.js +0 -3
- package/workers/asWorker.js +0 -14
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
|
|
@@ -116,8 +119,14 @@ multiTasks({
|
|
|
116
119
|
|
|
117
120
|
```
|
|
118
121
|
|
|
122
|
+
### Testing:
|
|
123
|
+
|
|
124
|
+
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`.
|
|
125
|
+
|
|
119
126
|
### Changelog:
|
|
120
127
|
|
|
128
|
+
- 3.0.2 Support taskTimeout: a task that does not finish in time is treated as failed with a timeout error
|
|
129
|
+
- 3.0.1 Versions 3.0.0 and above are maintained by AI
|
|
121
130
|
- 2.2.0 Support Resuming from an interrupted task
|
|
122
131
|
- 2.1.2 Update readme
|
|
123
132
|
- 2.1.1 Fix: recreate a new one when a worker collapsed unexpectedly.
|
|
@@ -150,10 +159,6 @@ multiTasks({
|
|
|
150
159
|
- 1.0.7 Remove moment
|
|
151
160
|
- 1.0.6 Performance optimization
|
|
152
161
|
|
|
153
|
-
###
|
|
154
|
-
|
|
155
|
-
[https://github.com/zhanglei923/multi-tasks](https://github.com/zhanglei923/multi-tasks)
|
|
156
|
-
|
|
157
|
-
### Support / Bug Report:
|
|
162
|
+
### Gitee:
|
|
158
163
|
|
|
159
|
-
[
|
|
164
|
+
[https://gitee.com/zhanglei923/multi-tasks](https://gitee.com/zhanglei923/multi-tasks)
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multi-tasks",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "3.0.2",
|
|
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
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"workers",
|
|
9
|
+
"utils/*.js"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://gitee.com/zhanglei923/multi-tasks.git"
|
|
14
|
+
},
|
|
6
15
|
"directories": {
|
|
7
|
-
"example": "
|
|
16
|
+
"example": "examples"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=14"
|
|
8
20
|
},
|
|
9
21
|
"scripts": {
|
|
10
|
-
"test": "jest"
|
|
22
|
+
"test": "jest",
|
|
23
|
+
"prepublishOnly": "npm test"
|
|
11
24
|
},
|
|
12
25
|
"keywords": [
|
|
13
26
|
"multitask",
|
package/utils/asyncQueue.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
class AsyncQueue {
|
|
2
2
|
constructor() {
|
|
3
3
|
this.queue = [];
|
|
4
|
-
this.isProcessing = false;
|
|
4
|
+
this.isProcessing = false;
|
|
5
|
+
this.processing = null;
|
|
5
6
|
}
|
|
6
7
|
enqueue(task) {
|
|
7
8
|
this.queue.push(task);
|
|
8
9
|
|
|
9
10
|
if (!this.isProcessing) {
|
|
10
|
-
this.processNext();
|
|
11
|
-
}
|
|
11
|
+
this.processing = this.processNext();
|
|
12
|
+
}
|
|
13
|
+
return this.processing;
|
|
12
14
|
}
|
|
13
15
|
async processNext() {
|
|
14
16
|
this.isProcessing = true;
|
|
@@ -16,20 +18,19 @@ class AsyncQueue {
|
|
|
16
18
|
this.isProcessing = false;
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
|
-
const task = this.queue.shift();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.processNext();
|
|
21
|
+
const task = this.queue.shift();
|
|
22
|
+
try {
|
|
23
|
+
const result = task();
|
|
24
|
+
let isPromise = result instanceof Promise || (result && typeof result.then === 'function');
|
|
25
|
+
if(!isPromise) {
|
|
26
|
+
console.error('[ERR]Queue functions must return a promise object');
|
|
27
|
+
} else {
|
|
28
|
+
await result;
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error processing task:', error);
|
|
32
|
+
}
|
|
33
|
+
await this.processNext();
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
package/utils/backward-v1.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
function v1tov2(config) {
|
|
2
|
-
if(config.multi_task_parent_folder) config.taskRootFolder = config.multi_task_parent_folder;//backward compatible
|
|
2
|
+
if(config.multi_task_parent_folder && !config.taskRootFolder) config.taskRootFolder = config.multi_task_parent_folder;//backward compatible
|
|
3
3
|
delete config.multi_task_parent_folder;
|
|
4
4
|
|
|
5
5
|
if(config.numberOfWorks && !config.numberOfWorkers) config.numberOfWorkers = config.numberOfWorks; //Backward compatible with the wrong variable name "numberOfWorks"
|
|
6
|
+
delete config.numberOfWorks;
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
return config;
|
package/utils/makedir.js
CHANGED
|
@@ -4,11 +4,6 @@ const path = require('path');
|
|
|
4
4
|
function sync(directoryPath) {
|
|
5
5
|
directoryPath = directoryPath.replace(/\\/g, '/');
|
|
6
6
|
|
|
7
|
-
let isAbsPath = false;
|
|
8
|
-
if(directoryPath.startsWith('/')){
|
|
9
|
-
isAbsPath = true;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
7
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
13
8
|
}
|
|
14
9
|
function getDirectFiles(dir, callback) {
|
|
@@ -23,8 +18,7 @@ function getDirectFiles(dir, callback) {
|
|
|
23
18
|
return callback([]);
|
|
24
19
|
}
|
|
25
20
|
files.forEach(file => {
|
|
26
|
-
const filePath = path.join(dir, file);
|
|
27
|
-
//console.log(filePath)
|
|
21
|
+
const filePath = path.join(dir, file);
|
|
28
22
|
try{
|
|
29
23
|
let stats = fs.statSync(filePath);
|
|
30
24
|
if (stats.isFile()) {
|
package/workers/Multithread.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
var fs = require('fs');
|
|
2
|
-
var pathutil = require('path');
|
|
3
1
|
const cluster = require('cluster');
|
|
4
2
|
const numCPUs = require('os').cpus().length;
|
|
5
3
|
|
|
6
4
|
let MSG_REQUEST_NEW_TASK = 'request_new_task';
|
|
7
5
|
let MSG_NO_TASK_FOUND = 'no_task_found';
|
|
8
|
-
let MSG_SET_GLOBAL_PARAMS = 'MSG_SET_GLOBAL_PARAMS';
|
|
9
6
|
|
|
10
7
|
const asMaster = require('./asMaster');
|
|
11
8
|
const TaskMgr = require('./TaskMgr');
|
|
@@ -23,7 +20,6 @@ let getDateTimeTxt = ()=>{
|
|
|
23
20
|
let HH=fmtdigit(d.getHours());
|
|
24
21
|
let mm=fmtdigit(d.getMinutes());
|
|
25
22
|
let ss=fmtdigit(d.getSeconds());
|
|
26
|
-
//return moment().format('YYYY-MM-DD HH:mm:ss');
|
|
27
23
|
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
|
|
28
24
|
};
|
|
29
25
|
|
|
@@ -31,6 +27,8 @@ let USER_CONFIG;
|
|
|
31
27
|
|
|
32
28
|
let taskCount = 0;
|
|
33
29
|
|
|
30
|
+
let workerTaskTimers = {};//worker.id -> {timer, subTask, startTimestamp}, master 端任务超时倒计时
|
|
31
|
+
|
|
34
32
|
let killAllWorkers = ()=>{
|
|
35
33
|
for (const id in cluster.workers) {
|
|
36
34
|
if (cluster.workers.hasOwnProperty(id)) {
|
|
@@ -41,8 +39,7 @@ let killAllWorkers = ()=>{
|
|
|
41
39
|
}
|
|
42
40
|
};
|
|
43
41
|
let killWorker = (worker)=>{
|
|
44
|
-
worker.disconnect();
|
|
45
|
-
//worker.process.kill();
|
|
42
|
+
worker.disconnect();
|
|
46
43
|
worker.kill();
|
|
47
44
|
};
|
|
48
45
|
|
|
@@ -61,10 +58,45 @@ const start = (config)=>{
|
|
|
61
58
|
const AsyncQueue = require(`../utils/asyncQueue`);
|
|
62
59
|
const queue = new AsyncQueue();
|
|
63
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
|
+
|
|
64
95
|
let initWorker = (worker)=>{
|
|
65
96
|
WorkerMgr.addWorker(worker);
|
|
66
97
|
worker.on('message', function(message) {
|
|
67
98
|
if(message === MSG_REQUEST_NEW_TASK){
|
|
99
|
+
clearTaskTimer(worker.id);//上一个任务已结束(无论成败), 撤销超时倒计时
|
|
68
100
|
queue.enqueue(async ()=>{
|
|
69
101
|
return TaskMgr.updateNewTasks();
|
|
70
102
|
});//use queue to make sure no I/O conflict
|
|
@@ -73,7 +105,6 @@ const start = (config)=>{
|
|
|
73
105
|
if(!subTask){
|
|
74
106
|
//no tasks left
|
|
75
107
|
console.log('[Master]: no tasks left!');
|
|
76
|
-
//console.log('workers', cluster.workers);
|
|
77
108
|
if(config.autoCloseAfterCompletion){
|
|
78
109
|
killWorker(worker);
|
|
79
110
|
}else{//waiting for new tasks
|
|
@@ -88,21 +119,26 @@ const start = (config)=>{
|
|
|
88
119
|
return process.exit(0);
|
|
89
120
|
}
|
|
90
121
|
//send new tasks to the worker
|
|
122
|
+
let startTimestamp = new Date()*1;
|
|
91
123
|
worker.send({
|
|
92
124
|
taskCount,
|
|
93
|
-
startTimestamp
|
|
125
|
+
startTimestamp,
|
|
94
126
|
config,
|
|
95
127
|
subTask
|
|
96
128
|
},()=>{}, {
|
|
97
129
|
keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
|
|
98
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
|
+
}
|
|
99
138
|
taskCount++;
|
|
100
139
|
}
|
|
101
140
|
});
|
|
102
141
|
});
|
|
103
|
-
}else
|
|
104
|
-
if(message.MSG_SET_GLOBAL_PARAMS){
|
|
105
|
-
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxx', message)
|
|
106
142
|
}
|
|
107
143
|
});
|
|
108
144
|
};
|
|
@@ -117,6 +153,7 @@ const start = (config)=>{
|
|
|
117
153
|
}
|
|
118
154
|
});
|
|
119
155
|
cluster.on('exit', (worker, code, signal) => {
|
|
156
|
+
clearTaskTimer(worker.id);//worker 退出(含崩溃)时清掉其超时计时器
|
|
120
157
|
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
121
158
|
console.log('Worker died unexpectedly starting a new one...');
|
|
122
159
|
let worker = cluster.fork();
|
|
@@ -144,10 +181,6 @@ const start = (config)=>{
|
|
|
144
181
|
};
|
|
145
182
|
function subWorker(){
|
|
146
183
|
let worker = cluster.worker;
|
|
147
|
-
//console.log(`>[w]=${process.pid} started`);
|
|
148
|
-
process.on('message', function(message) {
|
|
149
|
-
//
|
|
150
|
-
});
|
|
151
184
|
worker.on('message', function(message) {
|
|
152
185
|
if(message === MSG_NO_TASK_FOUND){//wait for new task
|
|
153
186
|
console.log(` (Worker #${worker.id}): waiting`);
|
|
@@ -158,63 +191,56 @@ function subWorker(){
|
|
|
158
191
|
if(message.subTask){//new task
|
|
159
192
|
let {config, subTask, startTimestamp, taskCount} = message;
|
|
160
193
|
TaskMgr.load(config);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
worker.send(MSG_REQUEST_NEW_TASK);//request new task
|
|
183
|
-
});
|
|
184
|
-
}else{
|
|
185
|
-
console.log('Can not find: message.subTask');
|
|
186
|
-
throw 'can not find subTask!'
|
|
187
|
-
}
|
|
194
|
+
subTask.index = taskCount;//for v1 backward
|
|
195
|
+
subTask.taskCount = taskCount;
|
|
196
|
+
subTask.workerId = worker.id;
|
|
197
|
+
subTask.workerPid = process.pid;
|
|
198
|
+
console.log(`[Task Start]: "${subTask.subid}"`, `pid=${process.pid}`, getDateTimeTxt());
|
|
199
|
+
let subTaskLog = {
|
|
200
|
+
workerId: worker.id,
|
|
201
|
+
workerPid: process.pid,
|
|
202
|
+
taskCount,
|
|
203
|
+
startTimestamp
|
|
204
|
+
};
|
|
205
|
+
runTask(config, subTask, subTaskLog).then((tasklog)=>{
|
|
206
|
+
let {cost, startTimestamp, endTimestamp} = tasklog;
|
|
207
|
+
|
|
208
|
+
console.log(`[Task Finished] "${config.masterTaskId}"."${subTask.subid}".`, getDateTimeTxt(), `cost=${cost}ms`)
|
|
209
|
+
}).then(()=>{
|
|
210
|
+
worker.send(MSG_REQUEST_NEW_TASK);//request new task
|
|
211
|
+
});
|
|
212
|
+
}else{
|
|
213
|
+
console.log('Can not find: message.subTask');
|
|
214
|
+
throw new Error('can not find subTask!')
|
|
188
215
|
}
|
|
189
216
|
});
|
|
190
217
|
worker.send(MSG_REQUEST_NEW_TASK);//request new task
|
|
191
218
|
|
|
192
219
|
};
|
|
193
|
-
function runTask(config, subTask, subTaskLog
|
|
220
|
+
function runTask(config, subTask, subTaskLog){
|
|
194
221
|
return new Promise((resolve)=>{
|
|
195
222
|
let resultPromise = Promise.resolve({});
|
|
196
223
|
let resultData;
|
|
197
224
|
let errors = [];
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
225
|
+
let serializeError = (e)=>{
|
|
226
|
+
if(e instanceof Error){
|
|
227
|
+
return {message: e.message, stack: e.stack};
|
|
228
|
+
}
|
|
229
|
+
return e;
|
|
204
230
|
};
|
|
231
|
+
helper.load(config);
|
|
205
232
|
TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
|
|
206
233
|
try{
|
|
207
234
|
resultPromise = USER_CONFIG.processTask(subTask, helper);//rewrite this method in subclass
|
|
208
235
|
}catch(e){
|
|
209
|
-
errors.push({subTask, exception:
|
|
236
|
+
errors.push({subTask, exception: serializeError(e)});
|
|
210
237
|
}
|
|
211
238
|
let ispromise = resultPromise instanceof Promise;
|
|
212
239
|
if(!ispromise){resultPromise = Promise.resolve(resultPromise);}//convert data to promise
|
|
213
240
|
resultPromise.then((data)=>{
|
|
214
241
|
resultData = data;
|
|
215
242
|
}).catch((ex)=>{
|
|
216
|
-
|
|
217
|
-
errors.push({ex});
|
|
243
|
+
errors.push({ex: serializeError(ex)});
|
|
218
244
|
}).finally(()=>{
|
|
219
245
|
let {startTimestamp} = subTaskLog;
|
|
220
246
|
let endTimestamp = new Date()*1;
|
|
@@ -225,7 +251,7 @@ function runTask(config, subTask, subTaskLog, sendMsg){
|
|
|
225
251
|
cost
|
|
226
252
|
};
|
|
227
253
|
let hasError = errors.length > 0;
|
|
228
|
-
if(hasError)console.
|
|
254
|
+
if(hasError)console.warn('[Task Error]', subTask.subid, errors);
|
|
229
255
|
TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
|
|
230
256
|
TaskMgr.saveTaskResult(subTask.subid, hasError, hasError ? errors : resultData);
|
|
231
257
|
TaskMgr.mvTask(subTask.subid, 'running',(hasError ? 'finished_with_errors':'finished'), (newPath)=>{
|
package/workers/TaskMgr.js
CHANGED
|
@@ -3,20 +3,6 @@ const pathutil = require('path');
|
|
|
3
3
|
const makeDir = require('../utils/makedir');
|
|
4
4
|
const randomsUtil = require('../utils/randoms');
|
|
5
5
|
|
|
6
|
-
let taskCounter = 0;
|
|
7
|
-
|
|
8
|
-
let taskFolder;
|
|
9
|
-
let progressRootFolder;
|
|
10
|
-
let resultsRootFolder;
|
|
11
|
-
|
|
12
|
-
let taskFolderNew;
|
|
13
|
-
let taskFolderRunning;
|
|
14
|
-
let taskFolderFinished;;
|
|
15
|
-
let taskFolderFailed;
|
|
16
|
-
|
|
17
|
-
let taskFolderResult;
|
|
18
|
-
let taskFolderResultFailed;
|
|
19
|
-
|
|
20
6
|
let allSubTasks = {
|
|
21
7
|
new:[],
|
|
22
8
|
running:[],
|
|
@@ -33,7 +19,7 @@ const TaskMgr = {
|
|
|
33
19
|
},
|
|
34
20
|
initFolders:()=>{
|
|
35
21
|
let {taskRootFolder, masterTaskId} = main_config;
|
|
36
|
-
taskFolder = pathutil.resolve(taskRootFolder, masterTaskId);
|
|
22
|
+
const taskFolder = pathutil.resolve(taskRootFolder, masterTaskId);
|
|
37
23
|
if(fs.existsSync(taskFolder) && !(main_config.__resume || main_config.__restart)){
|
|
38
24
|
let files = fs.readdirSync(taskFolder);
|
|
39
25
|
if(files.length > 0){
|
|
@@ -41,8 +27,6 @@ const TaskMgr = {
|
|
|
41
27
|
return process.exit(0);
|
|
42
28
|
}
|
|
43
29
|
};
|
|
44
|
-
//let taskfolder = pathutil.parse(__filename).dir;
|
|
45
|
-
//let taskFolder = pathutil.resolve(taskfolder, `./tasks/${masterTaskId}`);
|
|
46
30
|
const progressRootFolder = pathutil.resolve(taskFolder, './progress');
|
|
47
31
|
const resultsRootFolder = pathutil.resolve(taskFolder, './results');
|
|
48
32
|
const logsRootFolder = pathutil.resolve(taskFolder, './logs');
|
|
@@ -54,9 +38,6 @@ const TaskMgr = {
|
|
|
54
38
|
|
|
55
39
|
const taskFolderResult = pathutil.resolve(resultsRootFolder, './succ');
|
|
56
40
|
const taskFolderResultFailed = pathutil.resolve(resultsRootFolder, './errors');
|
|
57
|
-
//let mainRptFolder = pathutil.resolve(taskfolder, './report');
|
|
58
|
-
|
|
59
|
-
//makeDir.sync(mainRptFolder);
|
|
60
41
|
makeDir.sync(logsRootFolder);
|
|
61
42
|
makeDir.sync(taskFolderNew);
|
|
62
43
|
makeDir.sync(taskFolderRunning);
|
|
@@ -111,8 +92,12 @@ const TaskMgr = {
|
|
|
111
92
|
console.log('[WARN]', `file not found`, fpath)
|
|
112
93
|
}
|
|
113
94
|
if(fcontent){
|
|
114
|
-
|
|
115
|
-
|
|
95
|
+
try{
|
|
96
|
+
let task = JSON.parse(fcontent);
|
|
97
|
+
allSubTasks[type].push(task);
|
|
98
|
+
}catch(e){
|
|
99
|
+
console.warn('[WARN]', 'failed to parse task file, skipped', fpath);
|
|
100
|
+
}
|
|
116
101
|
}
|
|
117
102
|
});
|
|
118
103
|
resolve(allSubTasks[type]);
|
|
@@ -145,8 +130,13 @@ const TaskMgr = {
|
|
|
145
130
|
return new Promise((resolve)=>{
|
|
146
131
|
let newtask = allSubTasks.new.shift();
|
|
147
132
|
if(newtask){
|
|
148
|
-
TaskMgr.mvTask(newtask.subid, 'new', 'running', (newPath)=>{
|
|
149
|
-
|
|
133
|
+
TaskMgr.mvTask(newtask.subid, 'new', 'running', (newPath, succ)=>{
|
|
134
|
+
if(succ){
|
|
135
|
+
resolve(newtask);
|
|
136
|
+
}else{
|
|
137
|
+
allSubTasks.new.unshift(newtask);
|
|
138
|
+
resolve(null);
|
|
139
|
+
}
|
|
150
140
|
});
|
|
151
141
|
}else{
|
|
152
142
|
resolve(null);
|
|
@@ -156,6 +146,9 @@ const TaskMgr = {
|
|
|
156
146
|
getTask:(type, id)=>{
|
|
157
147
|
let result;
|
|
158
148
|
let arr = allSubTasks[type];
|
|
149
|
+
if(!arr){
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
159
152
|
for(let i=0;i<arr.length;i++){
|
|
160
153
|
let task = arr[i];
|
|
161
154
|
if(task.subid === id){
|
|
@@ -212,15 +205,23 @@ const TaskMgr = {
|
|
|
212
205
|
};
|
|
213
206
|
fs.writeFileSync(fpath, fcontent);
|
|
214
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
|
+
},
|
|
215
216
|
saveTaskLog:(subid, log)=>{
|
|
216
217
|
let {logsRootFolder} = main_config;
|
|
217
218
|
let folder = logsRootFolder;
|
|
218
219
|
let fpath = pathutil.resolve(folder, `${subid}.json`);
|
|
219
220
|
let fcontent;
|
|
220
|
-
log.subid = subid;
|
|
221
221
|
if(typeof log === 'undefined') {
|
|
222
222
|
fcontent = '';
|
|
223
223
|
}else{
|
|
224
|
+
log.subid = subid;
|
|
224
225
|
fcontent = JSON.stringify(log);
|
|
225
226
|
};
|
|
226
227
|
fs.writeFileSync(fpath, fcontent);
|
package/workers/WorkerMgr.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const pathutil = require('path');
|
|
3
|
-
const makeDir = require('../utils/makedir');
|
|
4
|
-
|
|
5
|
-
const BUSY = 1;
|
|
6
|
-
const IDLE = 0;
|
|
7
|
-
|
|
8
1
|
let workers = {};
|
|
9
2
|
let status = {};
|
|
10
3
|
|
|
@@ -12,7 +5,7 @@ const mgr = {
|
|
|
12
5
|
addWorker:(worker)=>{
|
|
13
6
|
let id = `${worker.id}`;
|
|
14
7
|
if(workers[id] || status[id]){
|
|
15
|
-
throw 'FATAL: duplicated worker!';
|
|
8
|
+
throw new Error('FATAL: duplicated worker!');
|
|
16
9
|
}
|
|
17
10
|
status[id] = {isBusy: true};
|
|
18
11
|
console.log('add worker', worker.id);
|
|
@@ -22,10 +15,11 @@ const mgr = {
|
|
|
22
15
|
return workers[`${id}`];
|
|
23
16
|
},
|
|
24
17
|
setBusy:(id, isBusy)=>{
|
|
25
|
-
|
|
18
|
+
status[`${id}`] = {isBusy: isBusy};
|
|
26
19
|
},
|
|
27
20
|
isBusy:(id)=>{
|
|
28
|
-
|
|
21
|
+
let s = status[`${id}`];
|
|
22
|
+
return s ? s.isBusy : false;
|
|
29
23
|
}
|
|
30
24
|
};
|
|
31
25
|
module.exports = mgr;
|
package/workers/asMaster.js
CHANGED
|
@@ -2,27 +2,11 @@ var fs = require('fs');
|
|
|
2
2
|
var pathUtil = require('path');
|
|
3
3
|
const numCPUs = require('os').cpus().length;
|
|
4
4
|
|
|
5
|
-
let MSG_REQUEST_TASKID = 'request_taskid';
|
|
6
|
-
|
|
7
5
|
const TaskMgr = require('./TaskMgr');
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const fmtDigit = (n)=>{
|
|
12
|
-
return n > 9 ? "" + n: "0" + n;
|
|
13
|
-
}
|
|
14
|
-
const getDateTimeTxt = ()=>{
|
|
15
|
-
let d=new Date();
|
|
16
|
-
let YYYY=d.getFullYear()
|
|
17
|
-
let MM=fmtDigit(d.getMonth()+1);
|
|
18
|
-
let DD=fmtDigit(d.getDate());
|
|
19
|
-
let HH=fmtDigit(d.getHours());
|
|
20
|
-
let mm=fmtDigit(d.getMinutes());
|
|
21
|
-
let ss=fmtDigit(d.getSeconds());
|
|
22
|
-
return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;
|
|
23
|
-
};
|
|
6
|
+
const randomsUtil = require('../utils/randoms');
|
|
24
7
|
|
|
25
8
|
const resume = (config)=>{
|
|
9
|
+
config.__resume = true;
|
|
26
10
|
let {taskRootFolder, taskId} = config;
|
|
27
11
|
let taskFolder = pathUtil.resolve(taskRootFolder, taskId);
|
|
28
12
|
|
|
@@ -33,33 +17,35 @@ const resume = (config)=>{
|
|
|
33
17
|
...task_config
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
// config.taskId = taskId;
|
|
37
|
-
// config.taskRootFolder = taskRootFolder;
|
|
38
|
-
// config.masterTaskId = taskId;
|
|
39
|
-
|
|
40
20
|
TaskMgr.load(config);
|
|
41
21
|
config = TaskMgr.initFolders(config);
|
|
42
22
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return new Promise((resolve)=>{
|
|
23
|
+
return new Promise((resolve, reject)=>{
|
|
46
24
|
|
|
47
25
|
TaskMgr.getTaskFiles('running', (taskfiles)=>{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
26
|
+
TaskMgr.getTaskFiles('finished_with_errors', (errfiles)=>{
|
|
27
|
+
let promises = [];
|
|
28
|
+
taskfiles.forEach((fname)=>{
|
|
29
|
+
let subid = fname.replace(/\.task\.json$/, '');
|
|
30
|
+
promises.push(TaskMgr.mvTaskPromise(subid, 'running', 'new'));
|
|
31
|
+
});
|
|
32
|
+
if(config.__restart){
|
|
33
|
+
errfiles.forEach((fname)=>{
|
|
34
|
+
let subid = fname.replace(/\.task\.json$/, '');
|
|
35
|
+
promises.push(TaskMgr.mvTaskPromise(subid, 'finished_with_errors', 'new'));
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
Promise.all(promises).then(()=>{
|
|
39
|
+
TaskMgr.loadAllTasks().then((types)=>{
|
|
40
|
+
console.log('******resume multi-thread')
|
|
41
|
+
setTimeout(()=>{
|
|
42
|
+
resolve(config);
|
|
43
|
+
}, 1000);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
}).catch((e)=>{
|
|
47
|
+
reject(e);
|
|
59
48
|
});
|
|
60
|
-
|
|
61
|
-
}).catch((e)=>{
|
|
62
|
-
console.log('failed', e)
|
|
63
49
|
});
|
|
64
50
|
});
|
|
65
51
|
});
|
|
@@ -68,19 +54,19 @@ const resume = (config)=>{
|
|
|
68
54
|
};
|
|
69
55
|
|
|
70
56
|
const init = (config)=>{
|
|
71
|
-
if(
|
|
72
|
-
if(!config.initialTasks){ throw 'Please provide initialTasks data!'; };
|
|
73
|
-
if(!config.processTask){ throw 'Please provide processTask function!'; };
|
|
57
|
+
if(!config) config = {};
|
|
58
|
+
if(!config.initialTasks){ throw new Error('Please provide initialTasks data!'); };
|
|
59
|
+
if(!config.processTask){ throw new Error('Please provide processTask function!'); };
|
|
74
60
|
|
|
75
61
|
if(typeof config.numberOfWorkers === 'undefined') config.numberOfWorkers = numCPUs - 1;
|
|
76
62
|
|
|
77
63
|
let {taskRootFolder, taskName} = config;
|
|
78
64
|
|
|
79
|
-
if(taskName && !taskName.match(
|
|
65
|
+
if(taskName && !taskName.match(/^[a-zA-Z0-9_-]+$/)){ throw new Error('Error: taskName can only contain letters and numbers'); }
|
|
80
66
|
if(!taskName){ taskName = 'multitasks'; }
|
|
81
67
|
|
|
82
68
|
let {round, pow, random} = Math;
|
|
83
|
-
let dateTimeText = getDateTimeTxt()
|
|
69
|
+
let dateTimeText = randomsUtil.getDateTimeTxt();
|
|
84
70
|
let randomNum = round((random() * pow(10,8)),8).toString(36);
|
|
85
71
|
let masterTaskId = config.taskId ? config.taskId : `${taskName}${dateTimeText}--${randomNum}`;
|
|
86
72
|
|
|
@@ -97,11 +83,6 @@ const init = (config)=>{
|
|
|
97
83
|
TaskMgr.createNewTask(task);
|
|
98
84
|
});
|
|
99
85
|
|
|
100
|
-
// let tasks = TaskMgr.getTasks('new')
|
|
101
|
-
// let task = tasks.shift();
|
|
102
|
-
// console.log(tasks)
|
|
103
|
-
// TaskMgr.mvTask(task.subid, 'new', 'finished',()=>{})
|
|
104
|
-
|
|
105
86
|
//to reload is for to reStart a collapsed task
|
|
106
87
|
return new Promise((resolve)=>{
|
|
107
88
|
TaskMgr.loadNewTasks().then(()=>{
|
package/workers/helper.js
CHANGED
|
@@ -8,6 +8,7 @@ const load = (config)=>{
|
|
|
8
8
|
myconfig = config;
|
|
9
9
|
};
|
|
10
10
|
const createNewTasks = (tasks)=>{
|
|
11
|
+
if (!myconfig) throw new Error('Please call helper.load(config) first!');
|
|
11
12
|
if (!Array.isArray(tasks)) tasks = [tasks];
|
|
12
13
|
TaskMgr.load(myconfig);
|
|
13
14
|
tasks.forEach((task)=>{
|
|
@@ -15,11 +16,7 @@ const createNewTasks = (tasks)=>{
|
|
|
15
16
|
TaskMgr.createNewTask(task);
|
|
16
17
|
});
|
|
17
18
|
};
|
|
18
|
-
const setGlobalParams = (params)=>{
|
|
19
|
-
//will be overridden in Multithread
|
|
20
|
-
};
|
|
21
19
|
module.exports = {
|
|
22
20
|
load,
|
|
23
|
-
createNewTasks
|
|
24
|
-
|
|
25
|
-
};
|
|
21
|
+
createNewTasks
|
|
22
|
+
};
|
package/workers/index.js
CHANGED
|
@@ -33,12 +33,19 @@ const restart = (config)=>{
|
|
|
33
33
|
config = backwardv1.v1tov2(config);
|
|
34
34
|
let {taskFolder} = config;
|
|
35
35
|
if(!taskFolder){
|
|
36
|
-
console.log('[ERROR]', 'method
|
|
36
|
+
console.log('[ERROR]', 'method restart need param "taskFolder"');
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
+
config.taskRootFolder = pathUtil.dirname(taskFolder);
|
|
40
|
+
config.taskId = pathUtil.basename(taskFolder);
|
|
41
|
+
config.__resume = true;
|
|
42
|
+
config.__restart = true;
|
|
43
|
+
config.initialTasks = [];
|
|
44
|
+
Multithread.start(config);
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
const multiTasks = (config)=>{
|
|
48
|
+
config = backwardv1.v1tov2(config);
|
|
42
49
|
let {initialTasks, taskRootFolder, taskId} = config;
|
|
43
50
|
let isResume = false;
|
|
44
51
|
|
|
@@ -50,10 +57,10 @@ const multiTasks = (config)=>{
|
|
|
50
57
|
let folder = pathUtil.resolve(taskRootFolder, initialTasks);
|
|
51
58
|
if(fs.existsSync(folder)){
|
|
52
59
|
isResume = true;
|
|
53
|
-
config.taskRootFolder = pathUtil.dirname(folder)
|
|
60
|
+
config.taskRootFolder = pathUtil.dirname(folder);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
if(!isResume) throw 'FATAL: initialTasks should be an array or a task root folder!';
|
|
63
|
+
if(!isResume) throw new Error('FATAL: initialTasks should be an array or a task root folder!');
|
|
57
64
|
}else{
|
|
58
65
|
let taskFolder = pathUtil.resolve(taskRootFolder, taskId);
|
|
59
66
|
if(fs.existsSync(taskFolder)){
|
package/cmd.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import readline from 'readline';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// 初始化数值
|
|
6
|
-
let values = [0, 0, 0];
|
|
7
|
-
|
|
8
|
-
// 更新并显示数值的函数
|
|
9
|
-
function updateAndDisplayValues() {
|
|
10
|
-
// 增加一些随机值
|
|
11
|
-
values = values.map(value => value + Math.floor(Math.random() * 10));
|
|
12
|
-
|
|
13
|
-
// 清除当前行并将光标移回行首
|
|
14
|
-
process.stdout.clearLine();
|
|
15
|
-
process.stdout.cursorTo(0);
|
|
16
|
-
|
|
17
|
-
// 显示更新后的数值,不换行
|
|
18
|
-
process.stdout.write(chalk.green(`Value 1: ${values[0]}\t`));
|
|
19
|
-
process.stdout.write(chalk.yellow(`Value 2: ${values[1]}\t`));
|
|
20
|
-
process.stdout.write(chalk.blue(`Value 3: ${values[2]}\r`)); // \r 将光标移回行首,准备下一次输出
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 创建readline接口实例以监听键盘输入
|
|
24
|
-
const rl = readline.createInterface({
|
|
25
|
-
input: process.stdin,
|
|
26
|
-
output: process.stdout
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// 每秒更新和显示数值
|
|
30
|
-
setInterval(updateAndDisplayValues, 1000);
|
|
31
|
-
|
|
32
|
-
// 监听'q'键退出
|
|
33
|
-
rl.on('line', (input) => {
|
|
34
|
-
if (input.trim().toLowerCase() === 'q') {
|
|
35
|
-
clearInterval(intervalId); // 清除定时器
|
|
36
|
-
rl.close(); // 关闭readline接口
|
|
37
|
-
process.exit(0); // 退出程序
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// 存储定时器的ID,以便稍后清除它
|
|
42
|
-
let intervalId = setInterval(updateAndDisplayValues, 1000);
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
let multiTasks = require('../../index').multiTasks;
|
|
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
|
-
alltasks.push({
|
|
7
|
-
name: `task-${i}`,
|
|
8
|
-
data: `This prop is for a subtask`
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
//Step2, Provide a function to process a certain sub-task and return the result data
|
|
13
|
-
let processTask = (task, helper)=>{
|
|
14
|
-
let {taskCount} = task;
|
|
15
|
-
|
|
16
|
-
return 'result data';
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
//Step2.1, or return a promise
|
|
20
|
-
let processTask = (task, helper)=>{
|
|
21
|
-
let {taskCount} = task;
|
|
22
|
-
|
|
23
|
-
return new Promise((resolve, reject)=>{
|
|
24
|
-
resolve({
|
|
25
|
-
data:`task${taskCount} complete`
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
//Step2.2, dynamically create a new task if find something new while processing
|
|
31
|
-
let processTask = (task, helper)=>{
|
|
32
|
-
let {taskCount} = task;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if(taskCount % 2 === 0){
|
|
36
|
-
helper.createNewTasks({//You can dynamically create a new task if find something new while processing
|
|
37
|
-
msg:'a new task'
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return new Promise((resolve, reject)=>{
|
|
43
|
-
resolve({
|
|
44
|
-
data:`task${taskCount} complete`
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
};
|
|
48
|
-
//Step2.3, this example shows how to handle exceptions/errors
|
|
49
|
-
let processTask = (task, helper)=>{
|
|
50
|
-
let {taskCount} = task;
|
|
51
|
-
|
|
52
|
-
//This is the demo of exceptions/errors, they will be captured and saved in the results/errors folder
|
|
53
|
-
if(taskCount===3) throw 'exception';
|
|
54
|
-
if(taskCount===4) return Promise.reject({err:'a test error'});
|
|
55
|
-
if(taskCount===5) aaa = bbb;
|
|
56
|
-
|
|
57
|
-
if(taskCount===6) return 'a result which is not a promise'; //Return a non-promise result is OK
|
|
58
|
-
|
|
59
|
-
//by default, you should return a promise, all returned data can be found in the results/succ folder
|
|
60
|
-
//but to return a non-promise result is also OK, see above
|
|
61
|
-
return new Promise((resolve, reject)=>{
|
|
62
|
-
setTimeout(()=>{
|
|
63
|
-
if(taskCount % 2 === 0){
|
|
64
|
-
helper.createNewTasks({//You can dynamically create a new task if find something new while processing
|
|
65
|
-
msg:'a new task'
|
|
66
|
-
});
|
|
67
|
-
resolve()
|
|
68
|
-
}else{
|
|
69
|
-
resolve({
|
|
70
|
-
data:`task${taskCount} complete`
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
}, 10)
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
//Step3, run!
|
|
78
|
-
multiTasks.start({
|
|
79
|
-
initialTasks: alltasks,
|
|
80
|
-
processTask,
|
|
81
|
-
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
82
|
-
taskId: 'my-readme-task',
|
|
83
|
-
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
84
|
-
//autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
|
|
85
|
-
onFinish: (report)=>{
|
|
86
|
-
console.log('finish callback', report);
|
|
87
|
-
}
|
|
88
|
-
});
|
package/examples/example0/run.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
let multiTasks = require('../../index').multiTasks;
|
|
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
|
-
if(taskCount===7) {return new Promise((resolve, reject)=>{//async exception
|
|
30
|
-
setTimeout(()=>{
|
|
31
|
-
a=beforeAll;
|
|
32
|
-
}, 10 * Math.random())
|
|
33
|
-
});}
|
|
34
|
-
|
|
35
|
-
//by default, you should return a promise, all returned data can be found in the results/succ folder
|
|
36
|
-
return new Promise((resolve, reject)=>{
|
|
37
|
-
setTimeout(()=>{
|
|
38
|
-
if(taskCount % 2 === 0)helper.createNewTasks({//
|
|
39
|
-
msg:'a new task'
|
|
40
|
-
});
|
|
41
|
-
taskCount === 2 ? reject('a rejected error') : resolve({
|
|
42
|
-
message: 'this is a result from a demo, random data=' + Math.random()
|
|
43
|
-
});
|
|
44
|
-
}, 10 * Math.random())
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//Step3, run!
|
|
49
|
-
multiTasks({
|
|
50
|
-
initialTasks: alltasks,
|
|
51
|
-
processTask,
|
|
52
|
-
taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
|
|
53
|
-
taskId: 'my-task' + Math.random(),
|
|
54
|
-
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
55
|
-
//autoCloseAfterCompletion: true,
|
|
56
|
-
onFinish: (report)=>{
|
|
57
|
-
console.log('finish callback', report);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
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/monitors/cli.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const cliProgress = require('cli-progress');
|
|
2
|
-
|
|
3
|
-
// create new container
|
|
4
|
-
const multibar = new cliProgress.MultiBar({
|
|
5
|
-
clearOnComplete: false,
|
|
6
|
-
hideCursor: true,
|
|
7
|
-
format: ' {bar} | {filename} | {value}/{total}',
|
|
8
|
-
}, cliProgress.Presets.shades_grey);
|
|
9
|
-
|
|
10
|
-
// add bars
|
|
11
|
-
const b1 = multibar.create(200, 0);
|
|
12
|
-
const b2 = multibar.create(1000, 0);
|
|
13
|
-
|
|
14
|
-
// control bars
|
|
15
|
-
b1.increment();
|
|
16
|
-
b2.update(20, {filename: "test1.txt"});
|
|
17
|
-
b1.update(20, {filename: "helloworld.txt"});
|
|
18
|
-
|
|
19
|
-
b2.setTotal(2000);
|
|
20
|
-
let v = 200;
|
|
21
|
-
setInterval(()=>{
|
|
22
|
-
v=v+20
|
|
23
|
-
b2.update(v, {filename: "2222.txt"});
|
|
24
|
-
|
|
25
|
-
// stop all bars
|
|
26
|
-
|
|
27
|
-
//multibar.stop();
|
|
28
|
-
}, 300)
|
package/test/splitter.test.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
let splitter = require('../workers/taskSplitter');
|
|
2
|
-
|
|
3
|
-
let createArray = (len)=>{
|
|
4
|
-
let alltasks = []
|
|
5
|
-
for(let i=0;i<len;i++){
|
|
6
|
-
alltasks.push(i)
|
|
7
|
-
}
|
|
8
|
-
return alltasks;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
test('test taskSplitter', async () => {
|
|
12
|
-
|
|
13
|
-
expect(splitter.split(createArray(3), 5).length).toBe(1);
|
|
14
|
-
expect(splitter.split(createArray(16), 5).length).toBe(4);
|
|
15
|
-
expect(splitter.split(createArray(101), 1).length).toBe(101);
|
|
16
|
-
expect(splitter.split(createArray(101), 33).length).toBe(4);
|
|
17
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
let multiTasks = require('../../index').multiTasks;
|
|
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<1000;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
|
-
}, 500 * Math.random())
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//Step3, run!
|
|
43
|
-
multiTasks({
|
|
44
|
-
initialTasks: alltasks,
|
|
45
|
-
processTask,
|
|
46
|
-
taskRootFolder: `./tmp`, //a directory to store progress and results files, you can check the progress here
|
|
47
|
-
taskId: 'test_resume',
|
|
48
|
-
numberOfWorkers: 3, //Assign how many workers are working in parallel
|
|
49
|
-
//autoCloseAfterCompletion: true,
|
|
50
|
-
shouldTerminate:(info)=>{
|
|
51
|
-
if(info.taskCount > 30) return true;
|
|
52
|
-
},
|
|
53
|
-
onFinish: (report)=>{
|
|
54
|
-
console.log('finish callback', report);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
let multiTasks = require('../../index').multiTasks;
|
|
2
|
-
|
|
3
|
-
let processTask = (task, helper)=>{
|
|
4
|
-
let {taskCount} = task;
|
|
5
|
-
|
|
6
|
-
return 'a result which is not a promise';
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
//Step3, run!
|
|
10
|
-
multiTasks({
|
|
11
|
-
processTask,
|
|
12
|
-
taskRootFolder: `./tmp`, //a directory to store progress and results files, you can check the progress here
|
|
13
|
-
taskId: 'test_resume',
|
|
14
|
-
numberOfWorkers: 11, //Assign how many workers are working in parallel
|
|
15
|
-
//autoCloseAfterCompletion: true,
|
|
16
|
-
shouldTerminate:(info)=>{
|
|
17
|
-
//if(info.taskCount > 30) return true;
|
|
18
|
-
},
|
|
19
|
-
onFinish: (report)=>{
|
|
20
|
-
console.log('finish callback', report);
|
|
21
|
-
}
|
|
22
|
-
});
|
package/test/test_resume/test.sh
DELETED
package/todo.txt
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
const AsyncQueue = require(`../asyncQueue`);
|
|
2
|
-
const queue = new AsyncQueue();
|
|
3
|
-
|
|
4
|
-
let count=0;
|
|
5
|
-
|
|
6
|
-
queue.enqueue(async () => {
|
|
7
|
-
count++;
|
|
8
|
-
console.log(`Task ${count} start`);
|
|
9
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
10
|
-
console.log(`Task ${count} end`);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
queue.enqueue(async () => {
|
|
15
|
-
count++;
|
|
16
|
-
console.log(`Task ${count} start`);
|
|
17
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
18
|
-
console.log(`Task ${count} end`);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
queue.enqueue(() => {
|
|
23
|
-
count++;
|
|
24
|
-
console.log(`Task ${count} start`);
|
|
25
|
-
console.log(`Task ${count} end`);
|
|
26
|
-
return 11;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
queue.enqueue(() => {
|
|
31
|
-
count++;
|
|
32
|
-
console.log(`Task ${count} start`);
|
|
33
|
-
console.log(`Task ${count} end`);
|
|
34
|
-
return 11;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
queue.enqueue(() => {
|
|
38
|
-
count++;
|
|
39
|
-
console.log(`Task ${count} start`);
|
|
40
|
-
console.log(`Task ${count} end`);
|
|
41
|
-
return 11;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
queue.enqueue(() => {
|
|
45
|
-
count++;
|
|
46
|
-
console.log(`Task ${count} start`);
|
|
47
|
-
console.log(`Task ${count} end`);
|
|
48
|
-
return 11;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
queue.enqueue(() => {
|
|
52
|
-
count++;
|
|
53
|
-
console.log(`Task ${count} start`);
|
|
54
|
-
console.log(`Task ${count} end`);
|
|
55
|
-
return 11;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
queue.enqueue(() => {
|
|
59
|
-
count++;
|
|
60
|
-
console.log(`Task ${count} start`);
|
|
61
|
-
console.log(`Task ${count} end`);
|
|
62
|
-
return 11;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
queue.enqueue(async () => {
|
|
66
|
-
count++;
|
|
67
|
-
console.log(`Task ${count} start`);
|
|
68
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
69
|
-
console.log(`Task ${count} end`);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
queue.enqueue(async () => {
|
|
75
|
-
count++;
|
|
76
|
-
console.log(`Task ${count} start`);
|
|
77
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
78
|
-
console.log(`Task ${count} end`);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
queue.enqueue(async () => {
|
|
84
|
-
count++;
|
|
85
|
-
console.log(`Task ${count} start`);
|
|
86
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
87
|
-
console.log(`Task ${count} end`);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
queue.enqueue(async () => {
|
|
93
|
-
count++;
|
|
94
|
-
console.log(`Task ${count} start`);
|
|
95
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
96
|
-
console.log(`Task ${count} end`);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
queue.enqueue(() => {
|
|
101
|
-
count++;
|
|
102
|
-
console.log(`Task ${count} start`);
|
|
103
|
-
console.log(`Task ${count} end`);
|
|
104
|
-
return 11;
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
queue.enqueue(async () => {
|
|
109
|
-
count++;
|
|
110
|
-
console.log(`Task ${count} start`);
|
|
111
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
112
|
-
console.log(`Task ${count} end`);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
package/utils/test_makedir.js
DELETED
package/workers/asWorker.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
var fs = require('fs');
|
|
2
|
-
var pathutil = require('path');
|
|
3
|
-
const numCPUs = require('os').cpus().length;
|
|
4
|
-
|
|
5
|
-
let MSG_REQUEST_TASKID = 'request_taskid';
|
|
6
|
-
|
|
7
|
-
const TaskMgr = require('./TaskMgr');
|
|
8
|
-
|
|
9
|
-
const init = (config, worker)=>{
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
module.exports = {
|
|
13
|
-
init
|
|
14
|
-
};
|