multi-tasks 3.4.1 → 3.5.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 +325 -277
- package/package.json +1 -1
- package/utils/startupBanner.js +51 -51
- package/workers/IpcTaskStore.js +136 -0
- package/workers/MemoryStore.js +247 -0
- package/workers/Multithread.js +203 -49
- package/workers/TaskMgr.js +406 -406
- package/workers/asMaster.js +109 -101
- package/workers/helper.js +77 -67
- package/workers/index.js +28 -0
package/workers/Multithread.js
CHANGED
|
@@ -7,6 +7,7 @@ let MSG_NO_TASK_FOUND = 'no_task_found';
|
|
|
7
7
|
|
|
8
8
|
const asMaster = require('./asMaster');
|
|
9
9
|
const TaskMgr = require('./TaskMgr');
|
|
10
|
+
const IpcTaskStore = require('./IpcTaskStore');
|
|
10
11
|
const WorkerMgr = require('./WorkerMgr');
|
|
11
12
|
const helper = require('./helper');
|
|
12
13
|
const { resolveNumberOfWorkers } = require('../utils/resolveWorkers');
|
|
@@ -58,6 +59,53 @@ const start = (config)=>{
|
|
|
58
59
|
const masterStartTimestamp = new Date()*1;//本轮 master 启动时间(resume 不追溯首轮)
|
|
59
60
|
let workerCrashCount = 0;//累计崩溃补员次数, 进 onFinish report
|
|
60
61
|
|
|
62
|
+
let memoryStore = null;//memory 档: 任务状态/结果全在 master 内存(file 档恒为 null, 走 TaskMgr 磁盘链路)
|
|
63
|
+
let getStats = ()=> memoryStore ? memoryStore.stats() : TaskMgr.getFinalStats();
|
|
64
|
+
let getRemaining = ()=> memoryStore ? memoryStore.getRemaining() : TaskMgr.getTasks('new').length;
|
|
65
|
+
|
|
66
|
+
//memory 档 worker 存储操作的应用点: 只在 __storageOp 分支的 queue.enqueue 里调(与派发同一串行通道,
|
|
67
|
+
//同 worker 的操作消息排在其后续领新请求之前, pop 时 Map 必已含该 worker 此前创建的全部任务)
|
|
68
|
+
let applyStorageOp = (worker, message)=>{
|
|
69
|
+
let op = message.op;
|
|
70
|
+
let args = (message.args || []).map((value)=>{
|
|
71
|
+
return (value && value[IpcTaskStore.__UNDEFINED_SENTINEL]) ? undefined : value;
|
|
72
|
+
});
|
|
73
|
+
let ackValue;
|
|
74
|
+
let ok = true;
|
|
75
|
+
let error = null;
|
|
76
|
+
try{
|
|
77
|
+
switch(op){
|
|
78
|
+
case 'createTask':
|
|
79
|
+
memoryStore.add(args[0]);
|
|
80
|
+
break;
|
|
81
|
+
case 'saveLog': memoryStore.saveLog(args[0], args[1]); break;
|
|
82
|
+
case 'saveResult': memoryStore.saveResult(args[0], args[1], args[2]); break;
|
|
83
|
+
case 'deleteResult': memoryStore.deleteResult(args[0]); break;
|
|
84
|
+
case 'mvTask': {
|
|
85
|
+
let succ = memoryStore.move(args[0], args[1], args[2]);
|
|
86
|
+
ackValue = {newPath: `${args[2]}/${args[0]}.task.json`, succ};
|
|
87
|
+
break;
|
|
88
|
+
};
|
|
89
|
+
case 'incrementRetry': ackValue = memoryStore.incrementRetry(args[0]); break;
|
|
90
|
+
case 'saveUserMessage': memoryStore.addMessage(args[0]); break;
|
|
91
|
+
case 'deleteUserMessage': memoryStore.clearMessages(args[0]); break;
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`unknown storage op "${op}"`);
|
|
94
|
+
};
|
|
95
|
+
}catch(e){
|
|
96
|
+
ok = false;
|
|
97
|
+
error = {message: e && e.message ? e.message : String(e)};
|
|
98
|
+
runLog.warn('[Master]: failed to apply storage op', op, e);
|
|
99
|
+
};
|
|
100
|
+
if(message.needAck){
|
|
101
|
+
try{
|
|
102
|
+
worker.send({__storageOpResult: true, ok, value: ackValue, error});
|
|
103
|
+
}catch(sendErr){
|
|
104
|
+
//来源 worker 已死: 回执发不出, 吞掉(在飞任务由崩溃处理兜底)
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
61
109
|
let clearTaskTimer = (workerId)=>{
|
|
62
110
|
let rec = workerTaskTimers[workerId];
|
|
63
111
|
if(rec){
|
|
@@ -71,16 +119,58 @@ const start = (config)=>{
|
|
|
71
119
|
clearTaskTimer(id);
|
|
72
120
|
};
|
|
73
121
|
killAllWorkers();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
122
|
+
let writeCounterAndExit = ()=>{
|
|
123
|
+
//计数器行与正常完成路径一致: 始终进 run.log(progressBar 未接管终端时 echo 上屏);
|
|
124
|
+
//runLog 写流与 stdout 都是异步写, 先写完再延迟 exit, 防计数器行被 process.exit 截断
|
|
125
|
+
let finalStats = getStats();
|
|
126
|
+
runLog.write(`[Tasks] succeeded=${finalStats.succeeded}, failed=${finalStats.failed}, succ-but-save-failed=${finalStats.succButSaveFailed || 0}`);
|
|
127
|
+
progressCtl.finish();
|
|
128
|
+
setTimeout(()=>{
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}, 10);
|
|
131
|
+
};
|
|
132
|
+
if(memoryStore){
|
|
133
|
+
//memory 档: 收尾排进派发同一串行队列(append-and-return, 绝不阻塞等队列——本函数在
|
|
134
|
+
//shouldTerminate 路径就是在队列任务内被调用的, 阻塞 await 队列即死锁): 已到达未应用的
|
|
135
|
+
//worker 存储操作先于收尾计数应用; 此后排队的已死 worker 操作按 terminate 语义丢弃
|
|
136
|
+
queue.enqueue(async ()=>{
|
|
137
|
+
writeCounterAndExit();
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
};
|
|
141
|
+
writeCounterAndExit();
|
|
142
|
+
};
|
|
143
|
+
let finalizeTimeoutTaskInMemory = (subTask, cost)=>{
|
|
144
|
+
//memory 档: 与 file 档同语义同顺序(先结果后迁移); 只在 queue.enqueue 里调(与 worker 存储操作
|
|
145
|
+
//同一串行通道): 该 worker 已到达未应用的 saveResult 等操作必先于本判定应用, 已交卷任务不会被误杀
|
|
146
|
+
let subid = subTask.subid;
|
|
147
|
+
try{
|
|
148
|
+
let resultType = memoryStore.getSavedResultType(subid);
|
|
149
|
+
if(resultType === 'succ'){//超时边界: worker 已成功交卷, 视为已完成
|
|
150
|
+
memoryStore.move(subid, 'running', 'finished');
|
|
151
|
+
}else if(resultType === 'error'){//超时边界: worker 已自行记错误
|
|
152
|
+
memoryStore.move(subid, 'running', 'finished_with_errors');
|
|
153
|
+
}else{
|
|
154
|
+
if(typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0){
|
|
155
|
+
let retryCount = memoryStore.incrementRetry(subid);
|
|
156
|
+
if(retryCount <= config.maxTaskRetries){
|
|
157
|
+
runLog.warn(`[Master]: task "${subid}" timeout, retry (${retryCount}/${config.maxTaskRetries})`);
|
|
158
|
+
memoryStore.move(subid, 'running', 'new');
|
|
159
|
+
return;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
memoryStore.saveResult(subid, true, [{type: 'timeout', timeout: config.taskTimeout, cost}]);
|
|
163
|
+
memoryStore.move(subid, 'running', 'finished_with_errors');
|
|
164
|
+
}
|
|
165
|
+
}catch(e){
|
|
166
|
+
runLog.warn('[Master]: failed to finalize timeout task', subid, e);
|
|
167
|
+
}
|
|
82
168
|
};
|
|
83
169
|
let finalizeTimeoutTask = (subTask, cost)=>{
|
|
170
|
+
if(memoryStore){
|
|
171
|
+
queue.enqueue(async ()=>{ finalizeTimeoutTaskInMemory(subTask, cost); });
|
|
172
|
+
return;
|
|
173
|
+
};
|
|
84
174
|
let subid = subTask.subid;
|
|
85
175
|
try{
|
|
86
176
|
let resultType = TaskMgr.getSavedResultType(subid);
|
|
@@ -114,8 +204,30 @@ const start = (config)=>{
|
|
|
114
204
|
worker.kill();//不 disconnect: exit 事件会按崩溃补员
|
|
115
205
|
finalizeTimeoutTask(subTask, cost);
|
|
116
206
|
};
|
|
207
|
+
let handleCrashedTaskInMemory = (subTask)=>{
|
|
208
|
+
//memory 档: 与 file 档同语义(重试移回 new / 超限记 worker_crash 归档), 操作走内存存储;
|
|
209
|
+
//只在 queue.enqueue 里调: 该 worker 崩溃前已到达的存储操作必先应用, 再判定重试/归档
|
|
210
|
+
let subid = subTask.subid;
|
|
211
|
+
try{
|
|
212
|
+
let retryCount = memoryStore.incrementRetry(subid);
|
|
213
|
+
if(retryCount <= config.maxTaskRetries){
|
|
214
|
+
runLog.warn(`[Master]: worker crashed, retry task "${subid}" (${retryCount}/${config.maxTaskRetries})`);
|
|
215
|
+
memoryStore.move(subid, 'running', 'new');
|
|
216
|
+
}else{
|
|
217
|
+
runLog.warn(`[Master]: task "${subid}" crashed ${retryCount} times, give up`);
|
|
218
|
+
memoryStore.saveResult(subid, true, [{type: 'worker_crash', retryCount: config.maxTaskRetries}]);
|
|
219
|
+
memoryStore.move(subid, 'running', 'finished_with_errors');
|
|
220
|
+
};
|
|
221
|
+
}catch(e){
|
|
222
|
+
runLog.warn('[Master]: failed to handle crashed task', subid, e);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
117
225
|
let handleCrashedTask = (subTask)=>{
|
|
118
226
|
if(!(typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0)) return;
|
|
227
|
+
if(memoryStore){
|
|
228
|
+
queue.enqueue(async ()=>{ handleCrashedTaskInMemory(subTask); });
|
|
229
|
+
return;
|
|
230
|
+
};
|
|
119
231
|
let subid = subTask.subid;
|
|
120
232
|
try{
|
|
121
233
|
let retryCount = TaskMgr.incrementTaskRetry(subid);
|
|
@@ -177,25 +289,36 @@ const start = (config)=>{
|
|
|
177
289
|
}
|
|
178
290
|
return;
|
|
179
291
|
}
|
|
292
|
+
if(memoryStore && message && message.__storageOp){//memory 档 worker 存储操作: 与派发同一串行通道逐条应用, 禁止捎带合批
|
|
293
|
+
queue.enqueue(async ()=>{ applyStorageOp(worker, message); });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
180
296
|
if(message === MSG_REQUEST_NEW_TASK){
|
|
181
297
|
clearTaskTimer(worker.id);//上一个任务已结束(无论成败), 撤销超时倒计时
|
|
182
298
|
progressCtl.taskCompleted(worker.id);//worker 行回到 idle; 完成数按盘上结果目录统计, 不在此计数
|
|
183
299
|
queue.enqueue(async () => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if(!subTask && config.autoCloseAfterCompletion){
|
|
194
|
-
//杀 worker 前延时 1s 再兜底确认一次: 等其他 worker 可能正在落盘的动态新任务
|
|
195
|
-
//(best-effort 再压小竞态窗口, 只限 autoClose 路径, 普通派发零开销)
|
|
196
|
-
await new Promise((resolve)=>{ setTimeout(resolve, 1000); });
|
|
197
|
-
await TaskMgr.loadNewTasks();
|
|
300
|
+
let subTask = null;
|
|
301
|
+
if(memoryStore){
|
|
302
|
+
//memory 档: 任务全在内存队列, pop 即出队; 无需磁盘重读兜底(动态任务经 __storageOp
|
|
303
|
+
//同一串行通道进来, pop 时必然已可见)
|
|
304
|
+
subTask = memoryStore.pop();
|
|
305
|
+
}else{
|
|
306
|
+
//use queue to make sure no I/O conflict; update/pop/判空须在同一个串行任务里原子完成
|
|
307
|
+
await TaskMgr.updateNewTasks();
|
|
198
308
|
subTask = await TaskMgr.popNewTask();
|
|
309
|
+
if(!subTask){
|
|
310
|
+
//判空前强制绕开缓存重读一次磁盘再 pop: 防其他 worker 动态建任务(createNewTasks)
|
|
311
|
+
//落在 update 与判空之间被漏判, 导致 autoClose 提前杀光 worker / 丢新任务
|
|
312
|
+
await TaskMgr.loadNewTasks();
|
|
313
|
+
subTask = await TaskMgr.popNewTask();
|
|
314
|
+
}
|
|
315
|
+
if(!subTask && config.autoCloseAfterCompletion){
|
|
316
|
+
//杀 worker 前延时 1s 再兜底确认一次: 等其他 worker 可能正在写盘的动态新任务
|
|
317
|
+
//(best-effort 再压小竞态窗口, 只限 autoClose 路径, 普通派发零开销)
|
|
318
|
+
await new Promise((resolve)=>{ setTimeout(resolve, 1000); });
|
|
319
|
+
await TaskMgr.loadNewTasks();
|
|
320
|
+
subTask = await TaskMgr.popNewTask();
|
|
321
|
+
}
|
|
199
322
|
}
|
|
200
323
|
if(!subTask){
|
|
201
324
|
//no tasks left
|
|
@@ -224,7 +347,7 @@ const start = (config)=>{
|
|
|
224
347
|
//send new tasks to the worker
|
|
225
348
|
let startTimestamp = new Date()*1;
|
|
226
349
|
//IPC 用裁剪副本: worker 端只取目录路径, 不需要 initialTasks 全量数组;
|
|
227
|
-
//带着它每次派发都要 O(N) 序列化(总量 O(N²))。config 本体保留: resume/restart
|
|
350
|
+
//带着它每次派发都要 O(N) 序列化(总量 O(N²))。config 本体保留: resume/restart 重写存档要用
|
|
228
351
|
let ipcConfig = { ...config };
|
|
229
352
|
delete ipcConfig.initialTasks;
|
|
230
353
|
worker.send({
|
|
@@ -250,8 +373,9 @@ const start = (config)=>{
|
|
|
250
373
|
};
|
|
251
374
|
|
|
252
375
|
let initPromise = config.__resume ? asMaster.resume(config) : asMaster.init(config);
|
|
253
|
-
initPromise.then((
|
|
254
|
-
config =
|
|
376
|
+
initPromise.then((inited)=>{
|
|
377
|
+
config = inited.config;
|
|
378
|
+
memoryStore = inited.store || null;
|
|
255
379
|
//一次性启动块(走 console, 在劫持前上屏): 标题框横幅 + 平铺关键字段, 模式由 __ 标记推导;
|
|
256
380
|
//init 成功后所有信息齐备(new tasks 数/masterTaskId/__sysLogFile), 一次打完
|
|
257
381
|
let startupInfo = {
|
|
@@ -261,7 +385,7 @@ const start = (config)=>{
|
|
|
261
385
|
numOfWorkers,
|
|
262
386
|
numCPUs,
|
|
263
387
|
pid: process.pid,
|
|
264
|
-
newTasks:
|
|
388
|
+
newTasks: getRemaining(),
|
|
265
389
|
sysLogFile: config.__sysLogFile,
|
|
266
390
|
startedAt: randomsUtil.getDateTimeTxt(),
|
|
267
391
|
};
|
|
@@ -269,8 +393,8 @@ const start = (config)=>{
|
|
|
269
393
|
progressCtl.setup({//TTY 判定 + 渲染器创建都在控制器内; 统计口径注入, 控制器不依赖 TaskMgr
|
|
270
394
|
config,
|
|
271
395
|
stream: process.stdout,
|
|
272
|
-
getStats
|
|
273
|
-
getRemaining
|
|
396
|
+
getStats,
|
|
397
|
+
getRemaining,
|
|
274
398
|
});
|
|
275
399
|
//console 劫持写 run.log 是永久机制, 与 progressBar 无关: 进度区接管终端(开且 TTY)时只写文件,
|
|
276
400
|
//否则 echo 同时透传 console 上屏; 判定收敛在 initAndHook 内, 无 __sysLogFile 时维持 console
|
|
@@ -310,12 +434,13 @@ const start = (config)=>{
|
|
|
310
434
|
allkilled = allkilled && worker.killed;
|
|
311
435
|
}
|
|
312
436
|
if(allkilled){
|
|
437
|
+
let finishAll = ()=>{
|
|
313
438
|
if(USER_CONFIG.onFinish){
|
|
314
439
|
let endTimestamp = new Date()*1;
|
|
315
|
-
let stats =
|
|
440
|
+
let stats = getStats();
|
|
316
441
|
let report = {
|
|
317
442
|
taskId: config.masterTaskId,
|
|
318
|
-
taskFolder:
|
|
443
|
+
taskFolder: config.taskFolder,
|
|
319
444
|
startTimestamp: masterStartTimestamp,
|
|
320
445
|
endTimestamp,
|
|
321
446
|
cost: endTimestamp - masterStartTimestamp,
|
|
@@ -326,23 +451,43 @@ const start = (config)=>{
|
|
|
326
451
|
succButSaveFailed: stats.succButSaveFailed || 0,//任务成功但结果保存失败(序列化兜底)的任务数, 明细见 .sys/user_messages
|
|
327
452
|
workers: {count: numOfWorkers, crashed: workerCrashCount},
|
|
328
453
|
};
|
|
329
|
-
let onFinishHelper
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
454
|
+
let onFinishHelper;
|
|
455
|
+
if(config.task_storage === 'memory'){
|
|
456
|
+
//memory 档: 结果只在内存(盘上 results/ 恒空), 遍历接口委托 memoryStore(与 TaskMgr 同签名同口径)
|
|
457
|
+
onFinishHelper = {
|
|
458
|
+
foreachSuccResult: (cb) => memoryStore.forEachSuccResult(cb),
|
|
459
|
+
foreachErrorResult: (cb) => memoryStore.forEachErrorResult(cb),
|
|
460
|
+
foreachResult: (cb) => memoryStore.forEachResult(cb),
|
|
461
|
+
};
|
|
462
|
+
}else{
|
|
463
|
+
onFinishHelper = {
|
|
464
|
+
foreachSuccResult: (cb) => TaskMgr.forEachSuccResult(cb),
|
|
465
|
+
foreachErrorResult: (cb) => TaskMgr.forEachErrorResult(cb),
|
|
466
|
+
foreachResult: (cb) => TaskMgr.forEachResult(cb),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
334
469
|
USER_CONFIG.onFinish(report, onFinishHelper);
|
|
335
470
|
delete USER_CONFIG.onFinish;
|
|
336
471
|
}
|
|
337
472
|
//计数器行先进 run.log(progressBar 未接管终端时 echo 上屏, 接管时由 progressCtl.finish() 打在屏幕);
|
|
338
473
|
//stdout/run.log 都是异步写, 写完留 10ms 再 exit, 防 process.exit 截断未 flush 的输出
|
|
339
|
-
let finalStats =
|
|
474
|
+
let finalStats = getStats();
|
|
340
475
|
runLog.write(`[Tasks] succeeded=${finalStats.succeeded}, failed=${finalStats.failed}, succ-but-save-failed=${finalStats.succButSaveFailed || 0}`);
|
|
341
476
|
runLog.write('[Master]: all workers are done, process exit.');
|
|
342
477
|
progressCtl.finish();//单行进度条换行定格 + 计数器行, 别让 shell 提示符盖住它
|
|
343
478
|
setTimeout(()=>{
|
|
344
479
|
process.exit(0);
|
|
345
480
|
}, 10);
|
|
481
|
+
};
|
|
482
|
+
if(memoryStore){
|
|
483
|
+
//memory 档: 同 terminateAllAndExit 口径, 收尾排进派发串行队列, 队列里已到达的存储操作
|
|
484
|
+
//先应用再统计; file 档维持原同步收尾一字不动
|
|
485
|
+
queue.enqueue(async ()=>{
|
|
486
|
+
finishAll();
|
|
487
|
+
});
|
|
488
|
+
}else{
|
|
489
|
+
finishAll();
|
|
490
|
+
}
|
|
346
491
|
}
|
|
347
492
|
});
|
|
348
493
|
}else{
|
|
@@ -371,6 +516,10 @@ function subWorker(config){
|
|
|
371
516
|
helper.__dispatchBroadcast(message);
|
|
372
517
|
return;
|
|
373
518
|
}
|
|
519
|
+
if(message && message.__storageOpResult){//memory 档存储操作回执: 兑付 IpcTaskStore 的 needAck 等待队列
|
|
520
|
+
IpcTaskStore.__dispatchStorageOpResult(message);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
374
523
|
if(message === MSG_NO_TASK_FOUND){//wait for new task
|
|
375
524
|
runLog.write(` (Worker #${worker.id}): waiting`);
|
|
376
525
|
setTimeout(()=>{
|
|
@@ -379,7 +528,10 @@ function subWorker(config){
|
|
|
379
528
|
}else
|
|
380
529
|
if(message.subTask){//new task
|
|
381
530
|
let {config, subTask, startTimestamp, taskCount} = message;
|
|
382
|
-
|
|
531
|
+
//memory 档 worker 不碰任务目录: 存储后端换 IpcTaskStore(逐条 {__storageOp} 发 master),
|
|
532
|
+
//操作序列与 file 档逐条同构; file 档后端即 TaskMgr(磁盘, 现状不变)
|
|
533
|
+
let store = config.task_storage === 'memory' ? IpcTaskStore : TaskMgr;
|
|
534
|
+
store.load(config);
|
|
383
535
|
//用户任务自带的 index/taskCount 保留原值(重试/续跑后任务身份不变),
|
|
384
536
|
//未提供时才填全局派发计数器(v1 兼容)
|
|
385
537
|
if(typeof subTask.index === 'undefined') subTask.index = taskCount;
|
|
@@ -393,7 +545,7 @@ function subWorker(config){
|
|
|
393
545
|
taskCount,
|
|
394
546
|
startTimestamp
|
|
395
547
|
};
|
|
396
|
-
runTask(config, subTask, subTaskLog).then((tasklog)=>{
|
|
548
|
+
runTask(config, subTask, subTaskLog, store).then((tasklog)=>{
|
|
397
549
|
if(tasklog.retried) return;//重试任务已回 new 等重跑, 未完成, 不打 [Task Finished]
|
|
398
550
|
let {cost, startTimestamp, endTimestamp} = tasklog;
|
|
399
551
|
|
|
@@ -425,13 +577,13 @@ function serializeError(e){
|
|
|
425
577
|
};
|
|
426
578
|
return serialized;
|
|
427
579
|
};
|
|
428
|
-
function runTask(config, subTask, subTaskLog){
|
|
580
|
+
function runTask(config, subTask, subTaskLog, store){
|
|
429
581
|
return new Promise((resolve)=>{
|
|
430
582
|
let resultPromise = Promise.resolve({});
|
|
431
583
|
let resultData;
|
|
432
584
|
let errors = [];
|
|
433
585
|
helper.load(config);
|
|
434
|
-
|
|
586
|
+
store.saveTaskLog(subTask.subid, subTaskLog);
|
|
435
587
|
try{
|
|
436
588
|
resultPromise = USER_CONFIG.processTask(subTask, helper);//rewrite this method in subclass
|
|
437
589
|
}catch(e){
|
|
@@ -456,21 +608,23 @@ function runTask(config, subTask, subTaskLog){
|
|
|
456
608
|
if(hasError)runLog.warn('[Task Error]', subTask.subid, errors);
|
|
457
609
|
let retried = false;
|
|
458
610
|
if(hasError && typeof config.maxTaskRetries === 'number' && config.maxTaskRetries > 0){
|
|
459
|
-
|
|
611
|
+
//重试决策在 worker(file 档如此, memory 档同构勿收归 master); await 两档通用:
|
|
612
|
+
//file 档 incrementTaskRetry 是同步值, memory 档是 needAck 回执 Promise——worker 侧唯一 async 化点
|
|
613
|
+
let retryCount = await store.incrementTaskRetry(subTask.subid);
|
|
460
614
|
if(retryCount <= config.maxTaskRetries){
|
|
461
615
|
runLog.warn(`[Task Retry] "${subTask.subid}" failed, retry (${retryCount}/${config.maxTaskRetries})`);
|
|
462
616
|
retried = true;
|
|
463
|
-
//等 rename
|
|
464
|
-
//autoClose
|
|
617
|
+
//等 rename 生效再 resolve/请求新任务: file 档等 rename 写盘完成(fire-and-forget 的异步 rename 会随
|
|
618
|
+
//worker 被 autoClose 杀掉而消亡), memory 档等价等 master 回执——约定两档继续成立, 勿回退
|
|
465
619
|
try{
|
|
466
|
-
await
|
|
620
|
+
await store.mvTaskPromise(subTask.subid, 'running', 'new');
|
|
467
621
|
}catch(newPath){
|
|
468
622
|
runLog.warn(`[Task Retry] "${subTask.subid}" failed to move back to new, left in running (recoverable via resume)`);
|
|
469
623
|
};
|
|
470
|
-
|
|
624
|
+
store.deleteUserMessage(subTask.subid);//重跑即新一轮: 清掉该任务前序尝试的遗留消息
|
|
471
625
|
};
|
|
472
626
|
};
|
|
473
|
-
|
|
627
|
+
store.saveTaskLog(subTask.subid, subTaskLog);
|
|
474
628
|
if(retried){
|
|
475
629
|
resolve({
|
|
476
630
|
startTimestamp,
|
|
@@ -481,10 +635,10 @@ function runTask(config, subTask, subTaskLog){
|
|
|
481
635
|
return;
|
|
482
636
|
};
|
|
483
637
|
if(!hasError){
|
|
484
|
-
|
|
638
|
+
store.deleteSavedResult(subTask.subid);//清掉前序尝试可能残留的(超时竞争)错误结果
|
|
485
639
|
};
|
|
486
|
-
|
|
487
|
-
|
|
640
|
+
store.saveTaskResult(subTask.subid, hasError, hasError ? errors : resultData);
|
|
641
|
+
store.mvTask(subTask.subid, 'running',(hasError ? 'finished_with_errors':'finished'), (newPath)=>{
|
|
488
642
|
resolve({
|
|
489
643
|
startTimestamp,
|
|
490
644
|
endTimestamp,
|