dcp-worker 4.5.0 → 4.5.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/.gitlab-ci.yml CHANGED
@@ -17,6 +17,7 @@ default:
17
17
  image: node:lts
18
18
  before_script:
19
19
  - npm add --global npm@latest
20
+ - npm config set allow-git=all
20
21
  # Access private deps. e.g., `dcp-client` from GitLab.
21
22
  - git config --global
22
23
  url."${CI_SERVER_PROTOCOL}://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}".insteadOf
@@ -26,45 +26,36 @@ process.stdout.write(`
26
26
  *** This program collects information from your computer, and sends it to
27
27
  *** Distributive and our analytics partners. This includes your local DCP
28
28
  *** configuration, which might include sensitive information such as Compute
29
- *** Group credentials, or API keys.
29
+ *** Group information, or API keys.
30
30
 
31
31
  `);
32
32
 
33
- while (argv[0])
33
+ for (; argv.length; argv.shift())
34
34
  {
35
- if (argv[0] === '-f' || argv[0] === '--force')
36
- {
37
- argv.shift();
38
- force = true;
39
- continue;
40
- }
41
-
42
- if (argv[0] === '-s')
43
- {
44
- syslogUrl = argv[0];
45
- argv.shift();
46
- continue;
47
- }
48
-
49
35
  if (argv[0].startsWith('--syslog='))
50
- {
51
36
  syslogUrl = argv[0].slice(0);
52
- argv.shift();
37
+ else if (argv[0].startsWith('--dcp-'))
53
38
  continue;
39
+ else if (argv[0] === '-f' || argv[0] === '--force')
40
+ force = true;
41
+ else if (argv[0] === '-s')
42
+ {
43
+ syslogUrl = argv[1];
44
+ shift();
54
45
  }
55
-
56
- break;
46
+ else if (argv.length === 1)
47
+ email = argv[0];
48
+ else
49
+ throw new Error('invalid argument: ' + argv[0]);
57
50
  }
58
51
 
59
52
  if (force)
60
53
  {
61
54
  go();
62
- email = argv[0] || 'wes+worker-debug-info@distributive.network';
55
+ email = email || 'dcp+worker-debug-info@distributive.network';
63
56
  }
64
57
  else
65
58
  {
66
- email = argv[0];
67
-
68
59
  const rl = readline.createInterface({
69
60
  input: process.stdin,
70
61
  output: process.stdout
@@ -100,8 +91,6 @@ async function emailReport(report)
100
91
  {
101
92
  const protocol = require('dcp/protocol');
102
93
  const worker = require('dcp/worker');
103
-
104
- const conn = new protocol.Connection(dcpConfig.scheduler.services.mailgw);
105
94
  const message = {
106
95
  from: `${os.userInfo().username}-${worker.DistributiveWorker.obtainWorkerId()}-worker-${os.hostname()}@distributive.network`,
107
96
  fromName: 'collect-debug-info',
@@ -112,6 +101,15 @@ async function emailReport(report)
112
101
  body: `<html><pre>${report}</pre></html>`
113
102
  };
114
103
 
104
+ if (email === '-')
105
+ {
106
+ console.log(report);
107
+ return;
108
+ }
109
+
110
+ const mailgw = dcpConfig.scheduler.services.mailgw;
111
+ console.log(` - sending email via ${mailgw.location}`);
112
+ const conn = new protocol.Connection(mailgw);
115
113
  const response = await conn.request('sendmail', message);
116
114
  conn.close();
117
115
  console.log(' - sent report to', message.to);
@@ -126,7 +124,7 @@ async function cmd(prog, args)
126
124
  argv.unshift(prog);
127
125
  argv.unshift('/c');
128
126
  const s = execFileSync('cmd', argv, { encoding: 'utf8' });
129
- return `------ [ ${prog} ] ---------------------------------------------------------------\n${s}`;
127
+ return `------ [ ${prog} ] ------\n${s}`;
130
128
  }
131
129
 
132
130
  async function sh(prog, args)
@@ -134,7 +132,7 @@ async function sh(prog, args)
134
132
  const argv = args.slice();
135
133
  console.log(' -', prog, argv.join(' '));
136
134
  const s = execFileSync(prog, argv, { encoding: 'utf8', maxBuffer: 1e9 });
137
- return `------ [ ${prog} ] ---------------------------------------------------------------\n${s}`;
135
+ return `------ [ ${prog} ] ------\n${s}`;
138
136
  }
139
137
 
140
138
  async function go()
@@ -147,9 +145,29 @@ async function go()
147
145
  worker: { logging: { syslog: {} } },
148
146
  }
149
147
  });
148
+ const { DcpURL } = require('dcp/dcp-url');
149
+ DcpURL.prototype.toJSON = function() { return { href: this.href } };
150
+ URL.prototype.toJSON = function() { return { href: this.href } };
151
+ const dcpConfigNoCreds = Object.assign({}, dcpConfig);
152
+ dcpConfigNoCreds.worker = Object.assign({}, dcpConfigNoCreds.worker);
153
+ delete dcpConfigNoCreds.worker.computeGroups;
154
+
155
+ for (let serviceGroup in dcpConfig)
156
+ {
157
+ if (dcpConfig[serviceGroup].location)
158
+ dcpConfig[serviceGroup].location = new URL(dcpConfig[serviceGroup].location);
159
+ if (dcpConfig[serviceGroup].services)
160
+ {
161
+ for (let service in dcpConfig[serviceGroup].services)
162
+ {
163
+ if (dcpConfig[serviceGroup].services[service].location)
164
+ dcpConfig[serviceGroup].services[service].location = new URL(dcpConfig[serviceGroup].services[service].location);
165
+ }
166
+ }
167
+ }
150
168
 
151
169
  console.log(' - host information');
152
- var report = `------ [ ${new Date()} ] ---------------------------------------------------------------
170
+ var report = `------ [ ${new Date()} ] ------
153
171
  os: ${os.version()}
154
172
  platform: node ${process.version} ${os.platform()} ${os.release()} ${os.machine()} ${os.endianness()}
155
173
  uptime: ${Math.floor(os.uptime() / 86400)} days ${Number(((os.uptime() / 86400) % 1) * 86400).toFixed(1)} seconds, load average: ${os.loadavg().join(', ')}
@@ -161,11 +179,12 @@ cpus: ${JSON.stringify(os.cpus, null, 2)}
161
179
  network: ${JSON.stringify(os.networkInterfaces, null, 2)}
162
180
  more versions: ${JSON.stringify(process.versions, null, 2)}
163
181
  `;
164
-
182
+ console.log(' - worker information');
183
+ report += await createWorkerReport();
165
184
  console.log(' - dcp information');
166
185
  report +=
167
186
  `
168
- dcpConfig: ${JSON.stringify(dcpConfig, null, 2)}
187
+ dcpConfig: ${JSON.stringify(dcpConfigNoCreds, null, 2)}
169
188
  `;
170
189
 
171
190
  if (os.platform() === 'win32')
@@ -190,3 +209,27 @@ dcpConfig: ${JSON.stringify(dcpConfig, null, 2)}
190
209
  else if (syslogUrl)
191
210
  syslogReport(report);
192
211
  }
212
+
213
+ async function createWorkerReport()
214
+ {
215
+ const identity = require('dcp/identity');
216
+ const wallet = require('dcp/wallet');
217
+ const utils = require('../lib/utils');
218
+ const { DistributiveWorker } = require('dcp/worker');
219
+ const worker = new DistributiveWorker(Object.assign({
220
+ unmanaged: true
221
+ }, dcpConfig.worker), Function).unref();
222
+ identity.interactive = false;
223
+ const rootIdentity = await wallet.getId();//identity.get();
224
+ await identity.masquerade(DistributiveWorker.obtainWorkerId(), '$login');
225
+ await worker.configure();
226
+
227
+ return `
228
+ root identity: ${rootIdentity.address}
229
+ masquerade: ${identity.get().address}
230
+ worker id: ${worker.id}
231
+ earnings acct: ${worker.config.paymentAddress}
232
+ join keys: ${Object.values(worker.config.computeGroups).map(cg => cg.joinKey)}
233
+ worker config: ${JSON.stringify(Object.assign({}, worker.config, { computeGroups: 'HIDDEN' }), null, 2)}
234
+ `
235
+ }
@@ -19,10 +19,10 @@
19
19
  */
20
20
  'use strict';
21
21
 
22
+ const net = require('mux-net').hook();
22
23
  const os = require('os');
23
24
  const fs = require('fs');
24
25
  const path = require('path');
25
- const net = require('net');
26
26
  const dns = require('dns');
27
27
  const debug = require('debug');
28
28
  const pidfile = require('../lib/pidfile');
@@ -64,6 +64,10 @@ const daemonConfig = {
64
64
 
65
65
  async function processExit(code)
66
66
  {
67
+ if (processExit.p$exiting)
68
+ return processExit.p$exiting;
69
+ processExit.p$exiting = new Promise(()=>1); /* never resolves, other "thread" will exit program */
70
+
67
71
  if (typeof code === 'undefined')
68
72
  code = process.exitCode;
69
73
  console.debug('Exit, code', code);
@@ -80,7 +84,7 @@ Released under the terms of the MIT License.
80
84
 
81
85
  Usage: ${process.argv[0]} [-h | --help] [--prefix=<dir>] [--pts=<dir>]
82
86
  [--disable-monitor=<session|screensaver|signal>] [-i <timeout>]
83
- [-p port] [-H hostname]
87
+ [-p port] [-H hostname[,hostname...]]
84
88
  [-a] [-l | --max-load=<number>] [-r | --rate=<number>] [-L]
85
89
  [-s | --signal=[name or number][,<toggle|quick>]]
86
90
  [-T | --loadavg-type=<short,medium,long,random>]
@@ -127,32 +131,56 @@ Environment:
127
131
  */
128
132
  async function listenForConnections(config)
129
133
  {
130
- var server = net.createServer((socket) => handleConnection(socket, config));
131
- var hostaddr;
134
+ const server = net.createServer((socket) => handleConnection(socket, config));
135
+ const hostnames = config.net.hostnames || [ config.net.hostname ];
136
+ const hosts = [];
132
137
 
133
- if (config.net.hostname === 'localhost')
134
- hostaddr = '::';
138
+ if (!hostnames[0])
139
+ {
140
+ hosts[0] = '::1';
141
+ hosts[1] = '127.0.0.1';
142
+ }
135
143
  else
136
- hostaddr = (await dns.promises.lookup(config.net.hostname, { family: 4 })).address || config.net.hostname;
144
+ {
145
+ for (let host of hostnames)
146
+ {
147
+ try
148
+ {
149
+ const hostaddrs = await dns.promises.lookup(host, { all: true, order: 'verbatim' });
150
+ for (let hostaddr of hostaddrs)
151
+ hosts.push(hostaddr.address);
152
+ }
153
+ catch(error)
154
+ {
155
+ if (error.code === 'EAI_AGAIN')
156
+ console.error(`hostname ${host} not found - check /etc/hosts or DNS configuration`);
157
+ else
158
+ console.error(`error ${error.code} resolving host ${host}:`, error.message);
159
+ await processExit(4);
160
+ }
161
+ }
162
+ }
137
163
 
138
164
  server.on('error', (error) => {
139
165
  delete error.stack;
166
+ if (processExit.p$exiting)
167
+ return;
140
168
  console.error(error);
141
169
  processExit(2);
142
170
  });
143
171
 
144
- server.listen({ host: hostaddr, port: config.net.port }, function daemonReady() {
145
- console.log('Listening for connections on', server._connectionKey);
172
+ console.log('Evaluator command is', config.proc, config.argv);
173
+ server.listen({ hosts, port: config.net.port }, function daemonReady() {
174
+ // eslint-ignore-block no-invalid-this
175
+ console.log(`Listening for connections on ${config.net.hostname} (${this._connectionKey})`);
146
176
  if (Number(config.net.port) === 0)
147
177
  {
148
178
  console.log('Using dynamic port', server.address().port);
149
179
  // Send info by IPC, if the spawn is setup for that. Probably don't need to try/catch.
150
180
  /* WHAT IS THIS FOR? /wg feb 2026 */
151
181
  if (process.send)
152
- try { process.send(server.address().port); } catch (e) {}
182
+ try { process.send(server.address().port); } catch (e) { e; 'dontcare'; }
153
183
  }
154
-
155
- console.log('Evaluator command is', config.proc, config.argv);
156
184
  });
157
185
  }
158
186
 
@@ -430,7 +458,7 @@ async function dbusScreenSaverMonitor()
430
458
  {
431
459
  const prettyName = chosen.split('.')[1];
432
460
  console.error(`${prettyName} screensaver doesn't support activeness query, screensaver is not started, or user ${os.userInfo().username} doesn't have access to dbus\n ${error}`);
433
- processExit(3);
461
+ await processExit(3);
434
462
  }
435
463
  }
436
464
 
@@ -654,7 +682,9 @@ async function main()
654
682
 
655
683
  case 'H':
656
684
  {
657
- daemonConfig.net.hostname = optarg;
685
+ delete daemonConfig.net.hostname;
686
+ daemonConfig.net.hostnames = optarg.split(/[, ]+/);
687
+ console.log(daemonConfig.net.hostnames);
658
688
  break;
659
689
  }
660
690
 
@@ -763,7 +793,8 @@ async function main()
763
793
  }
764
794
 
765
795
  /* Initialize dcp-client to use only local resources before launching the main function */
766
- try {
796
+ try
797
+ {
767
798
  process.on('SIGHUP', () => processExit( 1 - 128));
768
799
  process.on('SIGINT', () => processExit( 2 - 128));
769
800
  process.on('SIGTERM', () => processExit(15 - 128));
@@ -641,7 +641,7 @@ async function main()
641
641
  console.log('Worker Id: ', workerId);
642
642
  console.log('Max Sandboxes: ', workerConfig.maxSandboxes);
643
643
  console.log('Cores: ', Object.assign({}, workerConfig.cores));
644
- console.log('Utilization: ', workerConfig.utilization);
644
+ console.log('Utilization: ', Object.assign({}, workerConfig.utilization));
645
645
  console.log('DCP Address: ', String(identity.get().address));
646
646
 
647
647
  if (!task)
@@ -705,7 +705,4 @@ async function main()
705
705
  }
706
706
 
707
707
  /* Initialize dcp-client to use only local resources before launching the main function */
708
- require('dcp-client').init({
709
- programName: 'dcp-worker',
710
- parseArgv: false,
711
- }).then(main);
708
+ require('dcp-client').init({ programName: 'dcp-worker' }).then(main);
package/bin/dcp-worker CHANGED
@@ -13,6 +13,7 @@
13
13
  */
14
14
  'use strict';
15
15
  var worker;
16
+ var workerStarted = false;
16
17
 
17
18
  const process = require('process');
18
19
  const fs = require('fs');
@@ -26,6 +27,7 @@ const reports = require('../lib/reports');
26
27
  const utils = require('../lib/utils');
27
28
  const loggers = require('../lib/loggers');
28
29
  var wallet, a$sleep, DistributiveWorker; /* populate in main() */
30
+ var noStartWorker = false;
29
31
 
30
32
  const { exitCodes,
31
33
  logLevels } = require('../lib/consts');
@@ -81,6 +83,7 @@ Options:
81
83
  -o, --output=mode Set the console mode: text|dashboard|none
82
84
  -l, --logger=type Add an additional logger: syslog, logfile
83
85
  (all platforms), event-log (win32)
86
+ --no-logger=type Remove a logger
84
87
  --logfile=filename Change filename, implies --logger=logfile
85
88
  --overwrite-logfile Overwrite log files instead of appending
86
89
  --syslog=url|facility Change syslog target config, implies
@@ -257,6 +260,9 @@ function processCliOptsPhase1(argv, operatingMode)
257
260
  case 'l':
258
261
  loggingOptions.loggers.push(optarg);
259
262
  break;
263
+ case 'no-logger':
264
+ loggingOptions.loggers = loggingOptions.loggers.filter(logger => logger !== optarg);
265
+ break;
260
266
  case 'syslog':
261
267
  loggingOptions.loggers.push('syslog');
262
268
  loggingOptions.syslogUrl = utils.makeSyslogUrl(loggingOptions.syslogUrl, optarg);
@@ -311,9 +317,11 @@ function processCliOptsPhase1(argv, operatingMode)
311
317
  break;
312
318
  }
313
319
  /* options below this point are processed in both phase 1 and phase 2 */
320
+ case 'reset':
314
321
  case 'claim-earnings':
315
322
  case 'register':
316
323
  operatingMode.zeroTrust = true;
324
+ /* fallthrough */
317
325
  case 'show':
318
326
  operatingMode.consoleType = 'stdio';
319
327
  /* fallthrough */
@@ -384,7 +392,6 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
384
392
  break;
385
393
  case 'show':
386
394
  operatingMode.show = optarg;
387
- operatingMode.consoleType = 'stdio';
388
395
  break;
389
396
  case 'startup-delay':
390
397
  await a$sleep(Number(optarg));
@@ -421,6 +428,7 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
421
428
  console.log('Registration failure:', error.message);
422
429
  debug('dcp-worker:registration')(error);
423
430
  }
431
+ worker.config.unmanaged = true;
424
432
  await cleanupThenExit(exitCodes.normal);
425
433
  break;
426
434
  }
@@ -465,13 +473,14 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
465
473
  break;
466
474
  default:
467
475
  throw new Error(`unhandled option '${option}'`);
468
- /* hidden / undocumented options below */
476
+ /**** hidden / undocumented options below ****/
469
477
  case 'slice-report-interval':
470
478
  loggingOptions.sliceReportInterval = Number(optarg);
471
479
  break;
472
480
  case 'reset': /* can lose unclaimed earnings */
473
481
  DistributiveWorker.resetLocalStorage();
474
482
  console.info('reset complete');
483
+ worker.config.unmanaged = true;
475
484
  await cleanupThenExit(exitCodes.normal);
476
485
  break;
477
486
  }
@@ -519,7 +528,6 @@ async function mergeDbAppConfig(remainArgv, operatingMode, loggingOptions)
519
528
  */
520
529
  async function main()
521
530
  {
522
- var workerIsStopping = false;
523
531
  const identity = require('dcp/identity');
524
532
  /* handle file-wide dcp-client imports */
525
533
  wallet = require('dcp/wallet');
@@ -546,6 +554,8 @@ async function main()
546
554
  loggingOptions, /* options to tell us where logs go besides the console */
547
555
  } = processCliOptsPhase1(process.argv, operatingMode);
548
556
 
557
+ if (!operatingMode.zeroTrust)
558
+ loggingOptions.loggers.push('p2p');
549
559
  if (dcpConfig.worker.logging?.syslog)
550
560
  loggingOptions.loggers.push('syslog');
551
561
  if (dcpConfig.worker.logging?.logfile)
@@ -582,7 +592,6 @@ async function main()
582
592
  worker.runInfo.type = 'dcp-worker';
583
593
  worker.on('warning', (...payload) => console.warn (...payload));
584
594
  worker.on('claim', amount => console.log(` . transfered ${amount} ⊇ to ${worker.config.paymentAddress}`));
585
- worker.on('stop', () => workerIsStopping = true );
586
595
  await processCliOptsPhase2(remainArgv, operatingMode, loggingOptions);
587
596
 
588
597
  if (operatingMode.show)
@@ -623,6 +632,7 @@ async function main()
623
632
 
624
633
  require('../lib/default-ui-events').hook(worker, loggingOptions);
625
634
  console.setWorker(worker);
635
+ require('../lib/p2p-shared').init(worker);
626
636
 
627
637
  if (worker.config.jobIds?.length > 0)
628
638
  console.info(` * Processing only ${utils.qty(worker.config.jobIds, 'job')} ` + worker.config.jobIds.join(', '));
@@ -635,29 +645,51 @@ async function main()
635
645
  console.info(` + Verbosity level: ${loggingOptions.verbose}`);
636
646
 
637
647
  /* Poll the evaluator forever until it accepts a connection */
638
- let workerInfo = false;
648
+ let evaluatorInfo = false;
649
+ let p$throbSleep;
639
650
  console.throb(` . Waiting for dcp-evaluator on ${utils.shortLoc(sandboxConfig.location)} to start...`);
640
- for (let i=0; workerInfo === false; i++)
651
+ function dieBeforeEvaluatorReady()
652
+ {
653
+ debug('dcp-worker:exit')('Trapped DistributiveWorker<stop> stop event before evaluator ready');
654
+ evaluatorInfo = null;
655
+ p$throbSleep?.intr();
656
+ }
657
+ worker.once('end', dieBeforeEvaluatorReady);
658
+
659
+ for (let i=0; evaluatorInfo === false; i++)
641
660
  {
642
661
  const ts = performance.now();
643
- if ((workerInfo = await require('../lib/worker-info').getEvaluatorInformation(sandboxConfig)))
662
+ if ((evaluatorInfo = await require('../lib/worker-info').getEvaluatorInformation(sandboxConfig)))
644
663
  break;
645
664
  if (i === 0 && operatingMode.unmanaged !== true)
646
665
  utils.noteWorkerStart(worker);
647
666
  if (performance.now() - ts > 100)
648
667
  console.throb();
649
- await a$sleep(Math.max(i + 1, 10) / 4);
668
+ p$throbSleep = a$sleep(Math.max(i + 1, 10) / 4);
669
+ await p$throbSleep;
650
670
  console.throb();
651
671
  }
672
+ worker.off('end', dieBeforeEvaluatorReady);
673
+ worker.once('end', () => {
674
+ debug('dcp-worker:exit')('Trapped DistributiveWorker<end>');
675
+ process.nextTick(() => cleanupThenExit(exitCodes.normal));
676
+ /* clean up at the end of tick so that other end event handlers can fire. */
677
+ });
652
678
 
653
- if (Object.keys(workerInfo.worktimes).length > 0)
679
+ if (!evaluatorInfo)
680
+ {
681
+ console.warn(` . Could not contact dcp-evaluator on ${utils.shortLoc(sandboxConfig.location)} to start...`);
682
+ await cleanupThenExit(exitCodes.normal);
683
+ }
684
+
685
+ if (Object.keys(evaluatorInfo.worktimes).length > 0)
654
686
  {
655
687
  console.info(' . Worktimes Available:');
656
- for (const wt of workerInfo.worktimes)
688
+ for (const wt of evaluatorInfo.worktimes)
657
689
  console.info(` - ${wt.name}@${wt.version}`);
658
690
  }
659
691
 
660
- if (!workerInfo.webgpu.enabled)
692
+ if (!evaluatorInfo.webgpu.enabled)
661
693
  {
662
694
  console.info(' . WebGPU not enabled');
663
695
  systemStateInfo.gpu = false;
@@ -671,9 +703,9 @@ async function main()
671
703
  else
672
704
  {
673
705
  console.info(' . WebGPU available. GPU info:');
674
- for (let descriptor in workerInfo.webgpu.info)
675
- console.info(` - ${descriptor}: ${workerInfo.webgpu.info[descriptor]}`);
676
- systemStateInfo.gpu = Object.assign({}, workerInfo.webgpu.info);
706
+ for (let descriptor in evaluatorInfo.webgpu.info)
707
+ console.info(` - ${descriptor}: ${evaluatorInfo.webgpu.info[descriptor]}`);
708
+ systemStateInfo.gpu = Object.assign({}, evaluatorInfo.webgpu.info);
677
709
 
678
710
  /** @todo move bannedGPU support into DistributiveWorker class /wg jun 2025 */
679
711
  const bannedGPUs = Array.isArray(worker.config.bannedGPUs) ? worker.config.bannedGPUs : [ worker.config.bannedGPUs ];
@@ -681,7 +713,7 @@ async function main()
681
713
  {
682
714
  for (let descriptor in banEntry)
683
715
  {
684
- if (banEntry[descriptor].test(workerInfo.webgpu.info[descriptor]))
716
+ if (banEntry[descriptor].test(evaluatorInfo.webgpu.info[descriptor]))
685
717
  {
686
718
  console.error(' * This GPU is not supported; disabling');
687
719
  worker.config.cores = { cpu: worker.config.cores.cpu, gpu: 0 };
@@ -701,20 +733,17 @@ async function main()
701
733
  }
702
734
  else
703
735
  {
704
- worker.runInfo.gpuType = workerInfo.webgpu.info.device;
736
+ worker.runInfo.gpuType = evaluatorInfo.webgpu.info.device;
705
737
  worker.runInfo.gpuCores = 1;
706
738
  }
707
739
  }
708
740
  }
709
741
 
710
- if (!workerIsStopping)
711
- {
712
- console.info(` . Utilization Target: cpu: ${String(Math.round(worker.config.utilization.cpu * 100)).padStart(3, ' ')}% `
713
- + `gpu: ${Math.round(worker.config.utilization.gpu * 100)}%`);
714
- console.info(` . Configured Cores: cpu: ${String(worker.config.cores.cpu).padEnd(3, ' ')} `
742
+ console.info(` . Utilization Target: cpu: ${String(Math.round(worker.config.utilization.cpu * 100)).padStart(3, ' ')}% `
743
+ + `gpu: ${Math.round(worker.config.utilization.gpu * 100)}%`);
744
+ console.info(` . Configured Cores: cpu: ${String(worker.config.cores.cpu).padEnd(3, ' ')} `
715
745
  + ` gpu: ${worker.config.cores.gpu}`);
716
- console.info(` . Maximum Sandboxes: ${worker.config.maxSandboxes}`);
717
- }
746
+ console.info(` . Maximum Sandboxes: ${worker.config.maxSandboxes}`);
718
747
  console.info(' . Supervisor version: ' + worker.supervisorVersion);
719
748
  console.info(' . Console type: ' + operatingMode.consoleType);
720
749
  console.info(' . Logging: ' + (loggingOptions.loggers.join(', ') || 'none'));
@@ -730,12 +759,17 @@ async function main()
730
759
  return;
731
760
  require('../lib/check-scheduler-version').check();
732
761
 
733
- worker.on('end', async () => {
734
- if (process.exitCode === exitCodes.lostRef)
735
- process.exitCode = exitCodes.normal;
736
- await cleanupThenExit(exitCodes.normal);
762
+ /* Hold the worker.p$stop promise between the stop and end events, for use by cleanUpThenExit() */
763
+ worker.on('stop', () => {
764
+ debug('dcp-worker:exit')('Trapped DistributiveWorker<stop> stop event with running worker');
765
+ worker.on('error', (error) => {
766
+ debug('dcp-worker:exit')('Trapped DistributiveWorker<error> event after stop event');
767
+ throw error;
768
+ });
737
769
  });
738
770
 
771
+ worker.ref();
772
+ worker.on('start', () => workerStarted = true);
739
773
  worker.on('error', function handleWorkerError(error) {
740
774
  if (worker.isWorkerError && worker.isWorkerError(error))
741
775
  {
@@ -760,6 +794,7 @@ async function main()
760
794
  console.error('Unrecoverable', error.code ? `${error.name} (${error.code})` : error.name,
761
795
  '- shutting down the worker:',
762
796
  process.env.DCP_SUPERVISOR_DEBUG_DISPLAY_MAX_INFO ? error : error.message);
797
+ debug('dcp-worker:error')(error);
763
798
  }
764
799
 
765
800
  /* Let the supervisor try to finish slices for up to three minutes after a single unrecoverable error. */
@@ -782,12 +817,13 @@ async function main()
782
817
  console.info(` ! not claming earnings in the amount of ${amount} ⊇ (can claim later)`);
783
818
  }
784
819
 
785
- worker.on('stop', () => { console.log(' - Worker is stopping') });
820
+ worker.on('stop', () => { console.log(' . Worker is stopping') });
786
821
  worker.on('end', () => { console.log(' * Worker has stopped') });
787
822
  worker.on('job', job => console.log(` . Job: ${job.name} ${job.id.slice(0,8)} ${job.description || ''} ${job.link || ''}`));
788
823
  worker.on('start', () => console.info(' * Ready.\n'));
789
824
 
790
- await worker.start();
825
+ if (!noStartWorker)
826
+ await worker.start();
791
827
  } /* main() */
792
828
 
793
829
  /**
@@ -863,31 +899,48 @@ async function handleUnhandled(error)
863
899
  */
864
900
  async function cleanupThenExit(exitCode)
865
901
  {
866
- debug('dcp-worker:exit')('cleanup and exit, code', exitCode);
867
- if (worker)
868
- worker.stop(true); /* toggle internal worker state so it won't surprise us by starting sandboxes */
902
+ const pr$end = Promise.withResolvers();
869
903
 
870
- const p$note = utils.noteWorkerShutdown(worker);
871
- if (cleanupThenExit.busy)
904
+ debug('dcp-worker:exit')('cleanup and exit, code', exitCode);
905
+ if (process.exitCode === exitCodes.lostRef || process.exitCode === exitCodes.normal)
906
+ process.exitCode = exitCode;
907
+ if (!cleanupThenExit.busy)
908
+ cleanupThenExit.busy = true;
909
+ else
872
910
  {
873
- if (!process.exitCode)
874
- process.exitCode = exitCode;
875
911
  debug('dcp-worker:exit')('exit race, bailing on cleanup for exit code', exitCode);
876
912
  return;
877
913
  }
878
-
879
- worker = false;
880
- cleanupThenExit.busy = true;
881
- process.exitCode = exitCode;
882
-
883
914
  /* Final event loop reference; keeps the process alive while the loggers unhook */
884
915
  setTimeout(() => {
885
- console.warn(' *** logger cleanup timeout expired ***');
916
+ debug('dcp-worker:exit')('cleanup timeout expired');
917
+ console.warn(' *** cleanup timeout expired ***');
886
918
  process.exit();
887
919
  }, 10 * 1e3).ref();
888
920
 
921
+ if (!worker || !workerStarted || worker.inspect.stopped)
922
+ {
923
+ debug('dcp-worker:exit')('cleanup will not wait for DistributiveWorker<end> event');
924
+ pr$end.resolve();
925
+ }
926
+ else
927
+ {
928
+ debug('dcp-worker:exit')('cleanup will wait for DistributiveWorker<end> event');
929
+ worker.once('end', pr$end.resolve);
930
+ worker.once('error', pr$end.reject);
931
+ worker.stop(true); /* toggle internal worker state so it won't surprise us by starting sandboxes */
932
+ }
933
+
934
+ console.info(' ! begin shutdown, exit code =', exitCode);
935
+ const p$workerShutdownTasks = Promise.all([
936
+ require('../lib/p2p-shared').shutdown(),
937
+ utils.noteWorkerShutdown(worker),
938
+ worker?.p$stop,
939
+ ]);
940
+
889
941
  try
890
942
  {
943
+ debug('dcp-worker:exit')('unhooking process events');
891
944
  process.off('SIGESC', handleSigDeath);
892
945
  process.off('SIGINT', handleSigDeath);
893
946
  process.off('SIGTERM', handleSigDeath);
@@ -895,11 +948,18 @@ async function cleanupThenExit(exitCode)
895
948
  process.off('unhandledRejection', handleUnhandled);
896
949
  process.off('uncaughtException', handleUnhandled);
897
950
  if (console.close)
951
+ {
952
+ debug('dcp-worker:exit')('closing console');
898
953
  console.close();
899
- loggers.unhook()
954
+ }
955
+ debug('dcp-worker:exit')('waiting for DistributiveWorker<end> event');
956
+ await pr$end.promise;
957
+ debug('dcp-worker:exit')('unhooking loggers');
958
+ await loggers.unhook()
959
+ .then(() => debug('dcp-worker:exit')('loggers unhooked'))
900
960
  .catch(error => { console.error('Error unhooking loggers:', error) })
901
961
  .finally(async () => {
902
- await p$note;
962
+ await p$workerShutdownTasks.catch(console.error);
903
963
  debug('dcp-worker:exit')('done cleanup, exitCode=' + process.exitCode);
904
964
  process.exit();
905
965
  });
@@ -939,6 +999,7 @@ async function handleSigDeath(signalName, signal)
939
999
  });
940
1000
  }
941
1001
 
1002
+ noStartWorker = true; /* sigint before worker.start should not start worker */
942
1003
  const warning = signal
943
1004
  ? `\ntrapped ${signalName}, signal ${signal}`
944
1005
  : `\ntrapped ${signalName === 'SIGESC' ? '<esc>' : signalName}`;