multi-tasks 2.2.0 → 3.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # multi-tasks
2
2
 
3
- Multi-tasks is used to handle 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.
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
5
  ### Install:
6
6
 
@@ -116,8 +116,13 @@ multiTasks({
116
116
 
117
117
  ```
118
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
+
119
123
  ### Changelog:
120
124
 
125
+ - 3.0.1 Versions 3.0.0 and above are maintained by AI
121
126
  - 2.2.0 Support Resuming from an interrupted task
122
127
  - 2.1.2 Update readme
123
128
  - 2.1.1 Fix: recreate a new one when a worker collapsed unexpectedly.
@@ -150,10 +155,6 @@ multiTasks({
150
155
  - 1.0.7 Remove moment
151
156
  - 1.0.6 Performance optimization
152
157
 
153
- ### Github:
154
-
155
- [https://github.com/zhanglei923/multi-tasks](https://github.com/zhanglei923/multi-tasks)
156
-
157
- ### Support / Bug Report:
158
+ ### Gitee:
158
159
 
159
- [zhangleisupport923@163.com](mailto:zhangleisupport923@163.com)
160
+ [https://gitee.com/zhanglei923/multi-tasks](https://gitee.com/zhanglei923/multi-tasks)
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  const multiTasks = require('./workers');
2
2
  module.exports = {
3
3
  multiTasks
4
- }
4
+ };
package/package.json CHANGED
@@ -1,13 +1,26 @@
1
1
  {
2
2
  "name": "multi-tasks",
3
- "version": "2.2.0",
4
- "description": "",
3
+ "version": "3.0.1",
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": "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",
@@ -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
- let result = task();
21
- let isPromise = result instanceof Promise;
22
- //console.log('r=q',isPromise)
23
- if(!isPromise) {
24
- console.error('[ERR]Queue functions must return a promise object');
25
- throw 'Queue functions must return a promise object';
26
- }
27
- try {
28
- await result;
29
- } catch (error) {
30
- console.error('Error processing task:', error);
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
 
@@ -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()) {
@@ -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
 
@@ -41,8 +37,7 @@ let killAllWorkers = ()=>{
41
37
  }
42
38
  };
43
39
  let killWorker = (worker)=>{
44
- worker.disconnect();
45
- //worker.process.kill();
40
+ worker.disconnect();
46
41
  worker.kill();
47
42
  };
48
43
 
@@ -73,7 +68,6 @@ const start = (config)=>{
73
68
  if(!subTask){
74
69
  //no tasks left
75
70
  console.log('[Master]: no tasks left!');
76
- //console.log('workers', cluster.workers);
77
71
  if(config.autoCloseAfterCompletion){
78
72
  killWorker(worker);
79
73
  }else{//waiting for new tasks
@@ -100,9 +94,6 @@ const start = (config)=>{
100
94
  }
101
95
  });
102
96
  });
103
- }else
104
- if(message.MSG_SET_GLOBAL_PARAMS){
105
- console.log('xxxxxxxxxxxxxxxxxxxxxxxxxx', message)
106
97
  }
107
98
  });
108
99
  };
@@ -144,10 +135,6 @@ const start = (config)=>{
144
135
  };
145
136
  function subWorker(){
146
137
  let worker = cluster.worker;
147
- //console.log(`>[w]=${process.pid} started`);
148
- process.on('message', function(message) {
149
- //
150
- });
151
138
  worker.on('message', function(message) {
152
139
  if(message === MSG_NO_TASK_FOUND){//wait for new task
153
140
  console.log(` (Worker #${worker.id}): waiting`);
@@ -158,63 +145,56 @@ function subWorker(){
158
145
  if(message.subTask){//new task
159
146
  let {config, subTask, startTimestamp, taskCount} = message;
160
147
  TaskMgr.load(config);
161
- //console.log('ssss', subTask)
162
- if(subTask){
163
- subTask.index = taskCount;//for v1 backward
164
- subTask.taskCount = taskCount;
165
- subTask.workerId = worker.id;
166
- subTask.workerPid = process.pid;
167
- console.log(`[Task Start]: "${subTask.subid}"`, `pid=${process.pid}`, getDateTimeTxt());
168
- let sendMsg = (msg)=>{
169
- worker.send(msg);
170
- };
171
- let subTaskLog = {
172
- workerId: worker.id,
173
- workerPid: process.pid,
174
- taskCount,
175
- startTimestamp
176
- };
177
- runTask(config, subTask, subTaskLog, sendMsg).then((tasklog)=>{
178
- let {cost, startTimestamp, endTimestamp} = tasklog;
179
-
180
- console.log(`[Task Finished] "${config.masterTaskId}"."${subTask.subid}".`, getDateTimeTxt(), `cost=${cost}ms`)
181
- }).then(()=>{
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
- }
148
+ subTask.index = taskCount;//for v1 backward
149
+ subTask.taskCount = taskCount;
150
+ subTask.workerId = worker.id;
151
+ subTask.workerPid = process.pid;
152
+ console.log(`[Task Start]: "${subTask.subid}"`, `pid=${process.pid}`, getDateTimeTxt());
153
+ let subTaskLog = {
154
+ workerId: worker.id,
155
+ workerPid: process.pid,
156
+ taskCount,
157
+ startTimestamp
158
+ };
159
+ runTask(config, subTask, subTaskLog).then((tasklog)=>{
160
+ let {cost, startTimestamp, endTimestamp} = tasklog;
161
+
162
+ console.log(`[Task Finished] "${config.masterTaskId}"."${subTask.subid}".`, getDateTimeTxt(), `cost=${cost}ms`)
163
+ }).then(()=>{
164
+ worker.send(MSG_REQUEST_NEW_TASK);//request new task
165
+ });
166
+ }else{
167
+ console.log('Can not find: message.subTask');
168
+ throw new Error('can not find subTask!')
188
169
  }
189
170
  });
190
171
  worker.send(MSG_REQUEST_NEW_TASK);//request new task
191
172
 
192
173
  };
193
- function runTask(config, subTask, subTaskLog, sendMsg){
174
+ function runTask(config, subTask, subTaskLog){
194
175
  return new Promise((resolve)=>{
195
176
  let resultPromise = Promise.resolve({});
196
177
  let resultData;
197
178
  let errors = [];
198
- helper.load(config);
199
- helper.setGlobalParams = (params)=>{
200
- sendMsg({
201
- MSG_SET_GLOBAL_PARAMS,
202
- params
203
- });
179
+ let serializeError = (e)=>{
180
+ if(e instanceof Error){
181
+ return {message: e.message, stack: e.stack};
182
+ }
183
+ return e;
204
184
  };
185
+ helper.load(config);
205
186
  TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
206
187
  try{
207
188
  resultPromise = USER_CONFIG.processTask(subTask, helper);//rewrite this method in subclass
208
189
  }catch(e){
209
- errors.push({subTask, exception: 'Exception:' + e.toString()});
190
+ errors.push({subTask, exception: serializeError(e)});
210
191
  }
211
192
  let ispromise = resultPromise instanceof Promise;
212
193
  if(!ispromise){resultPromise = Promise.resolve(resultPromise);}//convert data to promise
213
194
  resultPromise.then((data)=>{
214
195
  resultData = data;
215
196
  }).catch((ex)=>{
216
- if(typeof ex !== 'string') ex = ex.toString();
217
- errors.push({ex});
197
+ errors.push({ex: serializeError(ex)});
218
198
  }).finally(()=>{
219
199
  let {startTimestamp} = subTaskLog;
220
200
  let endTimestamp = new Date()*1;
@@ -225,7 +205,7 @@ function runTask(config, subTask, subTaskLog, sendMsg){
225
205
  cost
226
206
  };
227
207
  let hasError = errors.length > 0;
228
- if(hasError)console.log('hasError=====', hasError, errors);
208
+ if(hasError)console.warn('[Task Error]', subTask.subid, errors);
229
209
  TaskMgr.saveTaskLog(subTask.subid, subTaskLog);
230
210
  TaskMgr.saveTaskResult(subTask.subid, hasError, hasError ? errors : resultData);
231
211
  TaskMgr.mvTask(subTask.subid, 'running',(hasError ? 'finished_with_errors':'finished'), (newPath)=>{
@@ -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
- let task = JSON.parse(fcontent);
115
- allSubTasks[type].push(task);
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
- resolve(newtask);
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){
@@ -217,10 +210,10 @@ const TaskMgr = {
217
210
  let folder = logsRootFolder;
218
211
  let fpath = pathutil.resolve(folder, `${subid}.json`);
219
212
  let fcontent;
220
- log.subid = subid;
221
213
  if(typeof log === 'undefined') {
222
214
  fcontent = '';
223
215
  }else{
216
+ log.subid = subid;
224
217
  fcontent = JSON.stringify(log);
225
218
  };
226
219
  fs.writeFileSync(fpath, fcontent);
@@ -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;
@@ -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 Multithread = require('./Multithread');
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
- //console.log(config)
44
-
45
- return new Promise((resolve)=>{
23
+ return new Promise((resolve, reject)=>{
46
24
 
47
25
  TaskMgr.getTaskFiles('running', (taskfiles)=>{
48
- let promises = [];
49
- taskfiles.forEach((fname)=>{
50
- let subid = fname.replace(/\.task\.json$/, '');
51
- promises.push(TaskMgr.mvTaskPromise(subid, 'running', 'new'));
52
- });
53
- Promise.all(promises).then(()=>{
54
- TaskMgr.loadAllTasks().then((types)=>{
55
- console.log('******resume multi-thread')
56
- setTimeout(()=>{
57
- resolve(config);
58
- }, 1000);
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(typeof config === 'undefined') config = {};
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(/[a-zA-Z0-9_-]/)){ throw 'Error: taskName can only contain letters and numbers'; }
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();//moment().format('YYYY-MM-DD_HH-mm-ss');
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
- setGlobalParams
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 resume need param "taskFolder"')
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
- });
@@ -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)
@@ -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
- });
@@ -1,5 +0,0 @@
1
- #!/bin/bash
2
- rm -rf ./tmp
3
- node step1_start_and_crash
4
- sleep 3
5
- node step2_resume
package/todo.txt DELETED
@@ -1,4 +0,0 @@
1
- Features:
2
- - Continue execution from the point of interruption
3
- - Re-run
4
- - Show status in command line
@@ -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
-
@@ -1,3 +0,0 @@
1
- let makedir = require('./makedir');
2
- makedir.sync('c:/aaa/bb')
3
- makedir.sync('c:/cc/dd')
@@ -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
- };