@andersbakken/fisk 5.0.14 → 5.0.17
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/package.json +1 -1
- package/scheduler/fisk-scheduler.js +353 -80
package/package.json
CHANGED
|
@@ -4426,10 +4426,9 @@ class Client extends require$$0__default$2["default"] {
|
|
|
4426
4426
|
this.npmVersion = "";
|
|
4427
4427
|
this.user = "";
|
|
4428
4428
|
this.port = 0;
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
});
|
|
4429
|
+
}
|
|
4430
|
+
notePong() {
|
|
4431
|
+
this.pingSent = undefined;
|
|
4433
4432
|
}
|
|
4434
4433
|
send(type, msg) {
|
|
4435
4434
|
try {
|
|
@@ -4472,8 +4471,12 @@ class Client extends require$$0__default$2["default"] {
|
|
|
4472
4471
|
error(message) {
|
|
4473
4472
|
try {
|
|
4474
4473
|
this.ws.send(`{"error": "${message}"}`);
|
|
4475
|
-
|
|
4474
|
+
// Emit before closing. A real websocket closes asynchronously, but a
|
|
4475
|
+
// DaemonJobSocket does it synchronously and tears the job down -- and
|
|
4476
|
+
// removes these listeners -- so closing first means nobody ever hears
|
|
4477
|
+
// this, including whoever releases the job's counters.
|
|
4476
4478
|
this.emit("error", message);
|
|
4479
|
+
this.ws.close();
|
|
4477
4480
|
}
|
|
4478
4481
|
catch (err) {
|
|
4479
4482
|
/* */
|
|
@@ -4506,6 +4509,115 @@ class Compile extends Client {
|
|
|
4506
4509
|
this.environment = environment;
|
|
4507
4510
|
this.sourcePath = sourcePath;
|
|
4508
4511
|
this.sha1 = sha1;
|
|
4512
|
+
// A job relayed by a daemon cannot stream an environment tarball to us: it is
|
|
4513
|
+
// one multiplexed stream on a connection shared with every other compile on
|
|
4514
|
+
// that host. Such a client reconnects directly to upload instead.
|
|
4515
|
+
this.canUploadEnvironment = true;
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
// One fiskc job on a daemon's shared connection. Everything the scheduler sends
|
|
4520
|
+
// to "its client" is framed onto that connection by relay(), and closing it (a
|
|
4521
|
+
// version mismatch, Client.error()) has to be reported to the daemon rather than
|
|
4522
|
+
// dropping a TCP connection, or the fiskc process would wait for its watchdog.
|
|
4523
|
+
class DaemonJobSocket {
|
|
4524
|
+
constructor(relay, closed) {
|
|
4525
|
+
this.relay = relay;
|
|
4526
|
+
this.closed = closed;
|
|
4527
|
+
this.open = true;
|
|
4528
|
+
}
|
|
4529
|
+
send(data) {
|
|
4530
|
+
if (!this.open) {
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4533
|
+
if (typeof data === "string") {
|
|
4534
|
+
this.relay(data);
|
|
4535
|
+
}
|
|
4536
|
+
else if (data instanceof Buffer) {
|
|
4537
|
+
this.relay(data.toString("utf8"));
|
|
4538
|
+
}
|
|
4539
|
+
else {
|
|
4540
|
+
console.error("Unexpected payload for a daemon job", typeof data);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
close(code, reason) {
|
|
4544
|
+
if (!this.open) {
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
this.open = false;
|
|
4548
|
+
this.closed(String(reason || `closed with ${code === undefined ? 1000 : code}`));
|
|
4549
|
+
}
|
|
4550
|
+
// eslint-disable-next-line class-methods-use-this
|
|
4551
|
+
ping() {
|
|
4552
|
+
// Liveness is a property of the daemon's connection, not of a single job.
|
|
4553
|
+
}
|
|
4554
|
+
terminate() {
|
|
4555
|
+
this.close(1006, "terminated");
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4559
|
+
function decode$3(message) {
|
|
4560
|
+
try {
|
|
4561
|
+
return JSON.parse(message);
|
|
4562
|
+
}
|
|
4563
|
+
catch (err) {
|
|
4564
|
+
return { error: `Unserializable scheduler message: ${message}` };
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
class DaemonConnection extends Client {
|
|
4568
|
+
constructor(ws, ip, option) {
|
|
4569
|
+
super(5 /* ClientType.Daemon */, ws, ip, option);
|
|
4570
|
+
this.jobs = new Map();
|
|
4571
|
+
}
|
|
4572
|
+
get activeJobs() {
|
|
4573
|
+
return this.jobs.size;
|
|
4574
|
+
}
|
|
4575
|
+
createJob(request) {
|
|
4576
|
+
var _a;
|
|
4577
|
+
if (this.jobs.has(request.id)) {
|
|
4578
|
+
return undefined;
|
|
4579
|
+
}
|
|
4580
|
+
const socket = new DaemonJobSocket((message) => {
|
|
4581
|
+
this.send({ type: "jobMessage", id: request.id, message: decode$3(message) });
|
|
4582
|
+
}, (reason) => {
|
|
4583
|
+
this.finishJob(request.id, reason, true);
|
|
4584
|
+
});
|
|
4585
|
+
const compile = new Compile(socket, this.ip, request.environment, request.sourcePath, request.sha1, this.option);
|
|
4586
|
+
compile.canUploadEnvironment = false;
|
|
4587
|
+
compile.npmVersion = request.npmVersion || "";
|
|
4588
|
+
compile.name = request.name || this.name;
|
|
4589
|
+
compile.hostname = request.hostname || this.hostname;
|
|
4590
|
+
compile.user = request.user || "";
|
|
4591
|
+
if (request.builder) {
|
|
4592
|
+
compile.builder = request.builder;
|
|
4593
|
+
}
|
|
4594
|
+
if ((_a = request.labels) === null || _a === void 0 ? void 0 : _a.length) {
|
|
4595
|
+
compile.labels = request.labels;
|
|
4596
|
+
}
|
|
4597
|
+
this.jobs.set(request.id, compile);
|
|
4598
|
+
return compile;
|
|
4599
|
+
}
|
|
4600
|
+
// The end of a job is a message now, not a socket close, so every exit has to
|
|
4601
|
+
// funnel through here: the daemon saying the fiskc process is gone, the
|
|
4602
|
+
// scheduler closing the job itself, or this whole connection dropping. Missing
|
|
4603
|
+
// one leaks a builder's activeClients slot and the scheduler's activeJobs
|
|
4604
|
+
// count for as long as the scheduler runs.
|
|
4605
|
+
finishJob(id, reason, notifyDaemon) {
|
|
4606
|
+
const compile = this.jobs.get(id);
|
|
4607
|
+
if (!compile) {
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4610
|
+
this.jobs.delete(id);
|
|
4611
|
+
if (notifyDaemon) {
|
|
4612
|
+
this.send({ type: "jobClosed", id, reason });
|
|
4613
|
+
}
|
|
4614
|
+
compile.emit("close", { code: 1000, reason });
|
|
4615
|
+
compile.removeAllListeners();
|
|
4616
|
+
}
|
|
4617
|
+
finishAllJobs(reason) {
|
|
4618
|
+
for (const id of Array.from(this.jobs.keys())) {
|
|
4619
|
+
this.finishJob(id, reason, false);
|
|
4620
|
+
}
|
|
4509
4621
|
}
|
|
4510
4622
|
}
|
|
4511
4623
|
|
|
@@ -56206,17 +56318,20 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56206
56318
|
}
|
|
56207
56319
|
this.emit("compile", client);
|
|
56208
56320
|
const remaining = {};
|
|
56209
|
-
|
|
56210
|
-
|
|
56321
|
+
ws.on("pong", () => {
|
|
56322
|
+
client.notePong();
|
|
56323
|
+
});
|
|
56324
|
+
ws.on("error", (err) => client.emit("error", err));
|
|
56325
|
+
ws.on("close", (code, reason) => {
|
|
56211
56326
|
if (remaining.bytes) {
|
|
56212
56327
|
client.emit("error", "Got close while reading a binary message");
|
|
56213
56328
|
}
|
|
56214
56329
|
if (client) {
|
|
56215
56330
|
client.emit("close", { code: code, reason: reason });
|
|
56216
56331
|
}
|
|
56217
|
-
|
|
56332
|
+
ws.removeAllListeners();
|
|
56218
56333
|
});
|
|
56219
|
-
|
|
56334
|
+
ws.on("message", (msg) => {
|
|
56220
56335
|
switch (typeof msg) {
|
|
56221
56336
|
case "string": {
|
|
56222
56337
|
if (remaining.bytes) {
|
|
@@ -56284,14 +56399,18 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56284
56399
|
}
|
|
56285
56400
|
});
|
|
56286
56401
|
}
|
|
56287
|
-
_handleBuilder(req,
|
|
56288
|
-
client
|
|
56402
|
+
_handleBuilder(req, ws, ip) {
|
|
56403
|
+
const client = new Builder(ws, ip);
|
|
56404
|
+
ws.on("pong", () => {
|
|
56405
|
+
client.notePong();
|
|
56406
|
+
});
|
|
56407
|
+
ws.on("close", (code, reason) => {
|
|
56289
56408
|
client.emit("close", { code: code, reason: reason });
|
|
56290
|
-
|
|
56409
|
+
ws.removeAllListeners();
|
|
56291
56410
|
});
|
|
56292
|
-
|
|
56411
|
+
ws.on("error", () => {
|
|
56293
56412
|
client.emit("close", { code: 1005, reason: "unknown" });
|
|
56294
|
-
|
|
56413
|
+
ws.removeAllListeners();
|
|
56295
56414
|
});
|
|
56296
56415
|
if (!("x-fisk-port" in req.headers)) {
|
|
56297
56416
|
client.error("No x-fisk-port header");
|
|
@@ -56328,7 +56447,7 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56328
56447
|
client.environments[env] = true;
|
|
56329
56448
|
}
|
|
56330
56449
|
});
|
|
56331
|
-
|
|
56450
|
+
ws.on("message", (msg) => {
|
|
56332
56451
|
// console.log("Got message from builder", typeof msg, msg.length);
|
|
56333
56452
|
switch (typeof msg) {
|
|
56334
56453
|
case "string": {
|
|
@@ -56364,28 +56483,128 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56364
56483
|
// console.log("Got dude", client);
|
|
56365
56484
|
this.emit("builder", client);
|
|
56366
56485
|
}
|
|
56367
|
-
_handleMonitor(req,
|
|
56486
|
+
_handleMonitor(req, ws, ip) {
|
|
56487
|
+
const client = new Client(3 /* ClientType.Monitor */, ws, ip);
|
|
56368
56488
|
client.nonce = this.nonces.get(req);
|
|
56369
56489
|
// console.log("Got nonce", req.nonce);
|
|
56370
|
-
|
|
56490
|
+
ws.on("pong", () => {
|
|
56491
|
+
client.notePong();
|
|
56492
|
+
});
|
|
56493
|
+
ws.on("message", (message) => client.emit("message", message));
|
|
56371
56494
|
this.emit("monitor", client);
|
|
56372
|
-
|
|
56373
|
-
|
|
56495
|
+
ws.on("close", (code, reason) => {
|
|
56496
|
+
ws.removeAllListeners();
|
|
56374
56497
|
client.emit("close", { code: code, reason: reason });
|
|
56375
56498
|
});
|
|
56376
|
-
|
|
56499
|
+
ws.on("error", (err) => client.emit("error", err));
|
|
56377
56500
|
}
|
|
56378
|
-
_handleClientVerify(req,
|
|
56501
|
+
_handleClientVerify(req, ws, ip) {
|
|
56502
|
+
const client = new Client(4 /* ClientType.ClientVerify */, ws, ip);
|
|
56379
56503
|
Object.assign(client, { npmVersion: header(req, "x-fisk-npm-version") });
|
|
56380
56504
|
this.emit("clientVerify", client);
|
|
56381
|
-
|
|
56382
|
-
client.
|
|
56505
|
+
ws.on("pong", () => {
|
|
56506
|
+
client.notePong();
|
|
56507
|
+
});
|
|
56508
|
+
ws.on("close", (code, reason) => {
|
|
56509
|
+
ws.removeAllListeners();
|
|
56383
56510
|
client.emit("close", { code: code, reason: reason });
|
|
56384
56511
|
});
|
|
56385
|
-
|
|
56512
|
+
ws.on("error", (err) => client.emit("error", err));
|
|
56513
|
+
}
|
|
56514
|
+
// One connection per host, shared by every fiskc process on it. fiskc used to
|
|
56515
|
+
// open its own websocket per translation unit, which is a TCP connect plus an
|
|
56516
|
+
// HTTP upgrade per compile; when this scheduler's event loop stalls the listen
|
|
56517
|
+
// backlog fills, SYNs get dropped and those clients time out and compile
|
|
56518
|
+
// locally instead. Jobs are multiplexed here as messages tagged with the
|
|
56519
|
+
// daemon's request id, and each one gets a Compile that behaves like any other
|
|
56520
|
+
// client so the scheduling and accounting code below stays untouched.
|
|
56521
|
+
_handleDaemon(req, ws, ip) {
|
|
56522
|
+
const configVersion = parseInt(header(req, "x-fisk-config-version") || "");
|
|
56523
|
+
if (configVersion !== this.configVersion) {
|
|
56524
|
+
ws.send(`{"error": "Bad config version, expected ${this.configVersion}, got ${configVersion}"}`);
|
|
56525
|
+
ws.close();
|
|
56526
|
+
return;
|
|
56527
|
+
}
|
|
56528
|
+
const connection = new DaemonConnection(ws, ip, this.option);
|
|
56529
|
+
connection.name = header(req, "x-fisk-daemon-name") || "";
|
|
56530
|
+
connection.hostname = header(req, "x-fisk-daemon-hostname") || "";
|
|
56531
|
+
connection.npmVersion = header(req, "x-fisk-npm-version") || "";
|
|
56532
|
+
ws.on("pong", () => {
|
|
56533
|
+
connection.notePong();
|
|
56534
|
+
});
|
|
56535
|
+
ws.on("close", (code, reason) => {
|
|
56536
|
+
connection.finishAllJobs(`daemon ${ip} disconnected: ${code} ${String(reason)}`);
|
|
56537
|
+
ws.removeAllListeners();
|
|
56538
|
+
connection.emit("close", { code: code, reason: reason });
|
|
56539
|
+
});
|
|
56540
|
+
ws.on("error", (err) => {
|
|
56541
|
+
connection.finishAllJobs(`daemon ${ip} error: ${err.message}`);
|
|
56542
|
+
connection.emit("error", err);
|
|
56543
|
+
});
|
|
56544
|
+
ws.on("message", (msg) => {
|
|
56545
|
+
if (typeof msg !== "string") {
|
|
56546
|
+
console.error("Unexpected binary message from daemon", ip);
|
|
56547
|
+
return;
|
|
56548
|
+
}
|
|
56549
|
+
let json;
|
|
56550
|
+
try {
|
|
56551
|
+
json = JSON.parse(msg);
|
|
56552
|
+
}
|
|
56553
|
+
catch (err) {
|
|
56554
|
+
console.error(`Unable to parse message from daemon ${ip} as JSON`, err);
|
|
56555
|
+
return;
|
|
56556
|
+
}
|
|
56557
|
+
if (!json || typeof json.id !== "number") {
|
|
56558
|
+
console.error("Message from daemon without a job id", ip, json);
|
|
56559
|
+
return;
|
|
56560
|
+
}
|
|
56561
|
+
const id = json.id;
|
|
56562
|
+
switch (json.type) {
|
|
56563
|
+
case "compileRequest": {
|
|
56564
|
+
if (typeof json.environment !== "string" || !json.environment) {
|
|
56565
|
+
connection.send({ type: "jobMessage", id, message: { error: "No environment" } });
|
|
56566
|
+
return;
|
|
56567
|
+
}
|
|
56568
|
+
if (typeof json.sourceFile !== "string" || !json.sourceFile) {
|
|
56569
|
+
connection.send({ type: "jobMessage", id, message: { error: "No sourceFile" } });
|
|
56570
|
+
return;
|
|
56571
|
+
}
|
|
56572
|
+
const sha1 = typeof json.sha1 === "string" ? json.sha1 : undefined;
|
|
56573
|
+
if (sha1 && sha1.length !== 40) {
|
|
56574
|
+
connection.send({ type: "jobMessage", id, message: { error: `Bad sha1 sum: ${sha1}` } });
|
|
56575
|
+
return;
|
|
56576
|
+
}
|
|
56577
|
+
const labels = typeof json.labels === "string" ? json.labels.split(/ +/).filter((x) => x) : undefined;
|
|
56578
|
+
const compile = connection.createJob({
|
|
56579
|
+
id,
|
|
56580
|
+
environment: json.environment,
|
|
56581
|
+
sourcePath: json.sourceFile,
|
|
56582
|
+
sha1,
|
|
56583
|
+
name: typeof json.name === "string" ? json.name : undefined,
|
|
56584
|
+
user: typeof json.user === "string" ? json.user : undefined,
|
|
56585
|
+
hostname: typeof json.hostname === "string" ? json.hostname : undefined,
|
|
56586
|
+
builder: typeof json.builder === "string" ? json.builder : undefined,
|
|
56587
|
+
labels,
|
|
56588
|
+
npmVersion: typeof json.npmVersion === "string" ? json.npmVersion : undefined
|
|
56589
|
+
});
|
|
56590
|
+
if (!compile) {
|
|
56591
|
+
connection.send({ type: "jobMessage", id, message: { error: `Duplicate job id ${id}` } });
|
|
56592
|
+
return;
|
|
56593
|
+
}
|
|
56594
|
+
this.emit("compile", compile);
|
|
56595
|
+
break;
|
|
56596
|
+
}
|
|
56597
|
+
case "compileDone":
|
|
56598
|
+
connection.finishJob(id, typeof json.reason === "string" ? json.reason : "done", false);
|
|
56599
|
+
break;
|
|
56600
|
+
default:
|
|
56601
|
+
console.error("Unexpected message type from daemon", ip, json.type);
|
|
56602
|
+
break;
|
|
56603
|
+
}
|
|
56604
|
+
});
|
|
56605
|
+
this.emit("daemon", connection);
|
|
56386
56606
|
}
|
|
56387
56607
|
_handleConnection(ws, req) {
|
|
56388
|
-
let client = undefined;
|
|
56389
56608
|
let ip = req.connection.remoteAddress;
|
|
56390
56609
|
// console.log("_handleConnection", ip);
|
|
56391
56610
|
if (!ip) {
|
|
@@ -56402,16 +56621,16 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56402
56621
|
this._handleCompile(req, ws, ip);
|
|
56403
56622
|
break;
|
|
56404
56623
|
case "/builder":
|
|
56405
|
-
|
|
56406
|
-
this._handleBuilder(req, client);
|
|
56624
|
+
this._handleBuilder(req, ws, ip);
|
|
56407
56625
|
break;
|
|
56408
56626
|
case "/monitor":
|
|
56409
|
-
|
|
56410
|
-
this._handleMonitor(req, client);
|
|
56627
|
+
this._handleMonitor(req, ws, ip);
|
|
56411
56628
|
break;
|
|
56412
56629
|
case "/client_verify":
|
|
56413
|
-
|
|
56414
|
-
|
|
56630
|
+
this._handleClientVerify(req, ws, ip);
|
|
56631
|
+
break;
|
|
56632
|
+
case "/daemon":
|
|
56633
|
+
this._handleDaemon(req, ws, ip);
|
|
56415
56634
|
break;
|
|
56416
56635
|
default:
|
|
56417
56636
|
console.error(`Invalid pathname ${url.pathname} from: ${ip}`);
|
|
@@ -58095,9 +58314,9 @@ catch (err) {
|
|
|
58095
58314
|
console.log("Couldn't parse package json", err);
|
|
58096
58315
|
process.exit();
|
|
58097
58316
|
}
|
|
58098
|
-
const builders =
|
|
58317
|
+
const builders = new Set();
|
|
58318
|
+
const daemons = new Set();
|
|
58099
58319
|
const monitors = [];
|
|
58100
|
-
let builderCount = 0;
|
|
58101
58320
|
let activeJobs = 0;
|
|
58102
58321
|
let capacity = 0;
|
|
58103
58322
|
let jobsFailed = 0;
|
|
@@ -58245,12 +58464,6 @@ function jobFinished(builder, job) {
|
|
|
58245
58464
|
});
|
|
58246
58465
|
}
|
|
58247
58466
|
}
|
|
58248
|
-
function builderKey(ip, port) {
|
|
58249
|
-
if (typeof ip === "object") {
|
|
58250
|
-
return ip.ip + " " + ip.port;
|
|
58251
|
-
}
|
|
58252
|
-
return ip + " " + port;
|
|
58253
|
-
}
|
|
58254
58467
|
function builderToMonitorInfo(builder, type) {
|
|
58255
58468
|
return {
|
|
58256
58469
|
type: type,
|
|
@@ -58270,8 +58483,7 @@ function builderToMonitorInfo(builder, type) {
|
|
|
58270
58483
|
};
|
|
58271
58484
|
}
|
|
58272
58485
|
function insertBuilder(builder) {
|
|
58273
|
-
builders
|
|
58274
|
-
++builderCount;
|
|
58486
|
+
builders.add(builder);
|
|
58275
58487
|
assert__default["default"](typeof builder.slots === "number");
|
|
58276
58488
|
capacity += builder.slots;
|
|
58277
58489
|
if (monitors.length) {
|
|
@@ -58285,8 +58497,8 @@ function insertBuilder(builder) {
|
|
|
58285
58497
|
}
|
|
58286
58498
|
}
|
|
58287
58499
|
function forEachBuilder(cb) {
|
|
58288
|
-
for (const
|
|
58289
|
-
cb(
|
|
58500
|
+
for (const builder of builders) {
|
|
58501
|
+
cb(builder);
|
|
58290
58502
|
}
|
|
58291
58503
|
}
|
|
58292
58504
|
function onObjectCacheCleared() {
|
|
@@ -58319,10 +58531,9 @@ if (option("object-cache")) {
|
|
|
58319
58531
|
setObjectCacheEnabled(true);
|
|
58320
58532
|
}
|
|
58321
58533
|
function removeBuilder(builder) {
|
|
58322
|
-
--builderCount;
|
|
58323
58534
|
assert__default["default"](typeof builder.slots === "number");
|
|
58324
58535
|
capacity -= builder.slots;
|
|
58325
|
-
delete
|
|
58536
|
+
builders.delete(builder);
|
|
58326
58537
|
if (monitors.length) {
|
|
58327
58538
|
const info = builderToMonitorInfo(builder, "builderRemoved");
|
|
58328
58539
|
if (monitorsLog) {
|
|
@@ -58468,27 +58679,44 @@ server.on("listen", (app) => {
|
|
|
58468
58679
|
app.get("/builders", (req, res) => {
|
|
58469
58680
|
const ret = [];
|
|
58470
58681
|
const now = Date.now();
|
|
58471
|
-
for (const
|
|
58472
|
-
|
|
58682
|
+
for (const builder of builders) {
|
|
58683
|
+
ret.push({
|
|
58684
|
+
ip: builder.ip,
|
|
58685
|
+
name: builder.name,
|
|
58686
|
+
labels: builder.labels,
|
|
58687
|
+
slots: builder.slots,
|
|
58688
|
+
port: builder.port,
|
|
58689
|
+
activeClients: builder.activeClients,
|
|
58690
|
+
jobsScheduled: builder.jobsScheduled,
|
|
58691
|
+
lastJob: builder.lastJob ? new Date(builder.lastJob).toString() : "",
|
|
58692
|
+
jobsPerformed: builder.jobsPerformed,
|
|
58693
|
+
compileSpeed: builder.jobsPerformed / builder.totalCompileSpeed || 0,
|
|
58694
|
+
uploadSpeed: builder.jobsPerformed / builder.totalUploadSpeed || 0,
|
|
58695
|
+
hostname: builder.hostname,
|
|
58696
|
+
system: builder.system,
|
|
58697
|
+
created: builder.created,
|
|
58698
|
+
load: builder.load,
|
|
58699
|
+
uptime: now - builder.created.valueOf(),
|
|
58700
|
+
npmVersion: builder.npmVersion,
|
|
58701
|
+
environments: Object.keys(builder.environments)
|
|
58702
|
+
});
|
|
58703
|
+
}
|
|
58704
|
+
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
58705
|
+
res.send(JSON.stringify(ret, null, pretty) + "\n");
|
|
58706
|
+
});
|
|
58707
|
+
app.get("/daemons", (req, res) => {
|
|
58708
|
+
const ret = [];
|
|
58709
|
+
const now = Date.now();
|
|
58710
|
+
for (const daemon of daemons) {
|
|
58473
58711
|
ret.push({
|
|
58474
|
-
ip:
|
|
58475
|
-
name:
|
|
58476
|
-
labels:
|
|
58477
|
-
|
|
58478
|
-
|
|
58479
|
-
|
|
58480
|
-
|
|
58481
|
-
|
|
58482
|
-
jobsPerformed: s.jobsPerformed,
|
|
58483
|
-
compileSpeed: s.jobsPerformed / s.totalCompileSpeed || 0,
|
|
58484
|
-
uploadSpeed: s.jobsPerformed / s.totalUploadSpeed || 0,
|
|
58485
|
-
hostname: s.hostname,
|
|
58486
|
-
system: s.system,
|
|
58487
|
-
created: s.created,
|
|
58488
|
-
load: s.load,
|
|
58489
|
-
uptime: now - s.created.valueOf(),
|
|
58490
|
-
npmVersion: s.npmVersion,
|
|
58491
|
-
environments: Object.keys(s.environments)
|
|
58712
|
+
ip: daemon.ip,
|
|
58713
|
+
name: daemon.name,
|
|
58714
|
+
labels: daemon.labels,
|
|
58715
|
+
port: daemon.port,
|
|
58716
|
+
hostname: daemon.hostname,
|
|
58717
|
+
created: daemon.created,
|
|
58718
|
+
uptime: now - daemon.created.valueOf(),
|
|
58719
|
+
npmVersion: daemon.npmVersion
|
|
58492
58720
|
});
|
|
58493
58721
|
}
|
|
58494
58722
|
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
@@ -58501,17 +58729,18 @@ server.on("listen", (app) => {
|
|
|
58501
58729
|
return { count: count, percentage: (count ? (count * 100) / jobs : 0).toFixed(1) + "%" };
|
|
58502
58730
|
}
|
|
58503
58731
|
const obj = {
|
|
58504
|
-
builderCount:
|
|
58732
|
+
builderCount: builders.size,
|
|
58733
|
+
daemonCount: daemons.size,
|
|
58505
58734
|
npmVersion: schedulerNpmVersion,
|
|
58506
58735
|
environments: environmentsInfo(),
|
|
58507
58736
|
configVersion: common.Version,
|
|
58508
|
-
capacity
|
|
58509
|
-
activeJobs
|
|
58737
|
+
capacity,
|
|
58738
|
+
activeJobs,
|
|
58510
58739
|
peaks: peakData(),
|
|
58511
58740
|
jobsFailed: percentage(jobsFailed),
|
|
58512
|
-
jobsStarted
|
|
58513
|
-
jobs
|
|
58514
|
-
jobsScheduled
|
|
58741
|
+
jobsStarted,
|
|
58742
|
+
jobs,
|
|
58743
|
+
jobsScheduled,
|
|
58515
58744
|
jobsFinished: percentage(jobsFinished),
|
|
58516
58745
|
cacheHits: percentage(objectCache ? objectCache.hits : 0),
|
|
58517
58746
|
uptimeMS: now - serverStartTime,
|
|
@@ -58560,9 +58789,9 @@ server.on("listen", (app) => {
|
|
|
58560
58789
|
code: req.query.code || 0,
|
|
58561
58790
|
purgeEnvironments: "purge_environments" in req.query
|
|
58562
58791
|
};
|
|
58563
|
-
console.log("Sending quit message to builders", msg,
|
|
58564
|
-
for (const
|
|
58565
|
-
|
|
58792
|
+
console.log("Sending quit message to builders", msg, builders.size);
|
|
58793
|
+
for (const builder of builders) {
|
|
58794
|
+
builder.send(msg);
|
|
58566
58795
|
}
|
|
58567
58796
|
});
|
|
58568
58797
|
app.get("/environment/*", (req, res) => {
|
|
@@ -58606,8 +58835,7 @@ server.on("listen", (app) => {
|
|
|
58606
58835
|
return;
|
|
58607
58836
|
}
|
|
58608
58837
|
let found;
|
|
58609
|
-
for (const
|
|
58610
|
-
const builder = builders[key];
|
|
58838
|
+
for (const builder of builders) {
|
|
58611
58839
|
console.log(builder.ip, builder.name, builder.hostname, req.body.builder);
|
|
58612
58840
|
if (builder.ip === req.body.builder ||
|
|
58613
58841
|
builder.name === req.body.builder ||
|
|
@@ -58742,7 +58970,7 @@ server.on("builder", (builder) => {
|
|
|
58742
58970
|
}
|
|
58743
58971
|
builder.activeClients = 0;
|
|
58744
58972
|
insertBuilder(builder);
|
|
58745
|
-
console.log("builder connected", builder.npmVersion, builder.ip, builder.name || "", builder.hostname || "", Object.keys(builder.environments), "builderCount is",
|
|
58973
|
+
console.log("builder connected", builder.npmVersion, builder.ip, builder.name || "", builder.hostname || "", Object.keys(builder.environments), "builderCount is", builders.size);
|
|
58746
58974
|
syncEnvironments(builder);
|
|
58747
58975
|
builder.on("environments", (message) => {
|
|
58748
58976
|
builder.environments = {};
|
|
@@ -58775,7 +59003,7 @@ server.on("builder", (builder) => {
|
|
|
58775
59003
|
if (objectCache) {
|
|
58776
59004
|
objectCache.removeNode(builder);
|
|
58777
59005
|
}
|
|
58778
|
-
console.log(`builder disconnected ${builder.ip}:${builder.port} ${builder.name} ${builder.hostname} builderCount is ${
|
|
59006
|
+
console.log(`builder disconnected ${builder.ip}:${builder.port} ${builder.name} ${builder.hostname} builderCount is ${builders.size}`);
|
|
58779
59007
|
builder.removeAllListeners();
|
|
58780
59008
|
});
|
|
58781
59009
|
builder.on("load", (message) => {
|
|
@@ -58810,6 +59038,20 @@ function requestEnvironment(compile) {
|
|
|
58810
59038
|
if (compile.environment in pendingEnvironments) {
|
|
58811
59039
|
return false;
|
|
58812
59040
|
}
|
|
59041
|
+
if (!compile.canUploadEnvironment) {
|
|
59042
|
+
// Tell it what we are missing but leave the pending slot open: this client
|
|
59043
|
+
// is going to reconnect with a websocket of its own to do the upload, and
|
|
59044
|
+
// that connection is the one that has to claim the slot and get the
|
|
59045
|
+
// listeners below.
|
|
59046
|
+
console.log(`Asking ${compile.name} ${compile.ip} to upload ${compile.environment} over a direct connection`);
|
|
59047
|
+
compile.send({ type: "needsEnvironment" });
|
|
59048
|
+
// Nothing further will ever happen on this job -- the upload arrives on a
|
|
59049
|
+
// new connection -- so release it now rather than holding it for the whole
|
|
59050
|
+
// local compile fiskc does in the meantime. The direct branch below must
|
|
59051
|
+
// NOT do this: that client uploads over this very socket.
|
|
59052
|
+
compile.close();
|
|
59053
|
+
return true;
|
|
59054
|
+
}
|
|
58813
59055
|
pendingEnvironments[compile.environment] = true;
|
|
58814
59056
|
console.log(`Asking ${compile.name} ${compile.ip} to upload ${compile.environment}`);
|
|
58815
59057
|
compile.send({ type: "needsEnvironment" });
|
|
@@ -58893,13 +59135,35 @@ server.on("clientVerify", (clientVerify) => {
|
|
|
58893
59135
|
clientVerify.send("version_verified", { minimum_version: `${clientMinimumVersion}` });
|
|
58894
59136
|
}
|
|
58895
59137
|
});
|
|
59138
|
+
// Also gives the connection an "error" listener: EventEmitter throws on an
|
|
59139
|
+
// unhandled "error" event, and this one would take the scheduler down with it.
|
|
59140
|
+
server.on("daemon", (daemon) => {
|
|
59141
|
+
console.log(`daemon connected ${daemon.ip} ${daemon.name} ${daemon.hostname} version ${daemon.npmVersion}`);
|
|
59142
|
+
daemon.on("error", (err) => {
|
|
59143
|
+
console.error(`daemon error ${daemon.ip}: ${typeof err === "string" ? err : err.message}`);
|
|
59144
|
+
// ### do I get a close if I get an error?
|
|
59145
|
+
daemons.delete(daemon);
|
|
59146
|
+
});
|
|
59147
|
+
daemon.on("close", () => {
|
|
59148
|
+
console.log(`daemon disconnected ${daemon.ip} ${daemon.name}`);
|
|
59149
|
+
daemon.removeAllListeners();
|
|
59150
|
+
daemons.delete(daemon);
|
|
59151
|
+
});
|
|
59152
|
+
daemons.add(daemon);
|
|
59153
|
+
});
|
|
58896
59154
|
server.on("compile", (compile) => {
|
|
58897
59155
|
compile.on("log", (event) => {
|
|
58898
59156
|
addLogFile({ source: "client", ip: compile.ip, contents: event.message });
|
|
58899
59157
|
});
|
|
59158
|
+
// Every dead end below closes the client. On a real websocket fiskc did that
|
|
59159
|
+
// for us by disconnecting; a job relayed by a daemon has no socket to drop, so
|
|
59160
|
+
// without this it sits in DaemonConnection.jobs holding a Compile until the
|
|
59161
|
+
// daemon notices fiskc exit -- which for these paths means all the way through
|
|
59162
|
+
// the local compile fiskc falls back to.
|
|
58900
59163
|
if (clientTooOld(compile.npmVersion)) {
|
|
58901
59164
|
++jobsFailed;
|
|
58902
59165
|
compile.send("version_mismatch", { minimum_version: `${clientMinimumVersion}` });
|
|
59166
|
+
compile.close();
|
|
58903
59167
|
return;
|
|
58904
59168
|
}
|
|
58905
59169
|
// console.log("request", compile.hostname, compile.ip, compile.environment);
|
|
@@ -58912,6 +59176,7 @@ server.on("compile", (compile) => {
|
|
|
58912
59176
|
if (!usableEnvs.length) {
|
|
58913
59177
|
console.log(`We're already waiting for ${compile.environment} and we don't have any compatible ones`);
|
|
58914
59178
|
compile.send("builder", {});
|
|
59179
|
+
compile.close();
|
|
58915
59180
|
++jobsFailed;
|
|
58916
59181
|
return;
|
|
58917
59182
|
}
|
|
@@ -58994,17 +59259,20 @@ server.on("compile", (compile) => {
|
|
|
58994
59259
|
++jobsFailed;
|
|
58995
59260
|
console.log(`Specific builder "${compile.builder}" was requested and we couldn't find a builder with that ${compile.environment}`);
|
|
58996
59261
|
compile.send("builder", {});
|
|
59262
|
+
compile.close();
|
|
58997
59263
|
return;
|
|
58998
59264
|
}
|
|
58999
59265
|
if (compile.labels) {
|
|
59000
59266
|
++jobsFailed;
|
|
59001
59267
|
console.log(`Specific labels "${compile.labels}" were specified we couldn't match ${compile.environment} with any builder with those labels`);
|
|
59002
59268
|
compile.send("builder", {});
|
|
59269
|
+
compile.close();
|
|
59003
59270
|
return;
|
|
59004
59271
|
}
|
|
59005
59272
|
++jobsFailed;
|
|
59006
59273
|
console.log("No builder for you", compile.ip);
|
|
59007
59274
|
compile.send("builder", {});
|
|
59275
|
+
compile.close();
|
|
59008
59276
|
return;
|
|
59009
59277
|
}
|
|
59010
59278
|
const data = {};
|
|
@@ -59437,10 +59705,15 @@ Environments.instance
|
|
|
59437
59705
|
.then(() => {
|
|
59438
59706
|
setInterval(() => {
|
|
59439
59707
|
// console.log("sending pings");
|
|
59440
|
-
for (const
|
|
59441
|
-
const builder = builders[key];
|
|
59708
|
+
for (const builder of builders) {
|
|
59442
59709
|
builder.ping();
|
|
59443
59710
|
}
|
|
59711
|
+
// A daemon holds every job on its host, so a silently dead one pins
|
|
59712
|
+
// activeJobs and activeClients until the kernel gives up on the TCP
|
|
59713
|
+
// connection. Pinging is what makes Client.ping close it instead.
|
|
59714
|
+
for (const daemon of daemons) {
|
|
59715
|
+
daemon.ping();
|
|
59716
|
+
}
|
|
59444
59717
|
}, option.int("ping-interval", 20000));
|
|
59445
59718
|
})
|
|
59446
59719
|
.catch((e) => {
|