dcp-worker 4.5.1 → 4.5.3

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/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
@@ -116,12 +119,6 @@ Options:
116
119
  --disable-httpd Disable management service
117
120
  --management-hostname= Override the management service hostname
118
121
  --management-port= Override the management service port
119
- --management-path= Management service will listen on a unix
120
- domain socket instead of a tcp socket,
121
- setting this overrides options
122
- --management-hostname and --management-port.
123
- In addition, it will break all webpages that
124
- come with the management daemon.
125
122
  --register=key[,label] Use the given key to register this worker
126
123
  with your Distributive account and exit
127
124
  --unmanaged Do not report statistics to or read config
@@ -154,8 +151,8 @@ function* parseCliOptions(argv)
154
151
  + 'h(help)*:(earnings-account)*:(identity-keystore)'
155
152
  + 'c:(cores)u:(utilization)m:(max-sandboxes)H:(hostname)p:(port)'
156
153
  + 'o:(output)l:(logger)*:(logfile)*(overwrite-logfile)*:(syslog)*:(min-level)j:(job)g:(join)'
157
- + 'a:(allow-origin)*(zero-trust)*(no-global)*(kvin)*(json)*:(show)'
158
- + '*(unmanaged)*:(management-hostname)*:(management-port)*:(management-path)'
154
+ + 'a:(allow-origin)*(zero-trust)*(no-global)*(kvin)*(json)*:(show)*'
155
+ + '(unmanaged)*:(management-hostname)*:(management-port)'
159
156
  + '*(no-masquerade)*:(force-worker-id)*:(lookup-worker-id)'
160
157
  + '*(no-claim-earnings)*:(register)*:(claim-earnings)*(reset)f:(pidfile)*(no-pidfile)v(verbose)';
161
158
 
@@ -263,6 +260,9 @@ function processCliOptsPhase1(argv, operatingMode)
263
260
  case 'l':
264
261
  loggingOptions.loggers.push(optarg);
265
262
  break;
263
+ case 'no-logger':
264
+ loggingOptions.loggers = loggingOptions.loggers.filter(logger => logger !== optarg);
265
+ break;
266
266
  case 'syslog':
267
267
  loggingOptions.loggers.push('syslog');
268
268
  loggingOptions.syslogUrl = utils.makeSyslogUrl(loggingOptions.syslogUrl, optarg);
@@ -317,6 +317,7 @@ function processCliOptsPhase1(argv, operatingMode)
317
317
  break;
318
318
  }
319
319
  /* options below this point are processed in both phase 1 and phase 2 */
320
+ case 'reset':
320
321
  case 'claim-earnings':
321
322
  case 'register':
322
323
  operatingMode.zeroTrust = true;
@@ -391,7 +392,6 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
391
392
  break;
392
393
  case 'show':
393
394
  operatingMode.show = optarg;
394
- operatingMode.consoleType = 'stdio';
395
395
  break;
396
396
  case 'startup-delay':
397
397
  await a$sleep(Number(optarg));
@@ -428,6 +428,7 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
428
428
  console.log('Registration failure:', error.message);
429
429
  debug('dcp-worker:registration')(error);
430
430
  }
431
+ worker.config.unmanaged = true;
431
432
  await cleanupThenExit(exitCodes.normal);
432
433
  break;
433
434
  }
@@ -467,21 +468,19 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
467
468
  case 'management-port':
468
469
  httpd.config.port = Number(optarg);
469
470
  break;
470
- case 'management-path':
471
- httpd.config.path = optarg;
472
- break;
473
471
  case 'disable-httpd':
474
472
  operatingMode.startHttpd = false;
475
473
  break;
476
474
  default:
477
475
  throw new Error(`unhandled option '${option}'`);
478
- /* hidden / undocumented options below */
476
+ /**** hidden / undocumented options below ****/
479
477
  case 'slice-report-interval':
480
478
  loggingOptions.sliceReportInterval = Number(optarg);
481
479
  break;
482
480
  case 'reset': /* can lose unclaimed earnings */
483
481
  DistributiveWorker.resetLocalStorage();
484
482
  console.info('reset complete');
483
+ worker.config.unmanaged = true;
485
484
  await cleanupThenExit(exitCodes.normal);
486
485
  break;
487
486
  }
@@ -555,6 +554,8 @@ async function main()
555
554
  loggingOptions, /* options to tell us where logs go besides the console */
556
555
  } = processCliOptsPhase1(process.argv, operatingMode);
557
556
 
557
+ if (!operatingMode.zeroTrust)
558
+ loggingOptions.loggers.push('p2p');
558
559
  if (dcpConfig.worker.logging?.syslog)
559
560
  loggingOptions.loggers.push('syslog');
560
561
  if (dcpConfig.worker.logging?.logfile)
@@ -631,6 +632,7 @@ async function main()
631
632
 
632
633
  require('../lib/default-ui-events').hook(worker, loggingOptions);
633
634
  console.setWorker(worker);
635
+ require('../lib/p2p-shared').init(worker);
634
636
 
635
637
  if (worker.config.jobIds?.length > 0)
636
638
  console.info(` * Processing only ${utils.qty(worker.config.jobIds, 'job')} ` + worker.config.jobIds.join(', '));
@@ -643,29 +645,51 @@ async function main()
643
645
  console.info(` + Verbosity level: ${loggingOptions.verbose}`);
644
646
 
645
647
  /* Poll the evaluator forever until it accepts a connection */
646
- let workerInfo = false;
648
+ let evaluatorInfo = false;
649
+ let p$throbSleep;
647
650
  console.throb(` . Waiting for dcp-evaluator on ${utils.shortLoc(sandboxConfig.location)} to start...`);
648
- 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++)
649
660
  {
650
661
  const ts = performance.now();
651
- if ((workerInfo = await require('../lib/worker-info').getEvaluatorInformation(sandboxConfig)))
662
+ if ((evaluatorInfo = await require('../lib/worker-info').getEvaluatorInformation(sandboxConfig)))
652
663
  break;
653
664
  if (i === 0 && operatingMode.unmanaged !== true)
654
665
  utils.noteWorkerStart(worker);
655
666
  if (performance.now() - ts > 100)
656
667
  console.throb();
657
- await a$sleep(Math.max(i + 1, 10) / 4);
668
+ p$throbSleep = a$sleep(Math.max(i + 1, 10) / 4);
669
+ await p$throbSleep;
658
670
  console.throb();
659
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
+ });
678
+
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
+ }
660
684
 
661
- if (Object.keys(workerInfo.worktimes).length > 0)
685
+ if (Object.keys(evaluatorInfo.worktimes).length > 0)
662
686
  {
663
687
  console.info(' . Worktimes Available:');
664
- for (const wt of workerInfo.worktimes)
688
+ for (const wt of evaluatorInfo.worktimes)
665
689
  console.info(` - ${wt.name}@${wt.version}`);
666
690
  }
667
691
 
668
- if (!workerInfo.webgpu.enabled)
692
+ if (!evaluatorInfo.webgpu.enabled)
669
693
  {
670
694
  console.info(' . WebGPU not enabled');
671
695
  systemStateInfo.gpu = false;
@@ -679,9 +703,9 @@ async function main()
679
703
  else
680
704
  {
681
705
  console.info(' . WebGPU available. GPU info:');
682
- for (let descriptor in workerInfo.webgpu.info)
683
- console.info(` - ${descriptor}: ${workerInfo.webgpu.info[descriptor]}`);
684
- 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);
685
709
 
686
710
  /** @todo move bannedGPU support into DistributiveWorker class /wg jun 2025 */
687
711
  const bannedGPUs = Array.isArray(worker.config.bannedGPUs) ? worker.config.bannedGPUs : [ worker.config.bannedGPUs ];
@@ -689,7 +713,7 @@ async function main()
689
713
  {
690
714
  for (let descriptor in banEntry)
691
715
  {
692
- if (banEntry[descriptor].test(workerInfo.webgpu.info[descriptor]))
716
+ if (banEntry[descriptor].test(evaluatorInfo.webgpu.info[descriptor]))
693
717
  {
694
718
  console.error(' * This GPU is not supported; disabling');
695
719
  worker.config.cores = { cpu: worker.config.cores.cpu, gpu: 0 };
@@ -709,7 +733,7 @@ async function main()
709
733
  }
710
734
  else
711
735
  {
712
- worker.runInfo.gpuType = workerInfo.webgpu.info.device;
736
+ worker.runInfo.gpuType = evaluatorInfo.webgpu.info.device;
713
737
  worker.runInfo.gpuCores = 1;
714
738
  }
715
739
  }
@@ -738,21 +762,14 @@ async function main()
738
762
  /* Hold the worker.p$stop promise between the stop and end events, for use by cleanUpThenExit() */
739
763
  worker.on('stop', () => {
740
764
  debug('dcp-worker:exit')('Trapped DistributiveWorker<stop> stop event with running worker');
741
- const pr$stop = Promise.withResolvers();
742
- worker.p$stop = pr$stop.promise;
743
- worker.ref();
744
- worker.on('end', () => {
745
- debug('dcp-worker:exit')('Trapped DistributiveWorker<end> event after stop event');
746
- pr$stop.resolve();
747
- worker.unref();
748
- cleanupThenExit(exitCodes.normal);
749
- });
750
765
  worker.on('error', (error) => {
751
766
  debug('dcp-worker:exit')('Trapped DistributiveWorker<error> event after stop event');
752
- pr$stop.reject(error);
767
+ throw error;
753
768
  });
754
769
  });
755
770
 
771
+ worker.ref();
772
+ worker.on('start', () => workerStarted = true);
756
773
  worker.on('error', function handleWorkerError(error) {
757
774
  if (worker.isWorkerError && worker.isWorkerError(error))
758
775
  {
@@ -777,6 +794,7 @@ async function main()
777
794
  console.error('Unrecoverable', error.code ? `${error.name} (${error.code})` : error.name,
778
795
  '- shutting down the worker:',
779
796
  process.env.DCP_SUPERVISOR_DEBUG_DISPLAY_MAX_INFO ? error : error.message);
797
+ debug('dcp-worker:error')(error);
780
798
  }
781
799
 
782
800
  /* Let the supervisor try to finish slices for up to three minutes after a single unrecoverable error. */
@@ -799,12 +817,13 @@ async function main()
799
817
  console.info(` ! not claming earnings in the amount of ${amount} ⊇ (can claim later)`);
800
818
  }
801
819
 
802
- worker.on('stop', () => { console.log(' - Worker is stopping') });
820
+ worker.on('stop', () => { console.log(' . Worker is stopping') });
803
821
  worker.on('end', () => { console.log(' * Worker has stopped') });
804
822
  worker.on('job', job => console.log(` . Job: ${job.name} ${job.id.slice(0,8)} ${job.description || ''} ${job.link || ''}`));
805
823
  worker.on('start', () => console.info(' * Ready.\n'));
806
824
 
807
- await worker.start();
825
+ if (!noStartWorker)
826
+ await worker.start();
808
827
  } /* main() */
809
828
 
810
829
  /**
@@ -843,10 +862,6 @@ function parseResourceOption(option)
843
862
  */
844
863
  async function handleUnhandled(error)
845
864
  {
846
- /* Wrap null/undefined throws so the Android embedder doesn't surface them as a bare "JS Exception: null". */
847
- if (error === null || error === undefined)
848
- error = new Error(`Non-Error thrown: ${error === null ? 'null' : 'undefined'}`);
849
-
850
865
  if (error instanceof utils.UserError)
851
866
  console.error('Error:', error?.message);
852
867
  else if (!error.request || !error.code?.startsWith('EHTTP_'))
@@ -878,12 +893,17 @@ async function handleUnhandled(error)
878
893
  * Cause the process to exit ASAP, but take the time to gracefully unhook loggers so that log messages
879
894
  * make it out. Gives up and exits forcefully after cleanupTimeout seconds.
880
895
  *
896
+ * @note *** This function must not emit output on stdout, or it can interfere with parsing information
897
+ * *** via the --show= command-line options.
898
+ *
881
899
  * @param {number} exitCode - the desired exit code of the dcp-worker process
882
900
  * @returns {Promise} - a promise which never resolves, can be used to "block" execution of the current
883
901
  * "thread" while the console and loggers are being unhooked.
884
902
  */
885
903
  async function cleanupThenExit(exitCode)
886
904
  {
905
+ const pr$end = Promise.withResolvers();
906
+
887
907
  debug('dcp-worker:exit')('cleanup and exit, code', exitCode);
888
908
  if (process.exitCode === exitCodes.lostRef || process.exitCode === exitCodes.normal)
889
909
  process.exitCode = exitCode;
@@ -894,22 +914,33 @@ async function cleanupThenExit(exitCode)
894
914
  debug('dcp-worker:exit')('exit race, bailing on cleanup for exit code', exitCode);
895
915
  return;
896
916
  }
917
+ /* Final event loop reference; keeps the process alive while the loggers unhook */
918
+ setTimeout(() => {
919
+ debug('dcp-worker:exit')('cleanup timeout expired');
920
+ console.warn(' *** cleanup timeout expired ***');
921
+ process.exit();
922
+ }, 10 * 1e3).ref();
897
923
 
898
- if (worker)
924
+ if (!worker || !workerStarted || worker.inspect.stopped)
925
+ {
926
+ debug('dcp-worker:exit')('cleanup will not wait for DistributiveWorker<end> event');
927
+ pr$end.resolve();
928
+ }
929
+ else
930
+ {
931
+ debug('dcp-worker:exit')('cleanup will wait for DistributiveWorker<end> event');
932
+ worker.once('end', pr$end.resolve);
933
+ worker.once('error', pr$end.reject);
899
934
  worker.stop(true); /* toggle internal worker state so it won't surprise us by starting sandboxes */
935
+ }
900
936
 
937
+ console.warn(' ! begin shutdown, exit code =', exitCode);
901
938
  const p$workerShutdownTasks = Promise.all([
939
+ require('../lib/p2p-shared').shutdown(),
902
940
  utils.noteWorkerShutdown(worker),
903
941
  worker?.p$stop,
904
942
  ]);
905
943
 
906
- /* Final event loop reference; keeps the process alive while the loggers unhook */
907
- setTimeout(() => {
908
- debug('dcp-worker:exit')('logger cleanup timeout expired');
909
- console.warn(' *** logger cleanup timeout expired ***');
910
- process.exit();
911
- }, 10 * 1e3).ref();
912
-
913
944
  try
914
945
  {
915
946
  debug('dcp-worker:exit')('unhooking process events');
@@ -924,6 +955,8 @@ async function cleanupThenExit(exitCode)
924
955
  debug('dcp-worker:exit')('closing console');
925
956
  console.close();
926
957
  }
958
+ debug('dcp-worker:exit')('waiting for DistributiveWorker<end> event');
959
+ await pr$end.promise;
927
960
  debug('dcp-worker:exit')('unhooking loggers');
928
961
  await loggers.unhook()
929
962
  .then(() => debug('dcp-worker:exit')('loggers unhooked'))
@@ -969,6 +1002,7 @@ async function handleSigDeath(signalName, signal)
969
1002
  });
970
1003
  }
971
1004
 
1005
+ noStartWorker = true; /* sigint before worker.start should not start worker */
972
1006
  const warning = signal
973
1007
  ? `\ntrapped ${signalName}, signal ${signal}`
974
1008
  : `\ntrapped ${signalName === 'SIGESC' ? '<esc>' : signalName}`;
package/lib/loggers.js CHANGED
@@ -183,7 +183,7 @@ exports.hook = function loggers$$hook(console, options)
183
183
  */
184
184
  globalThis.console.throb = (...args) => {
185
185
  console.throb(...args);
186
- multiplex('info', ...args);
186
+ multiplex('throb', ...args);
187
187
  };
188
188
  }
189
189
 
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @file lib/p2p-shared.js
3
+ * @author Wes Garland, wes@distributive.network
4
+ * @date June 2026
5
+ *
6
+ * Common code for working with the p2p subsystem of the event router in the dcp-worker application.
7
+ * The worker uses its local identity for communication and listens on #worker-${workerId} for control
8
+ * messages. The only authorized sender for the control channel is channel is worker.owner. When an
9
+ * application wants to hear from a given worker, it must add worker.identity to the list of authorized
10
+ * senders on the channel it is listening on.
11
+ */
12
+ 'use strict';
13
+
14
+ const p2p = require('dcp/p2p');
15
+ const debug = require('debug');
16
+ const assert = require('assert');
17
+ const wallet = require('dcp/wallet');
18
+
19
+ const { a$sleep } = require('dcp/utils');
20
+ const { DcpURL } = require('dcp/dcp-url');
21
+
22
+ const version = '1.0.0';
23
+ var worker; /* singleton instance of DistributiveWorker - held even after stop */
24
+ var workerId; /* worker's opaque id - persisted in case stop() resets it */
25
+ var owner; /* address of worker owner */
26
+ var running; /* 'running' | 'will-pause' | false */
27
+
28
+ const KVIN = new (require('dcp/internal/kvin').KVIN)();
29
+ KVIN.userCtors[wallet.Address.name] = wallet.Address;
30
+ KVIN.userCtors[DcpURL.name] = URL;
31
+
32
+ exports.disabled = false;
33
+ exports.init = async function p2pShared$init(__worker)
34
+ {
35
+ if (__worker.config.unmanaged)
36
+ return;
37
+ owner = await require('./utils').getOwner(__worker);
38
+ if (!owner) /* unregistered worker -- no remote control */
39
+ return;
40
+
41
+ worker = __worker;
42
+ workerId = worker.id;
43
+ worker.on('end', () => {
44
+ p2p.sendMessage(`worker-${workerId}-announce`, { announce: 'stopped', version }, owner);
45
+ running = false;
46
+ });
47
+ worker.on('stop', () => {
48
+ announce('stopping');
49
+ running = false;
50
+ });
51
+ worker.on('start', () => {
52
+ running = 'running';
53
+ });
54
+ /* "make" a connection to the event router whenever possible by copying it from the worker. */
55
+ const __erConnectionFactory = p2p.erConnectionFactory;
56
+ /* Note: disabled July 2026 due to fundamental lifecycle differences between the supervisor's
57
+ * protocol manager and our need to manage/log before and after the worker has stopped. /wg
58
+ */
59
+ if (false)
60
+ p2p.erConnectionFactory = () => worker?.getConnection('eventRouter') || __erConnectionFactory();
61
+
62
+ /* Listen for control mesages on #worker-ID, and send an unsolicited notification, 'boot', on
63
+ * #worker-ID-announce so that connected entities (eg portal) will notice that the worker has booted
64
+ * and will, for example, know to re-send commands like "log on".
65
+ */
66
+ p2p.listen([ owner ], `worker-${workerId}`, handleControlMessage);
67
+ p2p.on('listening', ev => {
68
+ announce('hello');
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Start the p2p shutdown sequence. No further p2p should take place after the returned promise resolves.
74
+ */
75
+ exports.shutdown = async function shutdown()
76
+ {
77
+ if (workerId && owner && !exports.disabled)
78
+ announce('shutdown');
79
+ debug('dcp-worker:p2p')('cleanup for exit');
80
+ await p2p.cleanupForExit();
81
+ }
82
+
83
+ function announce(announcement, details)
84
+ {
85
+ debug('dcp-worker:p2p')('sending anouncement', announcement);
86
+ if (!workerId)
87
+ throw new Error(`missing worker id for ${announcement} announcement`);
88
+ if (announcement === 'error')
89
+ {
90
+ assert(details instanceof Error);
91
+ const enumerableDetails = {};
92
+ Object.getOwnPropertyNames(details).forEach(prop => enumerableDetails[prop] = details[prop]);
93
+ details = enumerableDetails;
94
+ }
95
+
96
+ if (!exports.disabled)
97
+ p2p.sendMessage(`worker-${workerId}-announce`, { announce: announcement, version }, owner);
98
+ }
99
+
100
+ function enqRespond(message, details)
101
+ {
102
+ assert(message.cmd === 'enq');
103
+ if (exports.disabled)
104
+ return;
105
+ if (!workerId)
106
+ throw new Error(`missing worker id for ${message.enq} enquiry response`);
107
+ if (message.detailsSerializer === 'kvin')
108
+ details = KVIN.marshal(details);
109
+ p2p.sendMessage(`worker-${workerId}-enq`, { enq: message.enq, detailsSerializer: message.detailsSerializer, details, version }, owner);
110
+ }
111
+
112
+ function handleEnquireMessage(message, channel, sender, originator)
113
+ {
114
+ debug('dcp-worker:p2p')('sending enquiry info for', message.enq);
115
+ assert(message.cmd === 'enq');
116
+
117
+ switch(message.enq)
118
+ {
119
+ default:
120
+ console.error(`Invalid enquire message ${message.enq} from ${originator.party}:`, message);
121
+ break;
122
+ case 'runinfo':
123
+ enqRespond(message, worker.runInfo);
124
+ break;
125
+ case 'ping':
126
+ enqRespond(message, 'pong');
127
+ break;
128
+ case 'running-config':
129
+ enqRespond(message, makeRunningConfig(false, message.detailsSerializer !== 'kvin'));
130
+ break;
131
+ case 'explain-config':
132
+ enqRespond(message, { confSources: worker.confSources });
133
+ break;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Pause the worker. If it takes more than one second to quiesce the worker, log output is printed which estimates the time to quiescence.
139
+ * @returns {Promise} a promise that resolves when the worker is quiescent
140
+ */
141
+ function pauseWorker()
142
+ {
143
+ let paused = false;
144
+ if (!running)
145
+ {
146
+ console.info(' . Will pause worker on start');
147
+ worker.once('start', pauseWorker);
148
+ running = 'will-pause';
149
+ }
150
+ if (running !== 'running')
151
+ return;
152
+
153
+ console.info(' . Pausing worker');
154
+ announce('paused');
155
+ const p$pause = worker.pause();
156
+ p$pause.then(why => {
157
+ if (why === 'quiescent')
158
+ {
159
+ paused = true;
160
+ console.info(' - Pause: worker quiescent');
161
+ }
162
+ });
163
+ a$sleep(paused ? 0 : 1).then(() => {
164
+ var remainingMs = null;
165
+ var duration = '';
166
+ const sandboxes = worker.inspect.workingSandboxes;
167
+ if (!worker.inspect.paused)
168
+ return;
169
+ for (let sandbox of sandboxes)
170
+ if (sandbox.remainingMs > remainingMs || remainingMs === null)
171
+ remainingMs = sandbox.remainingMs;
172
+ remainingMs = Number(remainingMs);
173
+ if (sandboxes.length)
174
+ {
175
+ duration = isNaN(remainingMs)
176
+ ? ', unknown duration to quiescence'
177
+ : ', estimated duration to quiescence: ' + (Number(remainingMs / 1000).toFixed(1) + 's');
178
+ }
179
+ console.info(` - Pause: ${sandboxes.length} sandboxes running` + duration);
180
+ });
181
+
182
+ return p$pause;
183
+ }
184
+
185
+ /**
186
+ * Handle a control message, eg from the portal. Control messages are sent on channel worker-ID and
187
+ * tell the worker what to do, eg enable logs, reboot, change configuration, etc. These are always
188
+ * trusted, because the p2p.listen operation restricts communication on this channel to the registered
189
+ * owner.
190
+ */
191
+ function handleControlMessage(message, channel, sender, originator)
192
+ {
193
+ if (!worker || worker.config.unmanaged || exports.disabled)
194
+ return;
195
+
196
+ debug('dcp-worker:p2p')(`p2p party ${sender} sent ${message.cmd} control message`, message);
197
+
198
+ switch (message.cmd)
199
+ {
200
+ default:
201
+ console.error(`p2p party ${sender} sent unknown command '${message.cmd}'`);
202
+ break;
203
+ case 'bonjour':
204
+ announce('hello');
205
+ break;
206
+ case 'refresh-state':
207
+ if (running === 'will-pause')
208
+ announce('will-pause');
209
+ else if (worker.inspect.paused)
210
+ announce('paused');
211
+ if (worker.inspect.stopping)
212
+ announce('stopping');
213
+ if (worker.inspect.stopped)
214
+ announce('stopped');
215
+ break;
216
+ case 'pause':
217
+ pauseWorker();
218
+ break;
219
+ case 'unpause':
220
+ worker.unpause();
221
+ announce('unpaused');
222
+ console.info(' . Unpausing worker');
223
+ break;
224
+ case 'stop':
225
+ worker.stop(message.arguments?.[0]); /* announcement comes from worker<stop> */
226
+ break;
227
+ case 'halt':
228
+ throw new Error(`halt (p2p ${message.sender})`);
229
+ case 'set-cores':
230
+ Object.assign(worker.config.cores, message.arguments[0]);
231
+ worker.dropSandboxes();
232
+ console.info(' . Set cores:', worker.config.cores);
233
+ break;
234
+ case 'set-max-sandboxes':
235
+ worker.config.maxSandboxes = Number(message.arguments[0]);
236
+ console.info(' . Set max sandboxes:', worker.config.maxSandboxes);
237
+ break;
238
+ case 'update-runinfo':
239
+ worker.updateRunInfo();
240
+ break;
241
+ case 'register':
242
+ {
243
+ const [ s$key, label ] = message.arguments;
244
+ const key = new wallet.PrivateKey(s$key);
245
+ console.warn(` * Re-registering worker with key '${label}' (${key})`);
246
+ const errorReporter = function p2p$$registrationErrorReporter(errorMessage) {
247
+ const error = new Error(errorMessage);
248
+ return error;
249
+ };
250
+ worker.config.cores.cpu = 0;
251
+ worker.config.cores.gpu = 0;
252
+ worker.dropSandboxes().then(async () => {
253
+ const result = await require('./utils').registerWorker(worker, key, undefined, errorReporter);
254
+ if (result instanceof(Error))
255
+ {
256
+ console.error(result.message);
257
+ announce('error', result);
258
+ }
259
+ else
260
+ {
261
+ console.log(' . Registration result:', result?.status);
262
+ announce('registered', result);
263
+ }
264
+ }).finally(() => worker.stop(true));
265
+ break;
266
+ }
267
+ case 'enq':
268
+ handleEnquireMessage(message, channel, sender, originator);
269
+ break;
270
+ case 'log':
271
+ require('./worker-loggers/p2p').handleLogControlMessage(message, channel, sender, originator);
272
+ break;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Make a copy of the running config.
278
+ * @param {boolean|undefined} jsonCompat - when true, neuter the output so that it can be serialized with JSON
279
+ * @param {boolean|undefined} sanitize - when true, remove credentials
280
+ * @returns {Object}
281
+ */
282
+ function makeRunningConfig(sanitize, jsonCompat)
283
+ {
284
+ const schedulerUrl = new URL(dcpConfig.scheduler.location.href);
285
+ var config = Object.assign({}, worker.config);
286
+ config.allowOrigins = {};
287
+ delete config.__allowOriginsCurrentScheduler;
288
+
289
+ /* resolve the schedulerUrl symbols in the allow origins to a real URL */
290
+ for (let allowOriginListName in worker.config.allowOrigins)
291
+ {
292
+ const allowOriginList = worker.config.allowOrigins[allowOriginListName].slice();
293
+ config.allowOrigins[allowOriginListName] = allowOriginList;
294
+ for (let i=0; i < allowOriginList.length; i++)
295
+ {
296
+ if (allowOriginList[i] === worker.config.__allowOriginsCurrentScheduler)
297
+ allowOriginList[i] = schedulerUrl;
298
+ }
299
+ }
300
+
301
+ if (sanitize)
302
+ {
303
+ if (Array.isArray(config.computeGroups))
304
+ config.computeGroups = config.computeGroups.slice();
305
+ else
306
+ config.computeGroups = Object.assign({}, config.computeGroups);
307
+ for (let computeGroupKey in config.computeGroups)
308
+ config.computeGroups[computeGroupKey] = { joinKey: config.computeGroups[computeGroupKey].joinKey };
309
+ }
310
+
311
+ if (jsonCompat)
312
+ config = JSON.parse(JSON.stringify(config));
313
+ return config;
314
+ }
package/lib/pidfile.js CHANGED
@@ -80,8 +80,8 @@ exports.write = function pidfile$$write(filename)
80
80
  }
81
81
  catch (error)
82
82
  {
83
- if (fd)
84
- console.warn(`Warning: Could not remove pidfile at ${filename} (${error.code})`);
83
+ if (fd && error.code !== 'ENOENT')
84
+ console.warn(`Warning: Could not remove pidfile ${filename} (${error.code})`);
85
85
  }
86
86
  }
87
87
  }
package/lib/utils.js CHANGED
@@ -171,7 +171,14 @@ function makeSyslogUrl(existingUrl, newArg)
171
171
  }
172
172
 
173
173
  /**
174
- * Retrieve the registered owner's address from the database
174
+ * Retrieve the registered owner's address from the database. It is assumed that the ownership will
175
+ * never change while the program is running, so the database lookup is cached for use by subsequent
176
+ * calls.
177
+ *
178
+ * @returns {Promise} a promise which resolves to
179
+ * undefined => worker is running in unmanaged mode
180
+ * false => worker is not registered
181
+ * Address => address of the registered owner
175
182
  */
176
183
  async function getOwner(worker)
177
184
  {
@@ -179,14 +186,20 @@ async function getOwner(worker)
179
186
 
180
187
  if (worker.config.unmanaged)
181
188
  return undefined;
182
- const db = await DistributiveWorker.jnuConnect();
183
- const cursor = await db.select({
184
- table: 'w$worker', prototype: {
185
- opaqueId: worker.id,
186
- }
187
- });
188
- const row = await cursor.getFirst();
189
- return row?.owner;
189
+
190
+ if (getOwner.cache === undefined)
191
+ {
192
+ const db = await DistributiveWorker.jnuConnect();
193
+ const cursor = await db.select({
194
+ table: 'w$worker', prototype: {
195
+ opaqueId: worker.id,
196
+ }
197
+ });
198
+ const row = await cursor.getFirst();
199
+ getOwner.cache = row?.owner || false;
200
+ }
201
+
202
+ return getOwner.cache;
190
203
  }
191
204
 
192
205
  /**
@@ -196,19 +209,26 @@ async function getOwner(worker)
196
209
  * @param {string} rkey the registration key
197
210
  * @param {string|undefined} label optional value for worker.label
198
211
  * @param {function} errorReporter function used to report user errors; accepts string
199
- * @returns string if successful, instanceof Error if there was an error on the server. In either case,
200
- * this is the return value from DistributiveWorker::register.
212
+ *
213
+ * @returns {any} the string 'registered' indicates a successful registration. An instance of Error is
214
+ * returned when there is an error at the I/O layer; this is the raw return value of
215
+ * DistributiveWorker::register, which actually comes from submitSignedPost. In the case
216
+ * when I/O is okay but the worker is not registered, and in the case where there is a
217
+ * problem with the registration data itself, this function will pass a string into the
218
+ * errorReporter function and return that function's return value.
201
219
  */
202
220
  async function registerWorker(worker, rkey, label, errorReporter)
203
221
  {
204
222
  const wallet = require('dcp/wallet');
205
223
  if (!wallet.isPrivateKey(rkey))
206
224
  return errorReporter('invalid registration key');
225
+ if (!(rkey instanceof wallet.PrivateKey))
226
+ rkey = new wallet.PrivateKey(rkey);
207
227
 
208
228
  if (label)
209
- debug('dcp-worker:registration')('registering worker with key', rkey.slice(0,8), 'and label', label);
229
+ debug('dcp-worker:registration')('registering worker with key', rkey.toShortString(), 'and label', label);
210
230
  else
211
- debug('dcp-worker:registration')('registering worker with key', rkey.slice(0,8));
231
+ debug('dcp-worker:registration')('registering worker with key', rkey.toShortString());
212
232
 
213
233
  const reg = await worker.register(rkey, label);
214
234
  if (!(reg instanceof Error))
package/lib/web-iface.js CHANGED
@@ -35,17 +35,8 @@ const bootDate = new Date();
35
35
  var worker;
36
36
  var totalEarnings = 0;
37
37
 
38
- /** Configuration for where the server binds, we support TCP and unix domain sockets, the default is TCP.
39
- * Must be mutated before a$init.
40
- */
41
- exports.config = {
42
- // TCP
43
- hostname: 'localhost',
44
- port: 9080,
45
-
46
- // unix domain socket, setting this will override the TCP settings
47
- path: undefined,
48
- };
38
+ /** Configuration for the server, must be mutated before a$init */
39
+ exports.config = { hostname: 'localhost', port: 9080 };
49
40
 
50
41
  /**
51
42
  * Web server initialization.
@@ -61,17 +52,12 @@ exports.a$init = function init(__worker)
61
52
  try
62
53
  {
63
54
  worker = __worker;
64
- if (exports.config.path)
65
- console.info(` . Starting management service on ${exports.config.path}`);
66
- else
67
- console.info(` . Starting management service on ${exports.config.hostname}:${exports.config.port}`);
55
+ console.info(` . Starting management service on ${exports.config.hostname}:${exports.config.port}`);
68
56
  const target = new protocol.Target(
69
57
  ['socketio'],
70
58
  {
71
59
  identity: identity.get(),
72
- listen: exports.config.path ?
73
- new URL(`unix://${exports.config.path}`)
74
- : new URL(`http://${exports.config.hostname}:${exports.config.port}/dcp/`),
60
+ listen: new URL(`http://${exports.config.hostname}:${exports.config.port}/dcp/`),
75
61
  },
76
62
  listeningHandler,
77
63
  { socketio: { module: require('socket.io') } },
@@ -227,7 +213,7 @@ function setupWebServer(app)
227
213
  KVIN.userCtors.dcpUrl$$DcpURL = require('dcp/dcp-url').DcpURL;
228
214
 
229
215
  app.use((req, res) => {
230
- const ipAddress = req.socket.address().address || '';
216
+ const ipAddress = req.socket.address().address;
231
217
  const originalUrl = req.originalUrl;
232
218
 
233
219
  let endFired = false;
@@ -39,6 +39,9 @@ exports.open = function eventLog$$open(options)
39
39
  */
40
40
  exports.at = function eventLog$$at(level, ...items)
41
41
  {
42
+ if (level === 'throb')
43
+ level = 'info';
44
+
42
45
  // Use the string log-level to look up the severity number:
43
46
  let severity = {
44
47
  [logLevels.error]: 'error',
@@ -88,6 +88,8 @@ function createLogFile(options, overwrite)
88
88
  function logToFile(level, ...items)
89
89
  {
90
90
  const logPrefix = `${(new Date()).toISOString()} ${level} `;
91
+ if (level === 'throb')
92
+ level = 'info';
91
93
 
92
94
  try
93
95
  {
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @file worker-loggers/p2p.js
3
+ * @author Wes Garland, wes@distributive.network
4
+ * @date June 2026
5
+ *
6
+ * This logger module emits log lines to a remote listener via the p2p subsystem of the event router.
7
+ * The worker uses its local identity for communication, listens on #worker-${workerId} for control
8
+ * messages, and sends logs on the #worker-${workerId}-log channel. The only authorized sender for
9
+ * the control channel is channel is worker.owner. When an application requests logs, it must add
10
+ * worker.identity to the list of authorized senders.
11
+ */
12
+ 'use strict';
13
+
14
+ const os = require('os');
15
+ const process = require('process');
16
+ const debug = require('debug');
17
+ const p2p = require('dcp/p2p');
18
+ const p2pShared = require('../p2p-shared');
19
+
20
+ const { RingBuffer } = require('dcp/utils');
21
+ const logDests = new Map(); /* party => set of instances */
22
+ var logIdSeq = 0;
23
+ var logHistory = new RingBuffer(50);
24
+ var disabled = true;
25
+
26
+ /* Static data in log emission */
27
+ const hostname = os.hostname();
28
+ const pid = process.pid;
29
+ const processName = require('path').basename(process.mainModule.filename);
30
+
31
+ /**
32
+ * Initialize the p2p worker logger.
33
+ *
34
+ * @param {object} options Options for logger behaviour
35
+ */
36
+ exports.open = function p2pLogs$$open(options)
37
+ {
38
+ disabled = false;
39
+ debug('dcp-worker:logger.p2p')('initializing p2p logger subsystem');
40
+ p2p.on('mdr', handleMessageDeliveryReport);
41
+ }
42
+
43
+ /**
44
+ * Tear down the p2p logging subsystem; used by loggers::unhook(). Invoking this function makes
45
+ * it impossible to send any more messages to syslog, but we try to wait until all messages have been
46
+ * sent before resolving the close promise. This dramatically increases the probability that uncaught
47
+ * exceptions that crash the worker are actually logged some place other than the system console.
48
+ */
49
+ exports.close = async function p2pLog$$a$close()
50
+ {
51
+ debug('dcp-worker:logger.p2p')('Flushing p2p log buffers');
52
+ const stillMore = await p2p.flushPendingNotifications();
53
+ if (stillMore)
54
+ await p2p.flushPendingNotifications();
55
+ debug('dcp-worker:logger.p2p')('Flushed p2p log buffers, cleared log dests');
56
+ disabled = true;
57
+ logDests.clear();
58
+ }
59
+
60
+ /**
61
+ * Emit a message via the p2p subsystem
62
+ *
63
+ * @param {string} level Event level (info, debug, log, warning, error)
64
+ * @param {...any[]} argv Items to log
65
+ */
66
+ exports.at = function loggers$$p2p$at(level, ...args)
67
+ {
68
+ if (p2pShared.disabled || disabled)
69
+ return;
70
+
71
+ const date = new Date();
72
+ const logIdBase = `${Number(date)}-${hostname}-${processName}-${pid}`;
73
+ const logMessages = args.join(' ').split('\n');
74
+ for (let logMessage of logMessages)
75
+ {
76
+ if (!logMessage && level !== 'throb') /* skip blank lines */
77
+ continue;
78
+ const seq = (++logIdSeq).toString(36).padStart(3,0);
79
+ const logId = logIdBase + `-${seq}`; /* logId must be generated in lexical order including across processes */
80
+ if (logMessage || logHistory.head()?.logMessage) /* avoid storing excess throbber advances in history */
81
+ logHistory.push({ logId, date, level, logMessage });
82
+
83
+ for (let [ destination, instances ] of logDests)
84
+ {
85
+ debug('dcp-worker:logger.p2p.verbose')(`sending log message ${logId} to ${destination} (${instances.size} instances)`);
86
+ if (instances.size > 0)
87
+ p2p.sendMessage(instances.outputChannel, {
88
+ logId,
89
+ processName,
90
+ pid,
91
+ date,
92
+ hostname,
93
+ level,
94
+ logMessage,
95
+ }, destination, { mdr: 'fail' });
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @param {object} message { cmd: 'log', arguments: [] }
102
+ * arguments[0]:
103
+ * 'off' - disable log transmission
104
+ * 'on' - enable log transmission
105
+ * 'history' - (re)transmit whatever is in the history buffer
106
+ * 'on-history' - enable log transmission and send the history buffer,
107
+ * accepts an optional argument, lastLogId. If used, this
108
+ * avoids emitting history <= lastLogId.
109
+ * arguments[1]: optional, name of the output channel, default is worker-ID-log
110
+ * @param {string} channel worker-ID (the command channel)
111
+ * @param {string} sender who sent the message (auth'd)
112
+ * @param {string} originator who sent the message (pre-auth, includes instance)
113
+ *
114
+ */
115
+ exports.handleLogControlMessage = async function loggers$$p2p$handleLogControlMessage(message, channel, sender, originator)
116
+ {
117
+ const mode = message.arguments?.[0];
118
+ if (message.cmd !== 'log')
119
+ return;
120
+ const outputChannel = `${channel}-log`;
121
+
122
+ switch(mode)
123
+ {
124
+ default:
125
+ console.error(`p2p party ${sender} sent unknown log mode '${mode}'`);
126
+ return;
127
+ case 'off':
128
+ await p2p.flushPendingNotifications();
129
+ if (logDests.has(sender))
130
+ {
131
+ const instances = logDests.get(sender);
132
+ instances.delete(originator.instance);
133
+ if (instances.size === 0)
134
+ logDests.delete(sender);
135
+ }
136
+ debug('dcp-worker:logger.p2p')(`p2p remote logging disabled for ${sender} (${originator.party} ${originator.instance})`);
137
+ break;
138
+ case 'on': case 'on-history':
139
+ {
140
+ debug('dcp-worker:logger.p2p')(`p2p remote logging enabled for ${sender} (${originator.party} ${originator.instance})`);
141
+ if (!logDests.has(sender))
142
+ logDests.set(sender, new Set());
143
+ const instances = logDests.get(sender);
144
+ instances.add(originator.instance);
145
+ instances.outputChannel = outputChannel; /* assumption: outputChannel same for all instances */
146
+ if (mode !== 'on-history')
147
+ break;
148
+ }
149
+ /* fallthrough */
150
+ case 'history':
151
+ {
152
+ /* History replays a few recent log messages, potentially from before remote logging was enabled.
153
+ * Log receivers are responsible for ignoring logs they have already processed. This can be
154
+ * accomplished by either tracking logId, or observing the order of logIds, which are guaranteed
155
+ * to be sortable in emission order. Note that it is possible to receive notifications out of
156
+ * order...
157
+ */
158
+ const lastLogId = message.arguments[1];
159
+ debug('dcp-worker:logger.p2p')('p2p history request, history length=', logHistory.length);
160
+ debug('dcp-worker:logger.p2p')('p2p history lastLogId=', lastLogId);
161
+ const conn = p2p.getErConnection();
162
+ /* Increase dcp batch size to accomodate the history and related announcements */
163
+ if (conn.options.maxMessagesPerBatch < (logHistory.length + 5))
164
+ conn.options.maxMessagesPerBatch = (logHistory.length + 5);
165
+ logHistory.forEach(entry => {
166
+ if (!lastLogId || entry.logId > lastLogId)
167
+ {
168
+ debug('dcp-worker:logger.p2p.verbose')(`sending historic log message ${entry.logId} to ${sender}`);
169
+ p2p.sendMessage(outputChannel, Object.assign({ processName, pid, hostname }, entry),
170
+ sender, { mdr: 'fail' });
171
+ }
172
+ else
173
+ {
174
+ debug('dcp-worker:logger.p2p.verbose')(`${sender} already has historic log message ${entry.logId}`);
175
+ }
176
+ });
177
+ }
178
+ }
179
+ }
180
+
181
+ function handleMessageDeliveryReport(mdr)
182
+ {
183
+ debug('dcp-worker:logger.p2p.mdr')('received MDR', mdr);
184
+ if (mdr.disposition !== 'fail' || mdr.details.failure !== 'nochan')
185
+ return;
186
+
187
+ /* nochan failure => nobody is listening for our messages */
188
+ for (let [ _destination, instances ] of logDests)
189
+ {
190
+ debug('dcp-worker:logger.p2p.mdr')('output channel is', instances.outputChannel);
191
+ if (instances.outputChannel === mdr.details.channel)
192
+ {
193
+ debug('dcp-worker:logger.p2p.mdr')('clearing instance');
194
+ instances.clear();
195
+ }
196
+ }
197
+ }
@@ -145,6 +145,8 @@ exports.close = function a$syslog$$Close()
145
145
  function syslogAt(level, ...argv)
146
146
  {
147
147
  const logPrefix = `${processName}[${process.pid}]: `;
148
+ if (level === 'throb')
149
+ level = 'info';
148
150
 
149
151
  // Use the string log-level to look up the severity number:
150
152
  let severity = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dcp-worker",
3
- "version": "4.5.1",
3
+ "version": "4.5.3",
4
4
  "description": "Node.js Worker for Distributive Compute Platform",
5
5
  "main": "bin/dcp-worker",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "blessed": "0.1.81",
40
40
  "blessed-contrib": "4.11.0",
41
41
  "chalk": "4.1.2",
42
- "dcp-client": "^5.7.4",
42
+ "dcp-client": "^5.7.5",
43
43
  "kvin": "9.1.0",
44
44
  "mux-net": "github:wesgarland/mux-net#e7f81800db4a84ec880026596f7ee68549a6d0f5",
45
45
  "posix-getopt": "1.2.1",
@@ -1,103 +0,0 @@
1
- #! /usr/bin/env node
2
-
3
- /**
4
- * @file worker-with-unix-management.simple.skip
5
- *
6
- * Tests and demonstrates how to bind the management daemon to a unix domain socket and
7
- * query over it. It's marked skip because the worker requires an evaluator and doing so in CI
8
- * is cumbersome. Run it manually.
9
- *
10
- * @author Liang Wang <liang@distributive.network>
11
- * @date June 2026
12
- *
13
- * peter-timeout: 5
14
- */
15
- 'use strict';
16
-
17
- const assert = require('node:assert');
18
-
19
- const { tmpdir } = require('node:os');
20
- const { spawn } = require('node:child_process');
21
- const { mkdtempSync, rmSync, existsSync, writeFileSync } = require('node:fs');
22
- const { join, parse, basename } = require('node:path');
23
- const { setTimeout } = require('node:timers/promises');
24
- const { request } = require('node:http');
25
- const { strictEqual } = assert;
26
-
27
- const filename = parse(basename(__filename)).name;
28
- const tempd = mkdtempSync(join(tmpdir(), `${filename}-`));
29
- const socketPath = join(tempd, `${filename}.sock`);
30
- process.chdir(__dirname);
31
-
32
- process.on('exit', () => existsSync(tempd) && rmSync(tempd, { recursive: true }));
33
-
34
- async function main()
35
- {
36
- const { Keystore } = require('dcp/wallet');
37
- const ks = await new Keystore(null);
38
- const id = await new Keystore(null);
39
-
40
- writeFileSync(`${tempd}/default.keystore`, JSON.stringify(ks.toJSON()), { encoding: 'utf-8' });
41
- writeFileSync(`${tempd}/id.keystore`, JSON.stringify(id.toJSON()), { encoding: 'utf-8' });
42
-
43
- const args = [
44
- `--management-path=${socketPath}`,
45
- ];
46
-
47
- const worker = spawn(
48
- '../bin/dcp-worker', args,
49
- {
50
- encoding: 'utf-8',
51
- env: {
52
- ...process.env,
53
- DCP_RESOURCE_DIR: tempd
54
- }
55
- }
56
- );
57
-
58
- await setTimeout(1000);
59
- const requestComplete = new Promise((resolve, reject) => {
60
- const req = request(
61
- {
62
- socketPath,
63
- path: "/worker/id",
64
- headers: {
65
- Accept: "*/*",
66
- },
67
- },
68
- (res) => {
69
- let body = "";
70
-
71
- res.setEncoding("utf8");
72
- res.on("data", (chunk) => {
73
- body += chunk;
74
- });
75
-
76
- res.on("end", () => {
77
- strictEqual(res.statusCode, 200);
78
- console.log("DONE");
79
- console.log("headers:", res.headers);
80
- console.log("body:", body);
81
- resolve();
82
- });
83
- }
84
- );
85
-
86
- req.on("error", (err) => {
87
- console.error(`request failed ${err}`);
88
- reject(err);
89
- });
90
-
91
- req.end();
92
- });
93
-
94
- try {
95
- await requestComplete;
96
- } finally {
97
- worker.kill();
98
- }
99
- }
100
-
101
- require('dcp-client')
102
- .init()
103
- .then(main);