dcp-worker 4.5.0 → 4.5.1
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 +1 -0
- package/bin/collect-debug-info +73 -30
- package/bin/dcp-evaluator-manager +46 -15
- package/bin/dcp-task-probe +2 -5
- package/bin/dcp-worker +57 -27
- package/lib/utils.js +1 -1
- package/lib/web-iface.js +19 -5
- package/lib/worker-consoles/dashboard-console.js +9 -2
- package/package.json +9 -4
- package/tests/worker-with-options.simple +5 -1
- package/tests/worker-with-unix-management.simple.skip +103 -0
- package/www/admin/register-worker.html +1 -0
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
|
package/bin/collect-debug-info
CHANGED
|
@@ -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
|
|
29
|
+
*** Group information, or API keys.
|
|
30
30
|
|
|
31
31
|
`);
|
|
32
32
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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} ]
|
|
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} ]
|
|
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(
|
|
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
|
-
|
|
131
|
-
|
|
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 (
|
|
134
|
-
|
|
138
|
+
if (!hostnames[0])
|
|
139
|
+
{
|
|
140
|
+
hosts[0] = '::1';
|
|
141
|
+
hosts[1] = '127.0.0.1';
|
|
142
|
+
}
|
|
135
143
|
else
|
|
136
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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));
|
package/bin/dcp-task-probe
CHANGED
|
@@ -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
|
@@ -116,6 +116,12 @@ Options:
|
|
|
116
116
|
--disable-httpd Disable management service
|
|
117
117
|
--management-hostname= Override the management service hostname
|
|
118
118
|
--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.
|
|
119
125
|
--register=key[,label] Use the given key to register this worker
|
|
120
126
|
with your Distributive account and exit
|
|
121
127
|
--unmanaged Do not report statistics to or read config
|
|
@@ -148,8 +154,8 @@ function* parseCliOptions(argv)
|
|
|
148
154
|
+ 'h(help)*:(earnings-account)*:(identity-keystore)'
|
|
149
155
|
+ 'c:(cores)u:(utilization)m:(max-sandboxes)H:(hostname)p:(port)'
|
|
150
156
|
+ 'o:(output)l:(logger)*:(logfile)*(overwrite-logfile)*:(syslog)*:(min-level)j:(job)g:(join)'
|
|
151
|
-
+ 'a:(allow-origin)*(zero-trust)*(no-global)*(kvin)*(json)*:(show)
|
|
152
|
-
+ '(unmanaged)*:(management-hostname)*:(management-port)'
|
|
157
|
+
+ 'a:(allow-origin)*(zero-trust)*(no-global)*(kvin)*(json)*:(show)'
|
|
158
|
+
+ '*(unmanaged)*:(management-hostname)*:(management-port)*:(management-path)'
|
|
153
159
|
+ '*(no-masquerade)*:(force-worker-id)*:(lookup-worker-id)'
|
|
154
160
|
+ '*(no-claim-earnings)*:(register)*:(claim-earnings)*(reset)f:(pidfile)*(no-pidfile)v(verbose)';
|
|
155
161
|
|
|
@@ -314,6 +320,7 @@ function processCliOptsPhase1(argv, operatingMode)
|
|
|
314
320
|
case 'claim-earnings':
|
|
315
321
|
case 'register':
|
|
316
322
|
operatingMode.zeroTrust = true;
|
|
323
|
+
/* fallthrough */
|
|
317
324
|
case 'show':
|
|
318
325
|
operatingMode.consoleType = 'stdio';
|
|
319
326
|
/* fallthrough */
|
|
@@ -460,6 +467,9 @@ async function processCliOptsPhase2(argv, operatingMode, loggingOptions)
|
|
|
460
467
|
case 'management-port':
|
|
461
468
|
httpd.config.port = Number(optarg);
|
|
462
469
|
break;
|
|
470
|
+
case 'management-path':
|
|
471
|
+
httpd.config.path = optarg;
|
|
472
|
+
break;
|
|
463
473
|
case 'disable-httpd':
|
|
464
474
|
operatingMode.startHttpd = false;
|
|
465
475
|
break;
|
|
@@ -519,7 +529,6 @@ async function mergeDbAppConfig(remainArgv, operatingMode, loggingOptions)
|
|
|
519
529
|
*/
|
|
520
530
|
async function main()
|
|
521
531
|
{
|
|
522
|
-
var workerIsStopping = false;
|
|
523
532
|
const identity = require('dcp/identity');
|
|
524
533
|
/* handle file-wide dcp-client imports */
|
|
525
534
|
wallet = require('dcp/wallet');
|
|
@@ -582,7 +591,6 @@ async function main()
|
|
|
582
591
|
worker.runInfo.type = 'dcp-worker';
|
|
583
592
|
worker.on('warning', (...payload) => console.warn (...payload));
|
|
584
593
|
worker.on('claim', amount => console.log(` . transfered ${amount} ⊇ to ${worker.config.paymentAddress}`));
|
|
585
|
-
worker.on('stop', () => workerIsStopping = true );
|
|
586
594
|
await processCliOptsPhase2(remainArgv, operatingMode, loggingOptions);
|
|
587
595
|
|
|
588
596
|
if (operatingMode.show)
|
|
@@ -707,14 +715,11 @@ async function main()
|
|
|
707
715
|
}
|
|
708
716
|
}
|
|
709
717
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
+ `gpu: ${Math.round(worker.config.utilization.gpu * 100)}%`);
|
|
714
|
-
console.info(` . Configured Cores: cpu: ${String(worker.config.cores.cpu).padEnd(3, ' ')} `
|
|
718
|
+
console.info(` . Utilization Target: cpu: ${String(Math.round(worker.config.utilization.cpu * 100)).padStart(3, ' ')}% `
|
|
719
|
+
+ `gpu: ${Math.round(worker.config.utilization.gpu * 100)}%`);
|
|
720
|
+
console.info(` . Configured Cores: cpu: ${String(worker.config.cores.cpu).padEnd(3, ' ')} `
|
|
715
721
|
+ ` gpu: ${worker.config.cores.gpu}`);
|
|
716
|
-
|
|
717
|
-
}
|
|
722
|
+
console.info(` . Maximum Sandboxes: ${worker.config.maxSandboxes}`);
|
|
718
723
|
console.info(' . Supervisor version: ' + worker.supervisorVersion);
|
|
719
724
|
console.info(' . Console type: ' + operatingMode.consoleType);
|
|
720
725
|
console.info(' . Logging: ' + (loggingOptions.loggers.join(', ') || 'none'));
|
|
@@ -730,10 +735,22 @@ async function main()
|
|
|
730
735
|
return;
|
|
731
736
|
require('../lib/check-scheduler-version').check();
|
|
732
737
|
|
|
733
|
-
worker.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
738
|
+
/* Hold the worker.p$stop promise between the stop and end events, for use by cleanUpThenExit() */
|
|
739
|
+
worker.on('stop', () => {
|
|
740
|
+
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
|
+
worker.on('error', (error) => {
|
|
751
|
+
debug('dcp-worker:exit')('Trapped DistributiveWorker<error> event after stop event');
|
|
752
|
+
pr$stop.reject(error);
|
|
753
|
+
});
|
|
737
754
|
});
|
|
738
755
|
|
|
739
756
|
worker.on('error', function handleWorkerError(error) {
|
|
@@ -826,6 +843,10 @@ function parseResourceOption(option)
|
|
|
826
843
|
*/
|
|
827
844
|
async function handleUnhandled(error)
|
|
828
845
|
{
|
|
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
|
+
|
|
829
850
|
if (error instanceof utils.UserError)
|
|
830
851
|
console.error('Error:', error?.message);
|
|
831
852
|
else if (!error.request || !error.code?.startsWith('EHTTP_'))
|
|
@@ -864,30 +885,34 @@ async function handleUnhandled(error)
|
|
|
864
885
|
async function cleanupThenExit(exitCode)
|
|
865
886
|
{
|
|
866
887
|
debug('dcp-worker:exit')('cleanup and exit, code', exitCode);
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
888
|
+
if (process.exitCode === exitCodes.lostRef || process.exitCode === exitCodes.normal)
|
|
889
|
+
process.exitCode = exitCode;
|
|
890
|
+
if (!cleanupThenExit.busy)
|
|
891
|
+
cleanupThenExit.busy = true;
|
|
892
|
+
else
|
|
872
893
|
{
|
|
873
|
-
if (!process.exitCode)
|
|
874
|
-
process.exitCode = exitCode;
|
|
875
894
|
debug('dcp-worker:exit')('exit race, bailing on cleanup for exit code', exitCode);
|
|
876
895
|
return;
|
|
877
896
|
}
|
|
878
897
|
|
|
879
|
-
worker
|
|
880
|
-
|
|
881
|
-
|
|
898
|
+
if (worker)
|
|
899
|
+
worker.stop(true); /* toggle internal worker state so it won't surprise us by starting sandboxes */
|
|
900
|
+
|
|
901
|
+
const p$workerShutdownTasks = Promise.all([
|
|
902
|
+
utils.noteWorkerShutdown(worker),
|
|
903
|
+
worker?.p$stop,
|
|
904
|
+
]);
|
|
882
905
|
|
|
883
906
|
/* Final event loop reference; keeps the process alive while the loggers unhook */
|
|
884
907
|
setTimeout(() => {
|
|
908
|
+
debug('dcp-worker:exit')('logger cleanup timeout expired');
|
|
885
909
|
console.warn(' *** logger cleanup timeout expired ***');
|
|
886
910
|
process.exit();
|
|
887
911
|
}, 10 * 1e3).ref();
|
|
888
912
|
|
|
889
913
|
try
|
|
890
914
|
{
|
|
915
|
+
debug('dcp-worker:exit')('unhooking process events');
|
|
891
916
|
process.off('SIGESC', handleSigDeath);
|
|
892
917
|
process.off('SIGINT', handleSigDeath);
|
|
893
918
|
process.off('SIGTERM', handleSigDeath);
|
|
@@ -895,11 +920,16 @@ async function cleanupThenExit(exitCode)
|
|
|
895
920
|
process.off('unhandledRejection', handleUnhandled);
|
|
896
921
|
process.off('uncaughtException', handleUnhandled);
|
|
897
922
|
if (console.close)
|
|
923
|
+
{
|
|
924
|
+
debug('dcp-worker:exit')('closing console');
|
|
898
925
|
console.close();
|
|
899
|
-
|
|
926
|
+
}
|
|
927
|
+
debug('dcp-worker:exit')('unhooking loggers');
|
|
928
|
+
await loggers.unhook()
|
|
929
|
+
.then(() => debug('dcp-worker:exit')('loggers unhooked'))
|
|
900
930
|
.catch(error => { console.error('Error unhooking loggers:', error) })
|
|
901
931
|
.finally(async () => {
|
|
902
|
-
await p$
|
|
932
|
+
await p$workerShutdownTasks.catch(console.error);
|
|
903
933
|
debug('dcp-worker:exit')('done cleanup, exitCode=' + process.exitCode);
|
|
904
934
|
process.exit();
|
|
905
935
|
});
|
package/lib/utils.js
CHANGED
|
@@ -176,7 +176,6 @@ function makeSyslogUrl(existingUrl, newArg)
|
|
|
176
176
|
async function getOwner(worker)
|
|
177
177
|
{
|
|
178
178
|
const { DistributiveWorker } = require('dcp/worker');
|
|
179
|
-
const identity = require('dcp/identity');
|
|
180
179
|
|
|
181
180
|
if (worker.config.unmanaged)
|
|
182
181
|
return undefined;
|
|
@@ -385,6 +384,7 @@ exports.noteWorkerStart = async function noteWorkerStart(worker)
|
|
|
385
384
|
if (worker.config?.unmanaged)
|
|
386
385
|
return;
|
|
387
386
|
|
|
387
|
+
debug('dcp-worker:start')('noting worker start');
|
|
388
388
|
return worker.updateRunInfo({
|
|
389
389
|
lastStartTime: new Date(),
|
|
390
390
|
status: 'running',
|
package/lib/web-iface.js
CHANGED
|
@@ -35,8 +35,17 @@ const bootDate = new Date();
|
|
|
35
35
|
var worker;
|
|
36
36
|
var totalEarnings = 0;
|
|
37
37
|
|
|
38
|
-
/** Configuration for the server,
|
|
39
|
-
|
|
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
|
+
};
|
|
40
49
|
|
|
41
50
|
/**
|
|
42
51
|
* Web server initialization.
|
|
@@ -52,12 +61,17 @@ exports.a$init = function init(__worker)
|
|
|
52
61
|
try
|
|
53
62
|
{
|
|
54
63
|
worker = __worker;
|
|
55
|
-
|
|
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}`);
|
|
56
68
|
const target = new protocol.Target(
|
|
57
69
|
['socketio'],
|
|
58
70
|
{
|
|
59
71
|
identity: identity.get(),
|
|
60
|
-
listen:
|
|
72
|
+
listen: exports.config.path ?
|
|
73
|
+
new URL(`unix://${exports.config.path}`)
|
|
74
|
+
: new URL(`http://${exports.config.hostname}:${exports.config.port}/dcp/`),
|
|
61
75
|
},
|
|
62
76
|
listeningHandler,
|
|
63
77
|
{ socketio: { module: require('socket.io') } },
|
|
@@ -213,7 +227,7 @@ function setupWebServer(app)
|
|
|
213
227
|
KVIN.userCtors.dcpUrl$$DcpURL = require('dcp/dcp-url').DcpURL;
|
|
214
228
|
|
|
215
229
|
app.use((req, res) => {
|
|
216
|
-
const ipAddress = req.socket.address().address;
|
|
230
|
+
const ipAddress = req.socket.address().address || '';
|
|
217
231
|
const originalUrl = req.originalUrl;
|
|
218
232
|
|
|
219
233
|
let endFired = false;
|
|
@@ -163,7 +163,7 @@ function createDashboard()
|
|
|
163
163
|
function setWorker(worker)
|
|
164
164
|
{
|
|
165
165
|
const identity = require('dcp/identity');
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
console.log(' . Starting dashboard');
|
|
168
168
|
setInterval(() => screen && screen.render(), 2000).unref(); /* ensure we didn't forget to render an important update */
|
|
169
169
|
updateWorkerInfo();
|
|
@@ -321,7 +321,12 @@ exports.init = function dashboardConsole$$Init()
|
|
|
321
321
|
const logHistory = new RingBuffer(closeLogDumpSize);
|
|
322
322
|
const inspect = Symbol.for('nodejs.util.inspect.custom');
|
|
323
323
|
|
|
324
|
+
/* createDashoard adds a SIGINT event handler which intereferes with orderly shutdown, so we remove it */
|
|
325
|
+
const sigintHandlers = process.listeners('SIGINT');
|
|
324
326
|
createDashboard();
|
|
327
|
+
for (let sigintHandler of process.listeners('SIGINT'))
|
|
328
|
+
if (sigintHandlers.indexOf(sigintHandler) === -1)
|
|
329
|
+
process.off('SIGINT', sigintHandler);
|
|
325
330
|
|
|
326
331
|
function consoleMethodFactory(logLevel)
|
|
327
332
|
{
|
|
@@ -367,7 +372,8 @@ exports.init = function dashboardConsole$$Init()
|
|
|
367
372
|
}
|
|
368
373
|
|
|
369
374
|
/**
|
|
370
|
-
* Destroy the dashboard,
|
|
375
|
+
* Destroy the dashboard, dump the most recent lines back to stdout/stderr, then replace the console
|
|
376
|
+
* object's methods with methods that will write to stdio to capture shutdown error locally
|
|
371
377
|
*/
|
|
372
378
|
console.close = function dashboardConsole$$close()
|
|
373
379
|
{
|
|
@@ -382,6 +388,7 @@ exports.init = function dashboardConsole$$Init()
|
|
|
382
388
|
const { logLevel, argv } = entry;
|
|
383
389
|
stdioConsole[logLevel](...argv);
|
|
384
390
|
});
|
|
391
|
+
Object.assign(console, stdioConsole);
|
|
385
392
|
}
|
|
386
393
|
|
|
387
394
|
console.setWorker = setWorker;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"description": "Node.js Worker for Distributive Compute Platform",
|
|
5
5
|
"main": "bin/dcp-worker",
|
|
6
6
|
"keywords": [
|
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
"blessed": "0.1.81",
|
|
40
40
|
"blessed-contrib": "4.11.0",
|
|
41
41
|
"chalk": "4.1.2",
|
|
42
|
-
"dcp-client": "^5.7.
|
|
43
|
-
"kvin": "
|
|
42
|
+
"dcp-client": "^5.7.4",
|
|
43
|
+
"kvin": "9.1.0",
|
|
44
|
+
"mux-net": "github:wesgarland/mux-net#e7f81800db4a84ec880026596f7ee68549a6d0f5",
|
|
44
45
|
"posix-getopt": "1.2.1",
|
|
45
46
|
"semver": "7.7.3",
|
|
46
47
|
"socket.io": "4.8.1",
|
|
@@ -53,8 +54,11 @@
|
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@distributive/eslint-config": "2.1.4",
|
|
57
|
+
"@stylistic/eslint-plugin": "3.1.0",
|
|
56
58
|
"@trunkio/launcher": "1.2.7",
|
|
57
59
|
"eslint": "9.39.1",
|
|
60
|
+
"eslint-plugin-jsdoc": "47.0.2",
|
|
61
|
+
"eslint-plugin-unicorn": "49.0.0",
|
|
58
62
|
"peter": "2.4.7"
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
@@ -71,11 +75,12 @@
|
|
|
71
75
|
},
|
|
72
76
|
"overrides": {
|
|
73
77
|
"blessed-contrib": {
|
|
78
|
+
"lodash": "4.18.1",
|
|
74
79
|
"xml2js": "0.6.2"
|
|
75
80
|
},
|
|
76
81
|
"dbus-next": {
|
|
77
82
|
"xml2js": "0.6.2",
|
|
78
|
-
"tar": "7.5.
|
|
83
|
+
"tar": "7.5.22",
|
|
79
84
|
"usocket": "1.0.2"
|
|
80
85
|
}
|
|
81
86
|
}
|
|
@@ -67,7 +67,11 @@ async function main()
|
|
|
67
67
|
strictEqual(confObj.leaveGlobalGroup, true, `leaveGlobalGroup is ${confObj.leaveGlobalGroup}`);
|
|
68
68
|
deepEqual(confObj.cores, { cpu: 3, gpu: 1 }, `Mismatch in cores! cpu: ${confObj.cores.cpu}, gpu: ${confObj.cores.gpu}`);
|
|
69
69
|
deepEqual(confObj.utilization, { cpu: 0.1, gpu: 0.5 }, `Mismatch in utilization! cpu: ${confObj.utilization.cpu}, gpu: ${confObj.utilization.gpu}`);
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
let cgCompare = { joinKey1: { joinKey: 'joinKey1', joinSecret: 'joinHash1' }, joinKey2: { joinKey: 'joinKey2', joinSecret: 'joinHash2' } };
|
|
72
|
+
if (Array.isArray(confObj.computeGroups))
|
|
73
|
+
cgCompare = Object.assign([], cgCompare);
|
|
74
|
+
deepEqual(confObj.computeGroups, cgCompare, 'Mismatch in computeGroups');
|
|
71
75
|
deepEqual(confObj.maxSandboxes, 5, `Mismatch! maxSandboxes:${confObj.maxSandboxes}`);
|
|
72
76
|
deepEqual(confObj.jobIds, ['09fd0909Bd93f7bad915726C3AB4'], `Mismatch in job ids! jobIds:${confObj.jobIds}`);
|
|
73
77
|
|
|
@@ -0,0 +1,103 @@
|
|
|
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);
|