@andersbakken/fisk 5.0.17 → 5.0.19
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/builder/fisk-builder.js +363 -218
- package/daemon/fisk-daemon.js +5014 -197
- package/package.json +3 -1
- package/scheduler/fisk-scheduler.js +153 -123
package/builder/fisk-builder.js
CHANGED
|
@@ -47,6 +47,36 @@ var require$$8__default = /*#__PURE__*/_interopDefaultLegacy(require$$8);
|
|
|
47
47
|
var child_process__default = /*#__PURE__*/_interopDefaultLegacy(child_process);
|
|
48
48
|
var require$$1__default$4 = /*#__PURE__*/_interopDefaultLegacy(require$$1$5);
|
|
49
49
|
|
|
50
|
+
/******************************************************************************
|
|
51
|
+
Copyright (c) Microsoft Corporation.
|
|
52
|
+
|
|
53
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
54
|
+
purpose with or without fee is hereby granted.
|
|
55
|
+
|
|
56
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
57
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
58
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
59
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
60
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
61
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
62
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
63
|
+
***************************************************************************** */
|
|
64
|
+
|
|
65
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
66
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
67
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
68
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
69
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
70
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
71
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
76
|
+
var e = new Error(message);
|
|
77
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
78
|
+
};
|
|
79
|
+
|
|
50
80
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
51
81
|
|
|
52
82
|
function getDefaultExportFromCjs (x) {
|
|
@@ -8211,6 +8241,97 @@ class Job extends EventEmitter__default["default"] {
|
|
|
8211
8241
|
}
|
|
8212
8242
|
}
|
|
8213
8243
|
|
|
8244
|
+
const Version = 5;
|
|
8245
|
+
// 6: objects now carry the client's real source path and compilation dir,
|
|
8246
|
+
// baked in at compile time instead of the builder's /compiles paths, and the
|
|
8247
|
+
// stored response no longer keeps the paths the client used to patch with.
|
|
8248
|
+
const ObjectCacheFormatVersion = 6;
|
|
8249
|
+
function cacheDir(option) {
|
|
8250
|
+
let dir = option("cache-dir");
|
|
8251
|
+
if (!dir) {
|
|
8252
|
+
dir = path__default["default"].join(os__default["default"].homedir(), ".cache", "fisk", path__default["default"].basename(option.prefix || ""));
|
|
8253
|
+
}
|
|
8254
|
+
return dir;
|
|
8255
|
+
}
|
|
8256
|
+
function validateCache(option) {
|
|
8257
|
+
const dir = cacheDir(option);
|
|
8258
|
+
const file = path__default["default"].join(dir, "version");
|
|
8259
|
+
// console.log(dir);
|
|
8260
|
+
let version;
|
|
8261
|
+
try {
|
|
8262
|
+
version = fs$3.readFileSync(file);
|
|
8263
|
+
if (version.readUInt32BE() === Version) {
|
|
8264
|
+
return;
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
catch (err) {
|
|
8268
|
+
/* */
|
|
8269
|
+
}
|
|
8270
|
+
if (version) {
|
|
8271
|
+
console.log(`Wrong version. Destroying cache ${dir}`);
|
|
8272
|
+
}
|
|
8273
|
+
fs$3.removeSync(dir);
|
|
8274
|
+
fs$3.mkdirpSync(dir);
|
|
8275
|
+
const buf = Buffer.allocUnsafe(4);
|
|
8276
|
+
buf.writeUInt32BE(Version);
|
|
8277
|
+
fs$3.writeFileSync(file, buf);
|
|
8278
|
+
}
|
|
8279
|
+
function validateObjectCache(option) {
|
|
8280
|
+
const dir = cacheDir(option);
|
|
8281
|
+
const objectCacheDir = option.string("object-cache-dir") || path__default["default"].join(dir, "objectcache");
|
|
8282
|
+
const file = path__default["default"].join(objectCacheDir, "version");
|
|
8283
|
+
let version;
|
|
8284
|
+
try {
|
|
8285
|
+
version = fs$3.readFileSync(file);
|
|
8286
|
+
if (version.readUInt32BE() === ObjectCacheFormatVersion) {
|
|
8287
|
+
return;
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
catch (err) {
|
|
8291
|
+
/* */
|
|
8292
|
+
}
|
|
8293
|
+
if (version) {
|
|
8294
|
+
console.log(`Wrong object cache version. Destroying object cache ${objectCacheDir}`);
|
|
8295
|
+
}
|
|
8296
|
+
fs$3.removeSync(objectCacheDir);
|
|
8297
|
+
fs$3.mkdirpSync(objectCacheDir);
|
|
8298
|
+
const buf = Buffer.allocUnsafe(4);
|
|
8299
|
+
buf.writeUInt32BE(ObjectCacheFormatVersion);
|
|
8300
|
+
fs$3.writeFileSync(file, buf);
|
|
8301
|
+
}
|
|
8302
|
+
// The listen backlog is the depth of the kernel's accept queue. When it fills
|
|
8303
|
+
// -- which is what happens whenever the event loop stalls long enough to stop
|
|
8304
|
+
// calling accept() -- Linux does not refuse the connection, it silently drops
|
|
8305
|
+
// the SYN, and the client's first retransmit is a second later. A client with a
|
|
8306
|
+
// sub-second handshake budget sees that as a timeout with no server-side trace.
|
|
8307
|
+
// listen(2) clamps to net.core.somaxconn, so following somaxconn is both the
|
|
8308
|
+
// largest useful value and the one an operator can actually tune.
|
|
8309
|
+
function defaultBacklog() {
|
|
8310
|
+
try {
|
|
8311
|
+
return parseInt(fs$3.readFileSync("/proc/sys/net/core/somaxconn", "utf8")) || 511;
|
|
8312
|
+
}
|
|
8313
|
+
catch (err) {
|
|
8314
|
+
// Not Linux, or procfs isn't mounted. 511 is node's own default.
|
|
8315
|
+
return 511;
|
|
8316
|
+
}
|
|
8317
|
+
}
|
|
8318
|
+
// Only the builder keeps an object cache on disk. The scheduler tracks which
|
|
8319
|
+
// builder holds which sha1 in memory, and the daemon uses cacheDir purely for
|
|
8320
|
+
// the default socket path -- validating an object cache for either created a
|
|
8321
|
+
// directory they never read and, on a format bump, tried to destroy one they do
|
|
8322
|
+
// not necessarily own.
|
|
8323
|
+
function common$2(option, hasObjectCache = false) {
|
|
8324
|
+
validateCache(option);
|
|
8325
|
+
if (hasObjectCache) {
|
|
8326
|
+
validateObjectCache(option);
|
|
8327
|
+
}
|
|
8328
|
+
return {
|
|
8329
|
+
cacheDir: cacheDir.bind(undefined, option),
|
|
8330
|
+
Version,
|
|
8331
|
+
ObjectCacheFormatVersion
|
|
8332
|
+
};
|
|
8333
|
+
}
|
|
8334
|
+
|
|
8214
8335
|
/**
|
|
8215
8336
|
* Check if we're required to add a port number.
|
|
8216
8337
|
*
|
|
@@ -54881,8 +55002,14 @@ class Server extends EventEmitter__default["default"] {
|
|
|
54881
55002
|
this.emit("listen", this.app);
|
|
54882
55003
|
this.port = this.option.int("port", 8096);
|
|
54883
55004
|
this.server = require$$1__default$1["default"].createServer(this.app);
|
|
54884
|
-
|
|
54885
|
-
|
|
55005
|
+
// No backlog here on purpose: ws only uses that option when it creates
|
|
55006
|
+
// its own http server, which noServer mode explicitly does not do.
|
|
55007
|
+
this.ws = new ws.Server({ noServer: true });
|
|
55008
|
+
this.server.listen({
|
|
55009
|
+
port: this.port,
|
|
55010
|
+
backlog: this.option.int("backlog", defaultBacklog()),
|
|
55011
|
+
host: "0.0.0.0"
|
|
55012
|
+
});
|
|
54886
55013
|
this.server.on("upgrade", (req, socket, head) => {
|
|
54887
55014
|
assert__default["default"](this.ws, "Must have ws");
|
|
54888
55015
|
this.ws.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -55061,47 +55188,59 @@ class CompileJob extends EventEmitter__default["default"] {
|
|
|
55061
55188
|
this.dir = path__default["default"].join(vm.root, "compiles", String(this.id));
|
|
55062
55189
|
this.vmDir = path__default["default"].join("/", "compiles", String(this.id));
|
|
55063
55190
|
this.sourceFileName = sourcePath ? path__default["default"].basename(sourcePath) : "sourcefile";
|
|
55064
|
-
fs$3.mkdirpSync(this.dir);
|
|
55065
|
-
this.fd = fs$3.openSync(path__default["default"].join(this.dir, this.sourceFileName), "w");
|
|
55066
55191
|
this.cppSize = 0;
|
|
55067
55192
|
this.startCompile = undefined;
|
|
55193
|
+
this.opened = fs$3.mkdirp(this.dir).then(() => fs$3.open(path__default["default"].join(this.dir, this.sourceFileName), "w"));
|
|
55194
|
+
// Nothing awaits this until feed(), and an unhandled rejection in the
|
|
55195
|
+
// meantime would take the builder down.
|
|
55196
|
+
this.opened.catch(() => {
|
|
55197
|
+
/* reported by feed */
|
|
55198
|
+
});
|
|
55068
55199
|
}
|
|
55069
55200
|
sendCallback(error) {
|
|
55070
55201
|
if (error) {
|
|
55071
55202
|
console.error("Got send error for", this.vmDir, this.id, this.commandLine);
|
|
55072
|
-
|
|
55073
|
-
type: "compileFinished",
|
|
55074
|
-
success: false,
|
|
55075
|
-
id: this.id,
|
|
55076
|
-
files: [],
|
|
55077
|
-
exitCode: -1,
|
|
55078
|
-
sourcePath: "",
|
|
55079
|
-
error: error.toString()
|
|
55080
|
-
};
|
|
55081
|
-
this.vm.compileFinished(compileFinished);
|
|
55203
|
+
this.fail(error);
|
|
55082
55204
|
}
|
|
55083
55205
|
}
|
|
55084
55206
|
feed(data) {
|
|
55085
|
-
|
|
55086
|
-
|
|
55087
|
-
|
|
55088
|
-
|
|
55089
|
-
|
|
55090
|
-
|
|
55091
|
-
|
|
55092
|
-
|
|
55093
|
-
|
|
55094
|
-
|
|
55095
|
-
|
|
55096
|
-
|
|
55097
|
-
|
|
55098
|
-
|
|
55099
|
-
|
|
55100
|
-
|
|
55207
|
+
this.opened
|
|
55208
|
+
.then((fd) => __awaiter(this, void 0, void 0, function* () {
|
|
55209
|
+
yield fs$3.write(fd, data);
|
|
55210
|
+
this.cppSize += data.length;
|
|
55211
|
+
this.startCompile = Date.now();
|
|
55212
|
+
yield fs$3.close(fd);
|
|
55213
|
+
this.vm.child.send({
|
|
55214
|
+
type: "compile",
|
|
55215
|
+
commandLine: this.commandLine,
|
|
55216
|
+
argv0: this.argv0,
|
|
55217
|
+
id: this.id,
|
|
55218
|
+
dir: this.vmDir,
|
|
55219
|
+
sourceFileName: this.sourceFileName,
|
|
55220
|
+
clientSourcePath: this.sourcePath,
|
|
55221
|
+
clientCwd: this.clientCwd
|
|
55222
|
+
}, this.sendCallback.bind(this));
|
|
55223
|
+
}))
|
|
55224
|
+
.catch((err) => {
|
|
55225
|
+
console.error("Failed to write source file for", this.vmDir, this.id, err);
|
|
55226
|
+
this.fail(err);
|
|
55227
|
+
});
|
|
55101
55228
|
}
|
|
55102
55229
|
cancel() {
|
|
55103
55230
|
this.vm.child.send({ type: "cancel", id: this.id }, this.sendCallback.bind(this));
|
|
55104
55231
|
}
|
|
55232
|
+
fail(error) {
|
|
55233
|
+
const compileFinished = {
|
|
55234
|
+
type: "compileFinished",
|
|
55235
|
+
success: false,
|
|
55236
|
+
id: this.id,
|
|
55237
|
+
files: [],
|
|
55238
|
+
exitCode: -1,
|
|
55239
|
+
sourcePath: "",
|
|
55240
|
+
error: error.toString()
|
|
55241
|
+
};
|
|
55242
|
+
this.vm.compileFinished(compileFinished);
|
|
55243
|
+
}
|
|
55105
55244
|
}
|
|
55106
55245
|
|
|
55107
55246
|
class VM extends EventEmitter__default["default"] {
|
|
@@ -55179,6 +55318,28 @@ class VM extends EventEmitter__default["default"] {
|
|
|
55179
55318
|
console.error("Got some error", msg.error);
|
|
55180
55319
|
}
|
|
55181
55320
|
const now = Date.now();
|
|
55321
|
+
const dir = this.compiles[msg.id].dir;
|
|
55322
|
+
let released = false;
|
|
55323
|
+
const release = () => {
|
|
55324
|
+
if (released) {
|
|
55325
|
+
return;
|
|
55326
|
+
}
|
|
55327
|
+
released = true;
|
|
55328
|
+
clearTimeout(leakTimer);
|
|
55329
|
+
if (!this.keepCompiles) {
|
|
55330
|
+
fs$3.remove(dir).catch((err) => {
|
|
55331
|
+
console.error(`Failed to remove compile directory ${dir}`, err);
|
|
55332
|
+
});
|
|
55333
|
+
}
|
|
55334
|
+
};
|
|
55335
|
+
// A handler that throws before releasing would keep the directory
|
|
55336
|
+
// until the builder restarts and cleared its whole compiles root. The
|
|
55337
|
+
// reads it is holding the directory for take milliseconds, so anything
|
|
55338
|
+
// still outstanding this much later is a bug, not slow disk.
|
|
55339
|
+
const leakTimer = setTimeout(() => {
|
|
55340
|
+
console.error(`Compile directory ${dir} was never released, removing it anyway`);
|
|
55341
|
+
release();
|
|
55342
|
+
}, 5 * 60000).unref();
|
|
55182
55343
|
const finishedEvent = {
|
|
55183
55344
|
cppSize: compile.cppSize,
|
|
55184
55345
|
compileDuration: now - (compile.startCompile || 0),
|
|
@@ -55191,13 +55352,19 @@ class VM extends EventEmitter__default["default"] {
|
|
|
55191
55352
|
path: file.path,
|
|
55192
55353
|
absolute: path__default["default"].join(this.root, file.mapped ? file.mapped : file.path)
|
|
55193
55354
|
};
|
|
55194
|
-
})
|
|
55355
|
+
}),
|
|
55356
|
+
release
|
|
55195
55357
|
};
|
|
55196
|
-
compile.emit("finished", finishedEvent);
|
|
55197
|
-
if (!this.keepCompiles) {
|
|
55198
|
-
fs$3.remove(this.compiles[msg.id].dir);
|
|
55199
|
-
}
|
|
55200
55358
|
delete this.compiles[msg.id];
|
|
55359
|
+
// The directory used to be removed right here, which forced the
|
|
55360
|
+
// handler to read and compress every output file synchronously to beat
|
|
55361
|
+
// the cleanup. It owns the directory now and releases it when done.
|
|
55362
|
+
if (compile.listenerCount("finished")) {
|
|
55363
|
+
compile.emit("finished", finishedEvent);
|
|
55364
|
+
}
|
|
55365
|
+
else {
|
|
55366
|
+
release();
|
|
55367
|
+
}
|
|
55201
55368
|
}
|
|
55202
55369
|
destroy() {
|
|
55203
55370
|
this.destroying = true;
|
|
@@ -55219,81 +55386,6 @@ class VM extends EventEmitter__default["default"] {
|
|
|
55219
55386
|
}
|
|
55220
55387
|
}
|
|
55221
55388
|
|
|
55222
|
-
const Version = 5;
|
|
55223
|
-
// 6: objects now carry the client's real source path and compilation dir,
|
|
55224
|
-
// baked in at compile time instead of the builder's /compiles paths, and the
|
|
55225
|
-
// stored response no longer keeps the paths the client used to patch with.
|
|
55226
|
-
const ObjectCacheFormatVersion = 6;
|
|
55227
|
-
function cacheDir(option) {
|
|
55228
|
-
let dir = option("cache-dir");
|
|
55229
|
-
if (!dir) {
|
|
55230
|
-
dir = path__default["default"].join(os__default["default"].homedir(), ".cache", "fisk", path__default["default"].basename(option.prefix || ""));
|
|
55231
|
-
}
|
|
55232
|
-
return dir;
|
|
55233
|
-
}
|
|
55234
|
-
function validateCache(option) {
|
|
55235
|
-
const dir = cacheDir(option);
|
|
55236
|
-
const file = path__default["default"].join(dir, "version");
|
|
55237
|
-
// console.log(dir);
|
|
55238
|
-
let version;
|
|
55239
|
-
try {
|
|
55240
|
-
version = fs$3.readFileSync(file);
|
|
55241
|
-
if (version.readUInt32BE() === Version) {
|
|
55242
|
-
return;
|
|
55243
|
-
}
|
|
55244
|
-
}
|
|
55245
|
-
catch (err) {
|
|
55246
|
-
/* */
|
|
55247
|
-
}
|
|
55248
|
-
if (version) {
|
|
55249
|
-
console.log(`Wrong version. Destroying cache ${dir}`);
|
|
55250
|
-
}
|
|
55251
|
-
fs$3.removeSync(dir);
|
|
55252
|
-
fs$3.mkdirpSync(dir);
|
|
55253
|
-
const buf = Buffer.allocUnsafe(4);
|
|
55254
|
-
buf.writeUInt32BE(Version);
|
|
55255
|
-
fs$3.writeFileSync(file, buf);
|
|
55256
|
-
}
|
|
55257
|
-
function validateObjectCache(option) {
|
|
55258
|
-
const dir = cacheDir(option);
|
|
55259
|
-
const objectCacheDir = option.string("object-cache-dir") || path__default["default"].join(dir, "objectcache");
|
|
55260
|
-
const file = path__default["default"].join(objectCacheDir, "version");
|
|
55261
|
-
let version;
|
|
55262
|
-
try {
|
|
55263
|
-
version = fs$3.readFileSync(file);
|
|
55264
|
-
if (version.readUInt32BE() === ObjectCacheFormatVersion) {
|
|
55265
|
-
return;
|
|
55266
|
-
}
|
|
55267
|
-
}
|
|
55268
|
-
catch (err) {
|
|
55269
|
-
/* */
|
|
55270
|
-
}
|
|
55271
|
-
if (version) {
|
|
55272
|
-
console.log(`Wrong object cache version. Destroying object cache ${objectCacheDir}`);
|
|
55273
|
-
}
|
|
55274
|
-
fs$3.removeSync(objectCacheDir);
|
|
55275
|
-
fs$3.mkdirpSync(objectCacheDir);
|
|
55276
|
-
const buf = Buffer.allocUnsafe(4);
|
|
55277
|
-
buf.writeUInt32BE(ObjectCacheFormatVersion);
|
|
55278
|
-
fs$3.writeFileSync(file, buf);
|
|
55279
|
-
}
|
|
55280
|
-
// Only the builder keeps an object cache on disk. The scheduler tracks which
|
|
55281
|
-
// builder holds which sha1 in memory, and the daemon uses cacheDir purely for
|
|
55282
|
-
// the default socket path -- validating an object cache for either created a
|
|
55283
|
-
// directory they never read and, on a format bump, tried to destroy one they do
|
|
55284
|
-
// not necessarily own.
|
|
55285
|
-
function common$2(option, hasObjectCache = false) {
|
|
55286
|
-
validateCache(option);
|
|
55287
|
-
if (hasObjectCache) {
|
|
55288
|
-
validateObjectCache(option);
|
|
55289
|
-
}
|
|
55290
|
-
return {
|
|
55291
|
-
cacheDir: cacheDir.bind(undefined, option),
|
|
55292
|
-
Version,
|
|
55293
|
-
ObjectCacheFormatVersion
|
|
55294
|
-
};
|
|
55295
|
-
}
|
|
55296
|
-
|
|
55297
55389
|
var resolve;
|
|
55298
55390
|
var hasRequiredResolve;
|
|
55299
55391
|
|
|
@@ -60174,6 +60266,14 @@ const option = createOptions({
|
|
|
60174
60266
|
additionalFiles: ["fisk/builder.conf.override"]
|
|
60175
60267
|
});
|
|
60176
60268
|
const common = common$2(option, true);
|
|
60269
|
+
// Reads, writes, gzip and gunzip all run on libuv's threadpool, and now that
|
|
60270
|
+
// the job and object cache paths use the asynchronous forms they share it.
|
|
60271
|
+
// Four threads is not enough to keep as many jobs as we have slots moving.
|
|
60272
|
+
// libuv reads this when it first creates the pool, so it has to be set before
|
|
60273
|
+
// anything asynchronous has been submitted, which is why it lives up here.
|
|
60274
|
+
if (!process.env.UV_THREADPOOL_SIZE) {
|
|
60275
|
+
process.env.UV_THREADPOOL_SIZE = String(Math.min(128, Math.max(8, os__default["default"].cpus().length)));
|
|
60276
|
+
}
|
|
60177
60277
|
if (process.getuid() !== 0) {
|
|
60178
60278
|
console.error("fisk builder needs to run as root to be able to chroot");
|
|
60179
60279
|
process.exit(1);
|
|
@@ -60271,26 +60371,34 @@ function getFromCache(job, cb) {
|
|
|
60271
60371
|
else {
|
|
60272
60372
|
// console.log("got good response from file", file);
|
|
60273
60373
|
// console.log("sending some data", buffer.length, fileIdx, item.response.index.length);
|
|
60274
|
-
|
|
60275
|
-
|
|
60276
|
-
|
|
60277
|
-
|
|
60278
|
-
|
|
60374
|
+
const sendAndContinue = (sendBuffer) => {
|
|
60375
|
+
job.send(sendBuffer);
|
|
60376
|
+
pos += read;
|
|
60377
|
+
if (++fileIdx < item.response.index.length) {
|
|
60378
|
+
work();
|
|
60279
60379
|
}
|
|
60280
|
-
|
|
60281
|
-
|
|
60282
|
-
console.error(`Failed to gunzip ${path__default["default"].join(objectCache.dir, item.response.sha1)} for file index ${fileIdx}:`, gunzipErr);
|
|
60283
|
-
finish(gunzipErr);
|
|
60284
|
-
return;
|
|
60380
|
+
else {
|
|
60381
|
+
finish();
|
|
60285
60382
|
}
|
|
60286
|
-
}
|
|
60287
|
-
|
|
60288
|
-
|
|
60289
|
-
|
|
60290
|
-
|
|
60383
|
+
};
|
|
60384
|
+
// Decompress if client doesn't support compressed responses
|
|
60385
|
+
// from cache. Asynchronously: a cache hit is the fast path
|
|
60386
|
+
// and used to spend it inflating megabytes on the event
|
|
60387
|
+
// loop, once per file. Files are still sent in index order
|
|
60388
|
+
// because the next read only starts from sendAndContinue.
|
|
60389
|
+
if (!sendCompressed && buffer.byteLength > 0) {
|
|
60390
|
+
zlib__default["default"].gunzip(buffer, (gunzipErr, inflated) => {
|
|
60391
|
+
if (gunzipErr) {
|
|
60392
|
+
assert__default["default"](objectCache, "Must have objectCache");
|
|
60393
|
+
console.error(`Failed to gunzip ${path__default["default"].join(objectCache.dir, item.response.sha1)} for file index ${fileIdx}:`, gunzipErr);
|
|
60394
|
+
finish(gunzipErr);
|
|
60395
|
+
return;
|
|
60396
|
+
}
|
|
60397
|
+
sendAndContinue(inflated);
|
|
60398
|
+
});
|
|
60291
60399
|
}
|
|
60292
60400
|
else {
|
|
60293
|
-
|
|
60401
|
+
sendAndContinue(buffer);
|
|
60294
60402
|
}
|
|
60295
60403
|
}
|
|
60296
60404
|
});
|
|
@@ -60848,6 +60956,19 @@ server.on("listen", (app) => {
|
|
|
60848
60956
|
}
|
|
60849
60957
|
});
|
|
60850
60958
|
});
|
|
60959
|
+
const gzip = require$$1$3.promisify(zlib__default["default"].gzip);
|
|
60960
|
+
// Always compressed, because that is the form the object cache stores. The
|
|
60961
|
+
// uncompressed buffer is kept alongside it since the response index reports
|
|
60962
|
+
// both sizes and a client that cannot take compressed data gets that one.
|
|
60963
|
+
function readAndCompress(files) {
|
|
60964
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
60965
|
+
return Promise.all(files.map((f) => __awaiter(this, void 0, void 0, function* () {
|
|
60966
|
+
const uncompressed = yield fs$3.promises.readFile(f.absolute);
|
|
60967
|
+
const contents = uncompressed.byteLength > 0 ? yield gzip(uncompressed) : uncompressed;
|
|
60968
|
+
return { contents, uncompressed, path: f.path };
|
|
60969
|
+
})));
|
|
60970
|
+
});
|
|
60971
|
+
}
|
|
60851
60972
|
function startPending() {
|
|
60852
60973
|
// console.log(`startPending called ${jobQueue.length}`);
|
|
60853
60974
|
for (let idx = 0; idx < jobQueue.length; ++idx) {
|
|
@@ -60958,6 +61079,7 @@ server.on("job", (job) => {
|
|
|
60958
61079
|
j.op.on("finished", (event) => {
|
|
60959
61080
|
j.done = true;
|
|
60960
61081
|
if (j.aborted) {
|
|
61082
|
+
event.release();
|
|
60961
61083
|
return;
|
|
60962
61084
|
}
|
|
60963
61085
|
const end = Date.now();
|
|
@@ -60968,108 +61090,131 @@ server.on("job", (job) => {
|
|
|
60968
61090
|
}
|
|
60969
61091
|
else {
|
|
60970
61092
|
console.error("Can't find j?");
|
|
61093
|
+
event.release();
|
|
60971
61094
|
return;
|
|
60972
61095
|
}
|
|
60973
|
-
//
|
|
60974
|
-
//
|
|
60975
|
-
|
|
60976
|
-
|
|
60977
|
-
|
|
60978
|
-
|
|
60979
|
-
|
|
60980
|
-
|
|
60981
|
-
|
|
60982
|
-
}
|
|
60983
|
-
|
|
60984
|
-
|
|
60985
|
-
|
|
60986
|
-
|
|
60987
|
-
|
|
60988
|
-
|
|
60989
|
-
|
|
60990
|
-
|
|
60991
|
-
|
|
60992
|
-
|
|
61096
|
+
// Reading and gzipping every output file synchronously blocked
|
|
61097
|
+
// the event loop once per finished job, which on a full builder
|
|
61098
|
+
// is once per core per compile wave, and that is what stopped
|
|
61099
|
+
// handshakes from being answered. The compile directory is ours
|
|
61100
|
+
// until we release it, so this can take as long as it needs to.
|
|
61101
|
+
readAndCompress(event.files)
|
|
61102
|
+
.then((contents) => {
|
|
61103
|
+
if (j.aborted) {
|
|
61104
|
+
return;
|
|
61105
|
+
}
|
|
61106
|
+
respond(contents);
|
|
61107
|
+
})
|
|
61108
|
+
.catch((err) => {
|
|
61109
|
+
console.error(`Failed to collect output for job ${j.id}`, err);
|
|
61110
|
+
if (!j.aborted) {
|
|
61111
|
+
jobJob.send({
|
|
61112
|
+
type: "response",
|
|
61113
|
+
index: [],
|
|
61114
|
+
success: false,
|
|
61115
|
+
exitCode: 1,
|
|
61116
|
+
error: `Failed to collect output: ${err.message}`,
|
|
61117
|
+
sha1: jobJob.sha1,
|
|
61118
|
+
sourcePath: j.op.sourceFileName,
|
|
61119
|
+
stderr: j.stderr,
|
|
61120
|
+
stdout: j.stdout
|
|
61121
|
+
});
|
|
61122
|
+
}
|
|
61123
|
+
})
|
|
61124
|
+
.finally(() => {
|
|
61125
|
+
event.release();
|
|
61126
|
+
startPending();
|
|
60993
61127
|
});
|
|
60994
|
-
|
|
60995
|
-
|
|
60996
|
-
|
|
60997
|
-
|
|
60998
|
-
|
|
60999
|
-
assert__default["default"](
|
|
61000
|
-
|
|
61001
|
-
|
|
61002
|
-
|
|
61003
|
-
uncompressedSize: original.uncompressed.byteLength
|
|
61128
|
+
function respond(contents) {
|
|
61129
|
+
// Prepare data to send to client (compressed or uncompressed based on preference and capability)
|
|
61130
|
+
// Only send compressed if client wants it AND supports compressed responses
|
|
61131
|
+
const sendCompressed = j.job.compressed && j.job.supportsCompressedResponse;
|
|
61132
|
+
const toSend = contents.map((item) => {
|
|
61133
|
+
assert__default["default"](item.uncompressed, "Must have uncompressed data");
|
|
61134
|
+
return {
|
|
61135
|
+
contents: sendCompressed ? item.contents : item.uncompressed,
|
|
61136
|
+
path: item.path
|
|
61004
61137
|
};
|
|
61005
|
-
|
|
61006
|
-
|
|
61007
|
-
|
|
61008
|
-
|
|
61009
|
-
|
|
61010
|
-
|
|
61011
|
-
|
|
61012
|
-
|
|
61013
|
-
// job's chroot, and the requesting client's own path belongs
|
|
61014
|
-
// to whichever client happened to compile it first. Only the
|
|
61015
|
-
// file name survives being shared.
|
|
61016
|
-
sourcePath: j.op.sourceFileName,
|
|
61017
|
-
stderr: j.stderr,
|
|
61018
|
-
stdout: j.stdout
|
|
61019
|
-
};
|
|
61020
|
-
if (event.error) {
|
|
61021
|
-
response.error = event.error;
|
|
61022
|
-
}
|
|
61023
|
-
if (debug) {
|
|
61024
|
-
console.log("Sending response", jobJob.ip, jobJob.hostname, response);
|
|
61025
|
-
}
|
|
61026
|
-
jobJob.send(response);
|
|
61027
|
-
if (response.exitCode === 0 &&
|
|
61028
|
-
event.success &&
|
|
61029
|
-
objectCache &&
|
|
61030
|
-
response.sha1 &&
|
|
61031
|
-
objectCache.state(response.sha1) === "none") {
|
|
61032
|
-
// Cache metadata needs to reflect compressed sizes since we store compressed
|
|
61033
|
-
const cacheResponse = Object.assign(Object.assign({}, response), { index: contents.map((item) => {
|
|
61034
|
-
assert__default["default"](item.uncompressed, "Must have uncompressed data");
|
|
61035
|
-
return {
|
|
61138
|
+
});
|
|
61139
|
+
const response = {
|
|
61140
|
+
type: "response",
|
|
61141
|
+
index: toSend.map((item) => {
|
|
61142
|
+
const original = contents.find((c) => c.path === item.path);
|
|
61143
|
+
assert__default["default"](original, "Must have original contents");
|
|
61144
|
+
assert__default["default"](original.uncompressed, "Must have uncompressed data");
|
|
61145
|
+
const ret = {
|
|
61036
61146
|
path: item.path,
|
|
61037
61147
|
bytes: item.contents.length,
|
|
61038
|
-
uncompressedSize:
|
|
61148
|
+
uncompressedSize: original.uncompressed.byteLength
|
|
61039
61149
|
};
|
|
61040
|
-
|
|
61041
|
-
|
|
61042
|
-
|
|
61043
|
-
|
|
61044
|
-
|
|
61045
|
-
|
|
61046
|
-
|
|
61047
|
-
|
|
61150
|
+
return ret;
|
|
61151
|
+
}),
|
|
61152
|
+
success: event.success,
|
|
61153
|
+
exitCode: event.exitCode,
|
|
61154
|
+
sha1: jobJob.sha1,
|
|
61155
|
+
// Just the basename. This goes into the object cache and is
|
|
61156
|
+
// replayed on every hit, so it has to still mean something
|
|
61157
|
+
// later: the /compiles/<id> directory belongs to this one
|
|
61158
|
+
// job's chroot, and the requesting client's own path belongs
|
|
61159
|
+
// to whichever client happened to compile it first. Only the
|
|
61160
|
+
// file name survives being shared.
|
|
61161
|
+
sourcePath: j.op.sourceFileName,
|
|
61162
|
+
stderr: j.stderr,
|
|
61163
|
+
stdout: j.stdout
|
|
61164
|
+
};
|
|
61165
|
+
if (event.error) {
|
|
61166
|
+
response.error = event.error;
|
|
61048
61167
|
}
|
|
61049
|
-
|
|
61050
|
-
|
|
61051
|
-
|
|
61052
|
-
|
|
61053
|
-
|
|
61054
|
-
|
|
61055
|
-
|
|
61056
|
-
|
|
61057
|
-
|
|
61058
|
-
|
|
61059
|
-
|
|
61060
|
-
|
|
61061
|
-
|
|
61062
|
-
|
|
61063
|
-
|
|
61064
|
-
|
|
61065
|
-
|
|
61066
|
-
|
|
61067
|
-
|
|
61068
|
-
|
|
61069
|
-
|
|
61168
|
+
if (debug) {
|
|
61169
|
+
console.log("Sending response", jobJob.ip, jobJob.hostname, response);
|
|
61170
|
+
}
|
|
61171
|
+
jobJob.send(response);
|
|
61172
|
+
if (response.exitCode === 0 &&
|
|
61173
|
+
event.success &&
|
|
61174
|
+
objectCache &&
|
|
61175
|
+
response.sha1 &&
|
|
61176
|
+
objectCache.state(response.sha1) === "none") {
|
|
61177
|
+
// Cache metadata needs to reflect compressed sizes since we store compressed
|
|
61178
|
+
const cacheResponse = Object.assign(Object.assign({}, response), { index: contents.map((item) => {
|
|
61179
|
+
assert__default["default"](item.uncompressed, "Must have uncompressed data");
|
|
61180
|
+
return {
|
|
61181
|
+
path: item.path,
|
|
61182
|
+
bytes: item.contents.length,
|
|
61183
|
+
uncompressedSize: item.uncompressed.byteLength
|
|
61184
|
+
};
|
|
61185
|
+
}) });
|
|
61186
|
+
cacheResponse.commandLine = jobJob.commandLine;
|
|
61187
|
+
cacheResponse.environment = jobJob.hash;
|
|
61188
|
+
objectCache.add(cacheResponse, contents);
|
|
61189
|
+
}
|
|
61190
|
+
toSend.forEach((x) => {
|
|
61191
|
+
if (x.contents && x.contents.byteLength) {
|
|
61192
|
+
jobJob.send(x.contents);
|
|
61193
|
+
}
|
|
61070
61194
|
});
|
|
61195
|
+
// console.log("GOT ID", j);
|
|
61196
|
+
assert__default["default"](uploadDuration !== undefined, "Must have uploadDuration");
|
|
61197
|
+
if (event.success) {
|
|
61198
|
+
client.send("jobFinished", {
|
|
61199
|
+
id: j.id,
|
|
61200
|
+
cppSize: event.cppSize,
|
|
61201
|
+
compileDuration: event.compileDuration,
|
|
61202
|
+
compileSpeed: event.cppSize / event.compileDuration,
|
|
61203
|
+
uploadDuration: uploadDuration,
|
|
61204
|
+
uploadSpeed: event.cppSize / uploadDuration
|
|
61205
|
+
});
|
|
61206
|
+
}
|
|
61207
|
+
else {
|
|
61208
|
+
client.send("jobAborted", {
|
|
61209
|
+
id: j.id,
|
|
61210
|
+
cppSize: event.cppSize,
|
|
61211
|
+
compileDuration: event.compileDuration,
|
|
61212
|
+
compileSpeed: event.cppSize / event.compileDuration,
|
|
61213
|
+
uploadDuration: uploadDuration,
|
|
61214
|
+
uploadSpeed: event.cppSize / uploadDuration
|
|
61215
|
+
});
|
|
61216
|
+
}
|
|
61071
61217
|
}
|
|
61072
|
-
startPending();
|
|
61073
61218
|
});
|
|
61074
61219
|
},
|
|
61075
61220
|
cancel: function () {
|