multi-tasks 3.2.2 → 3.3.0
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 -7
- package/package.json +1 -1
- package/utils/progressCtl.js +77 -8
- package/utils/progressView.js +85 -18
- package/utils/resolvePriority.js +37 -0
- package/workers/Multithread.js +495 -447
- package/workers/TaskMgr.js +380 -293
- package/workers/asMaster.js +101 -100
package/workers/TaskMgr.js
CHANGED
|
@@ -1,294 +1,381 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const pathutil = require('path');
|
|
3
|
-
const makeDir = require('../utils/makedir');
|
|
4
|
-
const randomsUtil = require('../utils/randoms');
|
|
5
|
-
const { serializeConfig } = require('../utils/serializeFns');
|
|
6
|
-
const { forEachResult } = require('../utils/foreachResults');
|
|
7
|
-
const runLog = require('../utils/runLog');
|
|
8
|
-
|
|
9
|
-
let allSubTasks = {
|
|
10
|
-
new:[],
|
|
11
|
-
running:[],
|
|
12
|
-
finished:[],
|
|
13
|
-
finished_with_errors:[]
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
let main_config;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
let {taskFolderResult,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const pathutil = require('path');
|
|
3
|
+
const makeDir = require('../utils/makedir');
|
|
4
|
+
const randomsUtil = require('../utils/randoms');
|
|
5
|
+
const { serializeConfig } = require('../utils/serializeFns');
|
|
6
|
+
const { forEachResult } = require('../utils/foreachResults');
|
|
7
|
+
const runLog = require('../utils/runLog');
|
|
8
|
+
|
|
9
|
+
let allSubTasks = {
|
|
10
|
+
new:[],
|
|
11
|
+
running:[],
|
|
12
|
+
finished:[],
|
|
13
|
+
finished_with_errors:[]
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let main_config;
|
|
17
|
+
|
|
18
|
+
//.sys/user_messages 消息文件命名规范(严谨, 解析依赖以下两条不变量):
|
|
19
|
+
// 文件名 = <type>.<subid>.json; type 是库内机器常量, 只允许 [a-z0-9_](必须不含点), 写入时有同样校验;
|
|
20
|
+
// subid 是首个点之后的全部(可含点, 非空)。type 无点 ⇒ 按首个点切分无歧义
|
|
21
|
+
const MESSAGE_TYPE_PATTERN = /^[a-z0-9_]+$/;
|
|
22
|
+
//解析消息文件名 → subid; 不满足命名规范的文件(脏文件)返回 null, 调用方按无消息处理
|
|
23
|
+
const parseMessageSubid = (fname)=>{
|
|
24
|
+
if(!fname.endsWith('.json')){
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
let body = fname.slice(0, -'.json'.length);
|
|
28
|
+
let dotIndex = body.indexOf('.');
|
|
29
|
+
if(dotIndex <= 0 || dotIndex === body.length - 1){//无点/点开头/subid 为空, 均不合规
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
if(!MESSAGE_TYPE_PATTERN.test(body.slice(0, dotIndex))){
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
return body.slice(dotIndex + 1);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const TaskMgr = {
|
|
39
|
+
load:(config)=>{
|
|
40
|
+
main_config = config;
|
|
41
|
+
return TaskMgr;
|
|
42
|
+
},
|
|
43
|
+
initFolders:()=>{
|
|
44
|
+
let {taskRootFolder, masterTaskId} = main_config;
|
|
45
|
+
const taskFolder = pathutil.resolve(taskRootFolder, masterTaskId);
|
|
46
|
+
if(fs.existsSync(taskFolder) && !(main_config.__resume || main_config.__retryFails || main_config.__restart)){
|
|
47
|
+
let files = fs.readdirSync(taskFolder);
|
|
48
|
+
if(files.length > 0){
|
|
49
|
+
console.log('[FATAL] taskRootFolder already exist and not empty, delete it and try again: ', taskFolder);
|
|
50
|
+
return process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if(main_config.__restart && fs.existsSync(taskFolder)){
|
|
54
|
+
TaskMgr.resetTaskFolders(taskFolder);//restart: 整目录重置, 随后由下方 makeDir 重建
|
|
55
|
+
};
|
|
56
|
+
const progressRootFolder = pathutil.resolve(taskFolder, './progress');
|
|
57
|
+
const resultsRootFolder = pathutil.resolve(taskFolder, './results');
|
|
58
|
+
const logsRootFolder = pathutil.resolve(taskFolder, './logs');
|
|
59
|
+
const sysRootFolder = pathutil.resolve(taskFolder, './.sys');//库自身运行日志(run.log)等系统文件, 与任务数据/结果隔离
|
|
60
|
+
const userMessagesFolder = pathutil.resolve(sysRootFolder, './user_messages');//用户可见问题消息(一任务一问题一文件), monitor 工具可读
|
|
61
|
+
|
|
62
|
+
const taskFolderNew = pathutil.resolve(progressRootFolder, './new');
|
|
63
|
+
const taskFolderRunning = pathutil.resolve(progressRootFolder, './running');
|
|
64
|
+
const taskFolderFinished = pathutil.resolve(progressRootFolder, './finished');
|
|
65
|
+
const taskFolderFailed= pathutil.resolve(progressRootFolder, './finished_with_errors');
|
|
66
|
+
|
|
67
|
+
const taskFolderResult = pathutil.resolve(resultsRootFolder, './succ');
|
|
68
|
+
const taskFolderResultFailed = pathutil.resolve(resultsRootFolder, './errors');
|
|
69
|
+
makeDir.sync(logsRootFolder);
|
|
70
|
+
makeDir.sync(sysRootFolder);
|
|
71
|
+
makeDir.sync(userMessagesFolder);
|
|
72
|
+
makeDir.sync(taskFolderNew);
|
|
73
|
+
makeDir.sync(taskFolderRunning);
|
|
74
|
+
makeDir.sync(taskFolderFinished);
|
|
75
|
+
makeDir.sync(taskFolderFailed);
|
|
76
|
+
makeDir.sync(taskFolderResult);
|
|
77
|
+
makeDir.sync(taskFolderResultFailed);
|
|
78
|
+
|
|
79
|
+
main_config = {
|
|
80
|
+
...main_config,
|
|
81
|
+
taskFolder,
|
|
82
|
+
progressRootFolder,
|
|
83
|
+
resultsRootFolder,
|
|
84
|
+
logsRootFolder,
|
|
85
|
+
sysRootFolder,
|
|
86
|
+
userMessagesFolder,
|
|
87
|
+
//run.log 路径唯一兜底计算点: 入口(workers/index.js)没算过时在这里补(__ 前缀, 不进快照)
|
|
88
|
+
__sysLogFile: main_config.__sysLogFile || pathutil.resolve(sysRootFolder, 'run.log'),
|
|
89
|
+
|
|
90
|
+
taskFolderNew,
|
|
91
|
+
taskFolderRunning,
|
|
92
|
+
taskFolderFinished,
|
|
93
|
+
taskFolderFailed,
|
|
94
|
+
taskFolderResult,
|
|
95
|
+
taskFolderResultFailed,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//for resuming an interrupted task
|
|
99
|
+
let main_config2 = JSON.parse(JSON.stringify(serializeConfig(main_config)));//函数型配置键先序列化为源码字符串, 供 resume 从快照复活
|
|
100
|
+
delete main_config2.tasks;//will load from folder 'new', so no need the early tasks data
|
|
101
|
+
Object.keys(main_config2).forEach((key)=>{
|
|
102
|
+
if(key.startsWith('__')) delete main_config2[key];//__-prefixed keys are one-shot runtime markers, never persisted
|
|
103
|
+
});
|
|
104
|
+
fs.writeFileSync(pathutil.resolve(taskFolder, 'task_config.json'), JSON.stringify(main_config2));
|
|
105
|
+
return main_config;
|
|
106
|
+
},
|
|
107
|
+
createNewTask:(task)=>{
|
|
108
|
+
const {masterTaskId, taskFolderNew} = main_config;
|
|
109
|
+
const subid = randomsUtil.get36Id();
|
|
110
|
+
|
|
111
|
+
task.masterTaskId = masterTaskId;
|
|
112
|
+
task.subid = `${masterTaskId}--${subid}`;
|
|
113
|
+
let taskfilepath = pathutil.resolve(taskFolderNew, `./${task.subid}.task.json`);
|
|
114
|
+
fs.writeFileSync(taskfilepath, JSON.stringify(task));
|
|
115
|
+
},
|
|
116
|
+
loadAllTasks:(loadtype)=>{
|
|
117
|
+
const {progressRootFolder} = main_config;
|
|
118
|
+
let load = (type)=>{
|
|
119
|
+
allSubTasks[type] = [];
|
|
120
|
+
return new Promise((resolve)=>{
|
|
121
|
+
let folder = pathutil.resolve(progressRootFolder, type);
|
|
122
|
+
makeDir.getDirectFiles(folder, (taskFiles)=>{
|
|
123
|
+
taskFiles.forEach((filename)=>{
|
|
124
|
+
let fpath = pathutil.resolve(folder, filename);
|
|
125
|
+
let fcontent;
|
|
126
|
+
try{
|
|
127
|
+
fcontent = fs.readFileSync(fpath, 'utf-8');
|
|
128
|
+
}catch(e){
|
|
129
|
+
runLog.warn('[WARN]', `file not found`, fpath)
|
|
130
|
+
}
|
|
131
|
+
if(fcontent){
|
|
132
|
+
try{
|
|
133
|
+
let task = JSON.parse(fcontent);
|
|
134
|
+
allSubTasks[type].push(task);
|
|
135
|
+
}catch(e){
|
|
136
|
+
runLog.warn('[WARN]', 'failed to parse task file, skipped', fpath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
resolve(allSubTasks[type]);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
let promises = [];
|
|
145
|
+
if(typeof loadtype === 'undefined'){
|
|
146
|
+
for(let type in allSubTasks){
|
|
147
|
+
promises.push(load(type));
|
|
148
|
+
}
|
|
149
|
+
}else{
|
|
150
|
+
promises.push(load(loadtype));
|
|
151
|
+
}
|
|
152
|
+
return Promise.all(promises);
|
|
153
|
+
},
|
|
154
|
+
loadNewTasks:()=>{
|
|
155
|
+
return TaskMgr.loadAllTasks('new');
|
|
156
|
+
},
|
|
157
|
+
updateNewTasks:()=>{
|
|
158
|
+
let p;
|
|
159
|
+
if(allSubTasks.new.length === 0){
|
|
160
|
+
p = TaskMgr.loadNewTasks();//load again
|
|
161
|
+
}else{
|
|
162
|
+
p = Promise.resolve();
|
|
163
|
+
}
|
|
164
|
+
return p;
|
|
165
|
+
},
|
|
166
|
+
popNewTask:()=>{
|
|
167
|
+
return new Promise((resolve)=>{
|
|
168
|
+
let newtask = allSubTasks.new.shift();
|
|
169
|
+
if(newtask){
|
|
170
|
+
TaskMgr.mvTask(newtask.subid, 'new', 'running', (newPath, succ)=>{
|
|
171
|
+
if(succ){
|
|
172
|
+
resolve(newtask);
|
|
173
|
+
}else{
|
|
174
|
+
allSubTasks.new.unshift(newtask);
|
|
175
|
+
resolve(null);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}else{
|
|
179
|
+
resolve(null);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
getTask:(type, id)=>{
|
|
184
|
+
let result;
|
|
185
|
+
let arr = allSubTasks[type];
|
|
186
|
+
if(!arr){
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
for(let i=0;i<arr.length;i++){
|
|
190
|
+
let task = arr[i];
|
|
191
|
+
if(task.subid === id){
|
|
192
|
+
result = task;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
},
|
|
198
|
+
getTasks:(type)=>{
|
|
199
|
+
return allSubTasks[type];
|
|
200
|
+
},
|
|
201
|
+
getTaskFiles:(type, callback)=>{
|
|
202
|
+
let rootdir = main_config.progressRootFolder;
|
|
203
|
+
let targetFolder = pathutil.resolve(rootdir, `./${type}`);
|
|
204
|
+
makeDir.getDirectFiles(targetFolder, (files)=>{
|
|
205
|
+
callback(files);
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
getFinalStats:()=>{//master 收尾用: 以盘上终态为准统计成功/失败(resume 跨轮累计也是对的)
|
|
209
|
+
const {taskFolder, taskFolderFinished, taskFolderFailed, userMessagesFolder} = main_config;
|
|
210
|
+
const listSubids = (folder)=>{
|
|
211
|
+
return fs.readdirSync(folder)
|
|
212
|
+
.filter((fname)=>fname.endsWith('.task.json'))
|
|
213
|
+
.map((fname)=>fname.replace(/\.task\.json$/, ''));
|
|
214
|
+
};
|
|
215
|
+
const listMessageSubids = ()=>{//目录缺失(旧版本任务文件夹)按无消息计; 不合规文件名剔除, 防空 subid 虚增计数
|
|
216
|
+
try{
|
|
217
|
+
return fs.readdirSync(userMessagesFolder)
|
|
218
|
+
.map((fname)=>parseMessageSubid(fname))
|
|
219
|
+
.filter((subid)=>subid);
|
|
220
|
+
}catch(e){
|
|
221
|
+
return [];
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
const failedTasks = listSubids(taskFolderFailed);
|
|
225
|
+
return {
|
|
226
|
+
taskFolder,
|
|
227
|
+
succeeded: listSubids(taskFolderFinished).length,
|
|
228
|
+
failed: failedTasks.length,
|
|
229
|
+
failedTasks,
|
|
230
|
+
succButSaveFailed: new Set(listMessageSubids()).size,//任务成功但结果保存失败的任务数(一任务多问题只计一次)
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
mvTaskPromise: (subid, from, to)=> {
|
|
234
|
+
return new Promise((resolve, reject)=>{
|
|
235
|
+
TaskMgr.mvTask(subid, from, to, (newPath, succ)=>{
|
|
236
|
+
succ ? resolve(newPath) : reject(newPath);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
mvTask: (subid, from, to, callback)=> {
|
|
241
|
+
runLog.write('Move task:', subid, `${from}>${to}`);
|
|
242
|
+
let {progressRootFolder} = main_config;
|
|
243
|
+
let oldPath = pathutil.resolve(progressRootFolder, `./${from}/${subid}.task.json`);
|
|
244
|
+
let newPath = pathutil.resolve(progressRootFolder, `./${to}/${subid}.task.json`);
|
|
245
|
+
try{
|
|
246
|
+
fs.rename(oldPath, newPath, (err) => {
|
|
247
|
+
if (err) {
|
|
248
|
+
callback(newPath, false);
|
|
249
|
+
}else{
|
|
250
|
+
callback(newPath, true);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}catch(e){
|
|
254
|
+
runLog.warn(e);
|
|
255
|
+
callback(newPath, false);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
saveTaskResult:(subid, hasError, data)=>{
|
|
259
|
+
//数据不可 JSON 序列化时不抛错(抛错会把任务吞掉)也不翻转成败: 任务跑通了只是结果存不下,
|
|
260
|
+
//结果文件改写为说明占位(成功侧对象本体, 错误侧包成数组), 任务状态不受影响; 同步写用户消息文件
|
|
261
|
+
if(typeof data !== 'undefined'){
|
|
262
|
+
try{
|
|
263
|
+
let json = JSON.stringify(data);
|
|
264
|
+
if(typeof json !== 'string'){//函数/Symbol: stringify 返回 undefined 而不抛错, 同样视同序列化失败(否则 writeFileSync 抛 TypeError, 任务滞留 running 静默丢失)
|
|
265
|
+
throw new Error('result value is not JSON-serializable (function or symbol)');
|
|
266
|
+
};
|
|
267
|
+
}catch(serializeErr){
|
|
268
|
+
runLog.error('[Task Error] failed to serialize task result', subid, serializeErr);
|
|
269
|
+
let placeholder = {
|
|
270
|
+
type: 'result_serialize_error',
|
|
271
|
+
message: serializeErr && serializeErr.message ? serializeErr.message : String(serializeErr),
|
|
272
|
+
};
|
|
273
|
+
data = hasError ? [placeholder] : placeholder;
|
|
274
|
+
TaskMgr.saveUserMessage({
|
|
275
|
+
type: 'result_serialize_error',
|
|
276
|
+
level: 'warn',//结果写失败: 用户需关注的警告级问题
|
|
277
|
+
subid,
|
|
278
|
+
side: hasError ? 'error' : 'result',
|
|
279
|
+
message: placeholder.message,
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
let {taskFolderResult, taskFolderResultFailed} = main_config;
|
|
284
|
+
let folder = hasError ? taskFolderResultFailed : taskFolderResult;
|
|
285
|
+
let fpath = pathutil.resolve(folder, `${subid}.json`);
|
|
286
|
+
let fcontent;
|
|
287
|
+
if(typeof data === 'undefined') {
|
|
288
|
+
fcontent = 'undefined';
|
|
289
|
+
}else{
|
|
290
|
+
fcontent = JSON.stringify(data);
|
|
291
|
+
};
|
|
292
|
+
fs.writeFileSync(fpath, fcontent);
|
|
293
|
+
},
|
|
294
|
+
saveUserMessage:({type, subid, ...rest})=>{
|
|
295
|
+
//用户可见问题消息: 一任务一问题一文件(<type>.<subid>.json), JSON 格式, monitor 工具可读;
|
|
296
|
+
//每条必带 level(默认 info, 调用方可按严重度覆盖, 如结果写失败为 warn);
|
|
297
|
+
//尽力而为: 写失败只记日志, 不干扰任务主流程
|
|
298
|
+
try{
|
|
299
|
+
if(!MESSAGE_TYPE_PATTERN.test(type)){//type 含点会让文件名按首点切分出错的(见文件头命名规范), 拒写并显式报错
|
|
300
|
+
throw new Error(`invalid user message type "${type}": must match ${MESSAGE_TYPE_PATTERN}`);
|
|
301
|
+
};
|
|
302
|
+
let {userMessagesFolder} = main_config;
|
|
303
|
+
let fpath = pathutil.resolve(userMessagesFolder, `${type}.${subid}.json`);
|
|
304
|
+
let message = {type, subid, level: 'info', ...rest, timestamp: randomsUtil.getDateTimeTxt()};
|
|
305
|
+
fs.writeFileSync(fpath, JSON.stringify(message, null, 2) + '\n');
|
|
306
|
+
}catch(e){
|
|
307
|
+
runLog.error('[Task Error] failed to save user message', type, subid, e);
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
deleteUserMessage:(subid)=>{
|
|
311
|
+
//任务重跑(retry_fails/maxTaskRetries 重试)时清掉它在 .sys/user_messages 的全部类型消息,
|
|
312
|
+
//防前序尝试的遗留消息在新一轮成功后仍是脏数据; 尽力而为: 失败只记日志不干扰主流程
|
|
313
|
+
try{
|
|
314
|
+
let {userMessagesFolder} = main_config;
|
|
315
|
+
fs.readdirSync(userMessagesFolder)
|
|
316
|
+
.filter((fname)=>parseMessageSubid(fname) === subid)//按文件名解析精确比对, 后缀匹配会误删带点 subid 的他人消息
|
|
317
|
+
.forEach((fname)=>{
|
|
318
|
+
fs.unlinkSync(pathutil.resolve(userMessagesFolder, fname));
|
|
319
|
+
});
|
|
320
|
+
}catch(e){
|
|
321
|
+
runLog.error('[Task Error] failed to delete user message', subid, e);
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
getSavedResultType:(subid)=>{
|
|
325
|
+
let {taskFolderResult, taskFolderResultFailed} = main_config;
|
|
326
|
+
let succPath = pathutil.resolve(taskFolderResult, `${subid}.json`);
|
|
327
|
+
let errPath = pathutil.resolve(taskFolderResultFailed, `${subid}.json`);
|
|
328
|
+
if(fs.existsSync(succPath)) return 'succ';
|
|
329
|
+
if(fs.existsSync(errPath)) return 'error';
|
|
330
|
+
return null;
|
|
331
|
+
},
|
|
332
|
+
saveTaskLog:(subid, log)=>{
|
|
333
|
+
let {logsRootFolder} = main_config;
|
|
334
|
+
let folder = logsRootFolder;
|
|
335
|
+
let fpath = pathutil.resolve(folder, `${subid}.json`);
|
|
336
|
+
let fcontent;
|
|
337
|
+
if(typeof log === 'undefined') {
|
|
338
|
+
fcontent = '';
|
|
339
|
+
}else{
|
|
340
|
+
log.subid = subid;
|
|
341
|
+
fcontent = JSON.stringify(log);
|
|
342
|
+
};
|
|
343
|
+
fs.writeFileSync(fpath, fcontent);
|
|
344
|
+
},
|
|
345
|
+
incrementTaskRetry:(subid)=>{
|
|
346
|
+
let {taskFolderRunning} = main_config;
|
|
347
|
+
let fpath = pathutil.resolve(taskFolderRunning, `./${subid}.task.json`);
|
|
348
|
+
let task = JSON.parse(fs.readFileSync(fpath, 'utf-8'));
|
|
349
|
+
task.__retryCount = (task.__retryCount || 0) + 1;
|
|
350
|
+
fs.writeFileSync(fpath, JSON.stringify(task));
|
|
351
|
+
return task.__retryCount;
|
|
352
|
+
},
|
|
353
|
+
deleteSavedResult:(subid)=>{
|
|
354
|
+
let {taskFolderResult, taskFolderResultFailed} = main_config;
|
|
355
|
+
[taskFolderResult, taskFolderResultFailed].forEach((folder)=>{
|
|
356
|
+
let fpath = pathutil.resolve(folder, `${subid}.json`);
|
|
357
|
+
if(fs.existsSync(fpath)){
|
|
358
|
+
fs.unlinkSync(fpath);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
resetTaskRetry:(subid)=>{
|
|
363
|
+
let {taskFolderNew} = main_config;
|
|
364
|
+
let fpath = pathutil.resolve(taskFolderNew, `./${subid}.task.json`);
|
|
365
|
+
let task = JSON.parse(fs.readFileSync(fpath, 'utf-8'));
|
|
366
|
+
delete task.__retryCount;
|
|
367
|
+
fs.writeFileSync(fpath, JSON.stringify(task));
|
|
368
|
+
},
|
|
369
|
+
forEachSuccResult:(cb)=>{
|
|
370
|
+
let {taskFolderResult, taskFolderFinished} = main_config;
|
|
371
|
+
forEachResult(taskFolderResult, taskFolderFinished, cb);
|
|
372
|
+
},
|
|
373
|
+
forEachErrorResult:(cb)=>{
|
|
374
|
+
let {taskFolderResultFailed, taskFolderFailed} = main_config;
|
|
375
|
+
forEachResult(taskFolderResultFailed, taskFolderFailed, cb);
|
|
376
|
+
},
|
|
377
|
+
resetTaskFolders:(taskFolder)=>{
|
|
378
|
+
fs.rmSync(taskFolder, {recursive: true, force: true});
|
|
379
|
+
},
|
|
380
|
+
};
|
|
294
381
|
module.exports = TaskMgr;
|