multi-tasks 2.0.1 → 2.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 CHANGED
@@ -90,13 +90,14 @@ let processTask = (task, helper)=>{
90
90
  }
91
91
 
92
92
  //Step3, run!
93
- multiTasks({
93
+ multiTasks.start({
94
94
  tasks: alltasks,
95
95
  processTask,
96
96
  taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
97
97
  taskId: 'my-task',
98
98
  numberOfWorkers: 3, //Assign how many workers are working in parallel
99
99
  //autoCloseAfterCompletion: true, //if you have dynamically generated new tasks, put this as false
100
+ shouldTerminate,//return true if you need to terminate the whole process
100
101
  onFinish: (report)=>{
101
102
  console.log('finish callback', report);
102
103
  }
@@ -113,6 +114,7 @@ node examples/example1/run
113
114
 
114
115
  Changelog:
115
116
 
117
+ - 2.0.2 new feature: support shouldTerminate
116
118
  - 2.0.1 Avoid possible I/O conflicts.
117
119
  - 2.0.0 Rewritten with a new architecture to support dynamic tasks.
118
120
  - 1.2.8 Fix: create task folder failed on MacOS
@@ -75,7 +75,7 @@ let processTask = (task, helper)=>{
75
75
  }
76
76
 
77
77
  //Step3, run!
78
- multiTasks({
78
+ multiTasks.start({
79
79
  tasks: 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
@@ -40,13 +40,13 @@ let processTask = (task, helper)=>{
40
40
  }
41
41
 
42
42
  //Step3, run!
43
- multiTasks({
43
+ multiTasks.start({
44
44
  tasks: alltasks,
45
45
  processTask,
46
46
  taskRootFolder: `../examples-tmp-data/example0`, //a directory to store progress and results files, you can check the progress here
47
47
  taskId: 'my-task',
48
48
  numberOfWorkers: 3, //Assign how many workers are working in parallel
49
- autoCloseAfterCompletion: true,
49
+ //autoCloseAfterCompletion: true,
50
50
  onFinish: (report)=>{
51
51
  console.log('finish callback', report);
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-tasks",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -18,14 +18,18 @@ class AsyncQueue {
18
18
  }
19
19
  const task = this.queue.shift();
20
20
  let result = task();
21
- if(!result instanceof Promise) result = Promise.resolve(result);
22
-
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
+ }
23
27
  try {
24
28
  await result;
25
29
  } catch (error) {
26
30
  console.error('Error processing task:', error);
27
31
  }
28
- this.processNext(); // 处理完一个任务后,继续处理下一个任务
32
+ this.processNext();
29
33
  }
30
34
  }
31
35
 
package/utils/makedir.js CHANGED
@@ -39,10 +39,14 @@ function getDirectFiles(dir, callback) {
39
39
  files.forEach(file => {
40
40
  const filePath = path.join(dir, file);
41
41
  //console.log(filePath)
42
+ try{
42
43
  let stats = fs.statSync(filePath);
43
44
  if (stats.isFile()) {
44
45
  childrenfiles.push(file);
45
46
  }
47
+ }catch(e){
48
+ //
49
+ }
46
50
  });
47
51
  callback(childrenfiles);
48
52
  });
@@ -29,6 +29,21 @@ let USER_CONFIG;
29
29
 
30
30
  let taskCount = 0;
31
31
 
32
+ let killAllWorkers = ()=>{
33
+ for (const id in cluster.workers) {
34
+ if (cluster.workers.hasOwnProperty(id)) {
35
+ const worker = cluster.workers[id];
36
+ console.log(`Killing worker ${worker.process.pid}`);
37
+ killWorker(worker);
38
+ }
39
+ }
40
+ };
41
+ let killWorker = (worker)=>{
42
+ worker.disconnect();
43
+ //worker.process.kill();
44
+ worker.kill();
45
+ };
46
+
32
47
  const start = (config)=>{
33
48
  USER_CONFIG = config;//this will remain user defined functions
34
49
  let isMaster = (cluster.isPrimary || cluster.isMaster);
@@ -63,7 +78,7 @@ const start = (config)=>{
63
78
  console.log('[Master]: no tasks left!');
64
79
  //console.log('workers', cluster.workers);
65
80
  if(config.autoCloseAfterCompletion){
66
- worker.kill();
81
+ killWorker(worker);
67
82
  }else{//waiting for new tasks
68
83
  worker.send(MSG_NO_TASK_FOUND,()=>{}, {
69
84
  keepOpen: (typeof config.keepOpen === 'undefined') ? false : config.keepOpen
@@ -71,6 +86,12 @@ const start = (config)=>{
71
86
  }
72
87
  //
73
88
  }else{
89
+
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
+ }
74
95
  //send new tasks to the worker
75
96
  worker.send({
76
97
  taskCount,
@@ -34,7 +34,7 @@ const TaskMgr = {
34
34
  initFolders:()=>{
35
35
  let {taskRootFolder, masterTaskId} = main_config;
36
36
  taskFolder = pathutil.resolve(taskRootFolder, masterTaskId);
37
- if(fs.existsSync(taskFolder)){
37
+ if(fs.existsSync(taskFolder) && !(main_config.__resume || main_config.__restart)){
38
38
  let files = fs.readdirSync(taskFolder);
39
39
  if(files.length > 0){
40
40
  console.log('[FATAL] taskRootFolder already exist and not empty, delete it and try again: ', taskFolder);
@@ -95,13 +95,15 @@ const TaskMgr = {
95
95
  loadNewTasks:()=>{
96
96
  const {masterTaskId, taskFolderNew} = main_config;
97
97
  return new Promise((resolve)=>{
98
- console.log('get1')
99
98
  makeDir.getDirectFiles(taskFolderNew, (taskFiles)=>{
100
99
  taskFiles.forEach((filename)=>{
101
100
  let fpath = pathutil.resolve(taskFolderNew, filename);
102
101
  let fcontent;
102
+ try{
103
103
  fcontent = fs.readFileSync(fpath, 'utf-8');
104
-
104
+ }catch(e){
105
+ console.log('[WARN]', `file not found`, fpath)
106
+ }
105
107
  if(fcontent){
106
108
  let task = JSON.parse(fcontent);
107
109
  allSubTasks.new.push(task);
@@ -150,7 +152,6 @@ const TaskMgr = {
150
152
  getTaskFiles:(type, callback)=>{
151
153
  let rootdir = main_config.progressRootFolder;
152
154
  let targetFolder = pathutil.resolve(rootdir, `./${type}`);
153
- console.log('get2')
154
155
  makeDir.getDirectFiles(targetFolder, (files)=>{
155
156
  callback(files);
156
157
  });
@@ -160,11 +161,14 @@ const TaskMgr = {
160
161
  let {progressRootFolder} = main_config;
161
162
  let oldPath = pathutil.resolve(progressRootFolder, `./${from}/${subid}.task.json`);
162
163
  let newPath = pathutil.resolve(progressRootFolder, `./${to}/${subid}.task.json`);
163
-
164
+ try{
164
165
  fs.rename(oldPath, newPath, (err) => {
165
166
  //if (err) throw err;
166
167
  callback(newPath);
167
168
  });
169
+ }catch(e){
170
+ callback(newPath);
171
+ }
168
172
  },
169
173
  saveTaskResult:(subid, hasError, data)=>{
170
174
  let {taskFolderResult, taskFolderResultFailed} = main_config;
package/workers/index.js CHANGED
@@ -1,8 +1,43 @@
1
+ const pathUtil = require('path');
1
2
  const Multithread = require('./Multithread');
2
3
  const backwardv1 = require('../utils/backward-v1')
3
4
 
4
- const multiTasks = (config)=>{
5
+ const start = (config)=>{
5
6
  config = backwardv1.v1tov2(config);
6
7
  Multithread.start(config);
7
8
  };
9
+ const resume = (config)=>{
10
+ config = backwardv1.v1tov2(config);
11
+ let {taskFolder} = config;
12
+ if(!taskFolder){
13
+ console.log('[ERROR]', 'method resume need param "taskFolder"')
14
+ return;
15
+ }
16
+ let pathinfo = pathUtil.parse(taskFolder);
17
+ let taskId = pathinfo.name;
18
+ let taskRootFolder = pathinfo.dir;
19
+
20
+ config.taskId = taskId;
21
+ config.taskRootFolder = taskRootFolder;
22
+ config.__resume = true;
23
+
24
+ Multithread.start(config);
25
+ };
26
+ const restart = (config)=>{
27
+ config = backwardv1.v1tov2(config);
28
+ let {taskFolder} = config;
29
+ if(!taskFolder){
30
+ console.log('[ERROR]', 'method resume need param "taskFolder"')
31
+ return;
32
+ }
33
+ };
34
+
35
+ const multiTasks = (config)=>{
36
+ start(config);
37
+ };
38
+
39
+ multiTasks.start = start;
40
+ multiTasks.restart = restart;
41
+ multiTasks.resume = resume;
42
+
8
43
  module.exports = multiTasks;
@@ -1,176 +0,0 @@
1
- let fs = require('fs');
2
- let pathutil = require('path');
3
- const { spawn, exec } = require('child_process');
4
- let SuperConsumer = require('../index').SuperConsumer;
5
- let localUtil = require('./util');
6
- const makeDir = require('../utils/makedir');
7
-
8
- let thisroot = pathutil.parse(__filename).dir;
9
- let garbagedir = pathutil.resolve(thisroot,`./tmp-garbage`);
10
- makeDir.sync(garbagedir);
11
-
12
- let loadTaskFile = (dir)=>{
13
- var results = []
14
- var list = fs.readdirSync(dir);
15
- list.forEach(function(file) {
16
- var stat;
17
- try{
18
- stat = fs.statSync(pathutil.resolve(dir, file))
19
- }catch(e){
20
- //Must try-catch, the file maybe deleted by other process
21
- }
22
- if (stat && !stat.isDirectory()) results.push({
23
- file,
24
- fpath: pathutil.resolve(dir, file)
25
- });
26
- })
27
- //console.log(results);
28
- return results;
29
- };
30
- let processTask = (moduleName, requestVersion, callback)=>{
31
- let outputs = [];
32
- let ls = spawn(`ls`, [], {cwd: garbagedir, shell: true});
33
- ls.stdout.on('data', (data) => {
34
- let stdout = data.toString();
35
- outputs.push(stdout);
36
- //console.log('[stdout]', stdout)
37
- });
38
- ls.stderr.on('data', (data) => {
39
- let stdout = data.toString();
40
- //console.log('[stderr]', stdout)
41
- outputs.push(stdout);
42
- });
43
- ls.on('close', (code) => {
44
- //console.log('[close]', code)
45
- // if(code === 1) {
46
- // console.log('[error], checkout failed!!')
47
- // }
48
- let succ = true;
49
- let logTxt = outputs.join('');
50
-
51
- if(logTxt.indexOf('ERR!')>=0 && logTxt.match(/ERR\!/g)) {
52
- succ = false;
53
- }
54
- let logJson;
55
- try{
56
- logJson = JSON.parse(logTxt);
57
- }catch(e){
58
- console.log(e)
59
- console.log(`at===`, moduleName, requestVersion)
60
- console.log(`logTxt===`, logTxt)
61
- succ = false;
62
- }
63
- if(succ && logJson){
64
- if(!Array.isArray(logJson)){
65
- logJson = [logJson];
66
- };
67
- let data = {
68
- response: logJson,
69
- __info__: {
70
- moduleName,
71
- requestVersion,
72
- }
73
- };
74
- callback(true, data);
75
- }else{
76
- callback(false, logTxt);
77
- }
78
- });
79
-
80
- }
81
-
82
- let _do = (mainTaskInfo, actions, callback)=>{
83
- let{dynamicTasksFolderNew, dynamicTasksFolderSucc, dynamicTasksFolderFail} = mainTaskInfo.defination;
84
- let tasks = loadTaskFile(dynamicTasksFolderNew);
85
- if(tasks.length === 0) return callback({data:'hahaha'+Math.random()});//stop!
86
-
87
- tasks = localUtil.shuffleAnArray(tasks);//avoid file IO conflicts
88
-
89
- let task;
90
- let data;
91
- while(!data){
92
- task = tasks.shift();
93
- try{
94
- data = JSON.parse(fs.readFileSync(task.fpath, 'utf8'));
95
- }catch(e){
96
- console.log('xxxx')
97
- }
98
- }
99
-
100
- if(task && data){
101
- let fpath = task.fpath;
102
- {
103
- //processing...
104
- let {moduleName, requestVersion, fullnameMd5} = data.info;
105
- let oldfPath = fpath;
106
- let newfPath = pathutil.resolve(dynamicTasksFolderNew, `${fullnameMd5}.json`);
107
- let succfPath = pathutil.resolve(dynamicTasksFolderSucc, `${fullnameMd5}.json`);
108
- let errfPath = pathutil.resolve(dynamicTasksFolderFail, `${fullnameMd5}.json`);
109
- if(fs.existsSync(succfPath) || fs.existsSync(errfPath)) {
110
- console.log('exist...', process.pid, fullnameMd5);
111
- if(fs.existsSync(newfPath))
112
- try{
113
- fs.unlinkSync(newfPath);
114
- }catch(e){
115
- console.log('remove failed', process.pid, newfPath, e);
116
- }
117
- _do(mainTaskInfo, actions, callback);
118
- }else {
119
- console.log('processing...', process.pid, fpath);
120
- processTask(moduleName, requestVersion, (succ, logJson)=>{
121
- if(succ){
122
- fs.writeFileSync(succfPath, JSON.stringify(logJson));
123
- logJson.response.forEach((item)=>{
124
- if(item.dependencies){
125
- for(let module2 in item.dependencies){
126
- let version2 = item.dependencies[module2];
127
- console.log('found new task', module2, version2);
128
- localUtil.createNewTask(module2, version2, dynamicTasksFolderNew);//find new one, create a new task file
129
- }
130
- }
131
- });
132
- }else{
133
- console.log('ERR! failed', moduleName, requestVersion);
134
- fs.writeFileSync(pathutil.resolve(dynamicTasksFolderFail, `${fullnameMd5}.json`), JSON.stringify({
135
- moduleName, requestVersion,
136
- msg: logJson
137
- }));
138
- }
139
- _do(mainTaskInfo, actions, callback);
140
- });
141
- }
142
- if(fs.existsSync(oldfPath)){
143
- try{
144
- fs.unlinkSync(oldfPath);
145
- }catch(e){
146
- console.log('remove failed', process.pid, oldfPath, e);
147
- }
148
- }
149
- }
150
- }else{
151
- console.log('__done2', process.pid)
152
- return callback({data:'hahaha'+Math.random()});//stop!
153
- }
154
- };
155
-
156
-
157
- class TestConsumer extends SuperConsumer {
158
- // constructor
159
- constructor(taskname, parentfolder) {
160
- super(taskname, parentfolder)
161
-
162
- }
163
- setTaskTrunkInfo(trunkInfo){
164
- this.trunkInfo = trunkInfo;
165
- }
166
- run (mainTaskInfo, subTaskInfo, taskInfo, callback){
167
- //console.log(`this.trunkInfo===`, this.trunkInfo)
168
- let actions = subTaskInfo.actions;
169
- mainTaskInfo.defination = this.trunkInfo;
170
- _do(mainTaskInfo, actions, (result)=>{
171
- callback(result)
172
- })
173
-
174
- }
175
- }
176
- module.exports = TestConsumer;
@@ -1,49 +0,0 @@
1
- let SuperProducer = require('../index').SuperProducer;
2
- const fs = require('fs');
3
- let pathutil = require('path');
4
-
5
- let thisroot = pathutil.parse(__filename).dir;
6
-
7
- let localUtil = require('./util')
8
-
9
-
10
- let createInitialTasks = (info)=>{
11
- let succList = JSON.parse(fs.readFileSync(pathutil.resolve(thisroot,'../tmp/version_succList.json'), 'utf8'));
12
- //succList = succList.slice(0, 20);
13
- succList.forEach((item)=>{
14
- let {moduleName, requestVersion} = item;
15
- localUtil.createNewTask(moduleName, requestVersion, info.dynamicTasksFolderNew);
16
- });
17
- };
18
-
19
-
20
-
21
- class TestProducer extends SuperProducer {
22
-
23
- // constructor
24
- constructor(taskname, parentfolder) {
25
- super(taskname, parentfolder)
26
-
27
- }
28
- setTaskTrunkInfo(trunkInfo){
29
- this.trunkInfo = trunkInfo;
30
- }
31
- getAllActions() {
32
- createInitialTasks(this.trunkInfo);
33
- return new Promise((resolve, reject)=>{
34
- let trunkInfo = this.trunkInfo;
35
- let {NUMBER_OF_WORKS} = trunkInfo;
36
- //console.log(this.trunkInfo)
37
- //throw repos.length+'ff'+repos2.length
38
- //here, IGNORED_repoRegexStringList
39
- let alltasks = []
40
- for(let i=0;i<NUMBER_OF_WORKS;i++){//the task was driven by files, not here
41
- alltasks.push({
42
- num:i
43
- })
44
- }
45
- resolve(alltasks);
46
- })
47
- }
48
- }
49
- module.exports = TestProducer;
@@ -1,36 +0,0 @@
1
- let fs = require('fs');
2
- let pathutil = require('path');
3
- let makeDir = require('../utils/makedir');
4
- let RunnerProducer = require('./RunnerProducer');//switch to require from node_modules in your project
5
- let RunnerConsumer = require('./RunnerConsumer');//switch to require from node_modules in your project
6
-
7
- let thisroot = pathutil.parse(__filename).dir;
8
- let taskId = `workers_of_${Math.random()}`;
9
- let multi_task_parent_folder = pathutil.resolve(thisroot, `../../example2_dynamicly_increasing`);
10
-
11
- let producer = new RunnerProducer(taskId, multi_task_parent_folder)
12
- let consumer = new RunnerConsumer();
13
- let master = require('../index').master;
14
-
15
- console.log(producer.getTaskName())
16
- //return;
17
-
18
- let NUMBER_OF_WORKS = 5;//LOCK NUMBER OF WORKERS
19
-
20
- let taskConfig = {
21
- NUMBER_OF_WORKS,
22
- dynamicTasksFolderNew: pathutil.resolve(multi_task_parent_folder, `./dynamicTasks-NEW`),
23
- dynamicTasksFolderSucc: pathutil.resolve(multi_task_parent_folder, `./dynamicTasks-SUCC`),
24
- dynamicTasksFolderFail: pathutil.resolve(multi_task_parent_folder, `./dynamicTasks-FAIL`),
25
- }
26
- makeDir.sync(taskConfig.dynamicTasksFolderNew)
27
- makeDir.sync(taskConfig.dynamicTasksFolderSucc)
28
- makeDir.sync(taskConfig.dynamicTasksFolderFail)
29
-
30
-
31
- producer.setTaskTrunkInfo(taskConfig);
32
- consumer.setTaskTrunkInfo(taskConfig)
33
- master.run(producer, consumer, {
34
- quotaOfEachTask:1, //<--Lock as one
35
- numberOfWorkers:NUMBER_OF_WORKS
36
- });
@@ -1,46 +0,0 @@
1
- const md5 = require('md5');
2
- const fs = require('fs');
3
- let pathutil = require('path');
4
-
5
- let getFileName = (moduleName, requestVersion)=>{
6
- let fullname = `${moduleName}@${requestVersion}`;
7
- let fullnameMd5 = md5(fullname);
8
- return fullnameMd5;
9
- };
10
- let createNewTask = (moduleName, requestVersion, dynamicTasksFolderNew)=>{
11
- let fullnameMd5 = getFileName(moduleName, requestVersion);
12
- fs.writeFileSync(pathutil.resolve(dynamicTasksFolderNew, `./${fullnameMd5}.json`), JSON.stringify({
13
- info:{
14
- moduleName,
15
- requestVersion,
16
- fullnameMd5,
17
- }
18
- }));
19
- };
20
- function shuffleAnArray(array) {
21
- if(array.length === 0 || array.length === 1) return array;
22
- let currentIndex = array.length, randomIndex;
23
-
24
- // While there remain elements to shuffle.
25
- while (currentIndex > 0) {
26
-
27
- // Pick a remaining element.
28
- randomIndex = Math.floor(Math.random() * currentIndex);
29
- currentIndex--;
30
-
31
- // And swap it with the current element.
32
- let tmp = array[currentIndex];
33
- array[currentIndex] = array[randomIndex];
34
- array[randomIndex] = tmp;
35
-
36
- // [array[currentIndex], array[randomIndex]] = [
37
- // array[randomIndex], array[currentIndex]];
38
- }
39
-
40
- return array;
41
- }
42
- module.exports = {
43
- createNewTask,
44
- getFileName,
45
- shuffleAnArray
46
- };