@andersbakken/fisk 5.0.12 → 5.0.14
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/VM_runtime.js +55 -31
- package/builder/fisk-builder.js +56 -32
- package/daemon/fisk-daemon.js +235 -140
- package/package.json +1 -1
- package/scheduler/fisk-scheduler.js +44 -6
package/builder/VM_runtime.js
CHANGED
|
@@ -3027,20 +3027,18 @@ var output = {
|
|
|
3027
3027
|
|
|
3028
3028
|
var fs = libExports;
|
|
3029
3029
|
|
|
3030
|
-
//
|
|
3031
|
-
//
|
|
3032
|
-
//
|
|
3033
|
-
//
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
const FISK_NAME_PAD = "/fisk-name" + "_".repeat(FISK_PAD_LENGTH - "/fisk-name".length);
|
|
3041
|
-
const FISK_CDIR_PAD = "/fisk-cdir" + "_".repeat(FISK_PAD_LENGTH - "/fisk-cdir".length);
|
|
3030
|
+
// Flags asking the compiler to record its own argv, mapped to the negation the
|
|
3031
|
+
// same compiler spells it with. -g* forms land in DW_AT_producer, -f* forms in a
|
|
3032
|
+
// .GCC.command.line section; clang uses the command-line names and also accepts
|
|
3033
|
+
// the gcc-switches ones as aliases.
|
|
3034
|
+
const RECORD_FLAG_NEGATIONS = {
|
|
3035
|
+
"-grecord-command-line": "-gno-record-command-line",
|
|
3036
|
+
"-frecord-command-line": "-fno-record-command-line",
|
|
3037
|
+
"-grecord-gcc-switches": "-gno-record-gcc-switches",
|
|
3038
|
+
"-frecord-gcc-switches": "-fno-record-gcc-switches"
|
|
3039
|
+
};
|
|
3042
3040
|
class Compile extends EventEmitter__default["default"] {
|
|
3043
|
-
constructor(args, argv0, dir, debug, sourceFileName,
|
|
3041
|
+
constructor(args, argv0, dir, debug, sourceFileName, { clientSourcePath, clientCwd } = {}) {
|
|
3044
3042
|
super();
|
|
3045
3043
|
if (!args || !args.length || !dir || !argv0) {
|
|
3046
3044
|
console.error(argv0, args, dir);
|
|
@@ -3165,25 +3163,48 @@ class Compile extends EventEmitter__default["default"] {
|
|
|
3165
3163
|
throw new Error("No sourcefile");
|
|
3166
3164
|
}
|
|
3167
3165
|
const sourceFileInDir = path__default["default"].join(dir, sourceFileName || path__default["default"].basename(sourcePath));
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3166
|
+
// Bake the client's real paths into the object instead of emitting the
|
|
3167
|
+
// builder's /compiles path for the client to rewrite afterwards. The
|
|
3168
|
+
// client folds these paths into its object-cache key, so a cached object
|
|
3169
|
+
// is only ever handed to a client that wants exactly these values.
|
|
3170
|
+
//
|
|
3171
|
+
// This is the only approach that covers LTO: with -flto the output is
|
|
3172
|
+
// bitcode, which no ELF-level patcher can load. It also needs no help
|
|
3173
|
+
// for compressed debug sections or for gcc, both of which defeat a byte
|
|
3174
|
+
// scan over the finished object.
|
|
3175
|
+
if (clientSourcePath && clientCwd) {
|
|
3176
|
+
// -grecord-command-line (clang) / -frecord-gcc-switches (gcc) embed
|
|
3177
|
+
// our own argv verbatim into DW_AT_producer or .GCC.command.line,
|
|
3178
|
+
// which would leave the builder's /compiles path in there. The
|
|
3179
|
+
// recorded line is our rewritten argv rather than the client's
|
|
3180
|
+
// anyway, so it is misleading as well as leaky -- turn it off.
|
|
3181
|
+
//
|
|
3182
|
+
// Negate each flag in place rather than dropping it and appending one
|
|
3183
|
+
// fixed negation: whichever compiler accepted the positive spelling
|
|
3184
|
+
// necessarily accepts its own negation, whereas a fixed flag is a
|
|
3185
|
+
// guess about the compiler.
|
|
3186
|
+
for (let i = 0; i < args.length; ++i) {
|
|
3187
|
+
const negation = RECORD_FLAG_NEGATIONS[args[i]];
|
|
3188
|
+
if (negation) {
|
|
3189
|
+
args[i] = negation;
|
|
3179
3190
|
}
|
|
3180
3191
|
}
|
|
3181
|
-
|
|
3182
|
-
//
|
|
3183
|
-
//
|
|
3184
|
-
//
|
|
3185
|
-
|
|
3186
|
-
|
|
3192
|
+
// -fdebug-prefix-map only, for both compilers. clang also has
|
|
3193
|
+
// -Xclang -main-file-name / -Xclang -fdebug-compilation-dir, which
|
|
3194
|
+
// set the two values outright with no rule-precedence subtlety, but
|
|
3195
|
+
// those are cc1 internals reached through -Xclang and carry no
|
|
3196
|
+
// cross-version compatibility guarantee. -fdebug-prefix-map is a
|
|
3197
|
+
// driver flag that predates every compiler fisk supports (gcc 4.3,
|
|
3198
|
+
// clang 3.8), so it cannot fail on an older toolchain.
|
|
3199
|
+
//
|
|
3200
|
+
// The directory rule comes first and the more specific source-file
|
|
3201
|
+
// rule last: both compilers let a later mapping win, and the
|
|
3202
|
+
// directory is a prefix of the file. On gcc the file rule is inert --
|
|
3203
|
+
// gcc takes DW_AT_name from the #line markers in the preprocessed
|
|
3204
|
+
// source, which already name the client's file -- but it is harmless
|
|
3205
|
+
// there and needed for clang.
|
|
3206
|
+
args.push(`-fdebug-prefix-map=${dir}=${clientCwd}`);
|
|
3207
|
+
args.push(`-fdebug-prefix-map=${sourceFileInDir}=${clientSourcePath}`);
|
|
3187
3208
|
}
|
|
3188
3209
|
if (!hasDashX) {
|
|
3189
3210
|
switch (path__default["default"].extname(sourcePath)) {
|
|
@@ -3771,7 +3792,10 @@ process.on("message", (msg) => {
|
|
|
3771
3792
|
if (argv.debug) {
|
|
3772
3793
|
console.log("Creating new compile", msg.commandLine, msg.argv0, msg.dir);
|
|
3773
3794
|
}
|
|
3774
|
-
const compile = new Compile(msg.commandLine, msg.argv0, msg.dir, argv.debug, msg.sourceFileName,
|
|
3795
|
+
const compile = new Compile(msg.commandLine, msg.argv0, msg.dir, argv.debug, msg.sourceFileName, {
|
|
3796
|
+
clientSourcePath: msg.clientSourcePath,
|
|
3797
|
+
clientCwd: msg.clientCwd
|
|
3798
|
+
});
|
|
3775
3799
|
// console.log("running thing", msg.commandLine);
|
|
3776
3800
|
compile.on("stdout", (data) => {
|
|
3777
3801
|
send({ type: "compileStdOut", id: msg.id, data: data });
|
package/builder/fisk-builder.js
CHANGED
|
@@ -8171,7 +8171,6 @@ class Job extends EventEmitter__default["default"] {
|
|
|
8171
8171
|
this.sha1 = data.sha1;
|
|
8172
8172
|
this.id = data.id;
|
|
8173
8173
|
this.builderIp = data.builderIp;
|
|
8174
|
-
this.paddedPaths = data.paddedPaths;
|
|
8175
8174
|
this.supportsCompressedResponse = data.supportsCompressedResponse;
|
|
8176
8175
|
}
|
|
8177
8176
|
get readyState() {
|
|
@@ -54955,7 +54954,6 @@ class Server extends EventEmitter__default["default"] {
|
|
|
54955
54954
|
sourcePath: String(req.headers["x-fisk-sourcefile"]),
|
|
54956
54955
|
user: String(req.headers["x-fisk-user"]),
|
|
54957
54956
|
supportsCompressedResponse: req.headers["x-fisk-supports-compressed-response"] === "true",
|
|
54958
|
-
paddedPaths: req.headers["x-fisk-padded-paths"] === "true",
|
|
54959
54957
|
ws
|
|
54960
54958
|
});
|
|
54961
54959
|
break;
|
|
@@ -54990,6 +54988,7 @@ class Server extends EventEmitter__default["default"] {
|
|
|
54990
54988
|
client.compressed = json.compressed;
|
|
54991
54989
|
client.commandLine = json.commandLine;
|
|
54992
54990
|
client.argv0 = json.argv0;
|
|
54991
|
+
client.cwd = json.cwd;
|
|
54993
54992
|
client.connectTime = connectTime;
|
|
54994
54993
|
client.wait = json.wait;
|
|
54995
54994
|
this.emit("job", client);
|
|
@@ -55051,13 +55050,14 @@ class Server extends EventEmitter__default["default"] {
|
|
|
55051
55050
|
}
|
|
55052
55051
|
|
|
55053
55052
|
class CompileJob extends EventEmitter__default["default"] {
|
|
55054
|
-
constructor(commandLine, argv0, id, vm, sourcePath,
|
|
55053
|
+
constructor(commandLine, argv0, id, vm, sourcePath, clientCwd) {
|
|
55055
55054
|
super();
|
|
55056
55055
|
this.commandLine = commandLine;
|
|
55057
55056
|
this.argv0 = argv0;
|
|
55058
55057
|
this.id = id;
|
|
55059
55058
|
this.vm = vm;
|
|
55060
|
-
this.
|
|
55059
|
+
this.sourcePath = sourcePath;
|
|
55060
|
+
this.clientCwd = clientCwd;
|
|
55061
55061
|
this.dir = path__default["default"].join(vm.root, "compiles", String(this.id));
|
|
55062
55062
|
this.vmDir = path__default["default"].join("/", "compiles", String(this.id));
|
|
55063
55063
|
this.sourceFileName = sourcePath ? path__default["default"].basename(sourcePath) : "sourcefile";
|
|
@@ -55088,7 +55088,16 @@ class CompileJob extends EventEmitter__default["default"] {
|
|
|
55088
55088
|
this.startCompile = Date.now();
|
|
55089
55089
|
fs$3.closeSync(this.fd);
|
|
55090
55090
|
this.fd = undefined;
|
|
55091
|
-
this.vm.child.send({
|
|
55091
|
+
this.vm.child.send({
|
|
55092
|
+
type: "compile",
|
|
55093
|
+
commandLine: this.commandLine,
|
|
55094
|
+
argv0: this.argv0,
|
|
55095
|
+
id: this.id,
|
|
55096
|
+
dir: this.vmDir,
|
|
55097
|
+
sourceFileName: this.sourceFileName,
|
|
55098
|
+
clientSourcePath: this.sourcePath,
|
|
55099
|
+
clientCwd: this.clientCwd
|
|
55100
|
+
}, this.sendCallback.bind(this));
|
|
55092
55101
|
}
|
|
55093
55102
|
cancel() {
|
|
55094
55103
|
this.vm.child.send({ type: "cancel", id: this.id }, this.sendCallback.bind(this));
|
|
@@ -55199,8 +55208,8 @@ class VM extends EventEmitter__default["default"] {
|
|
|
55199
55208
|
}
|
|
55200
55209
|
});
|
|
55201
55210
|
}
|
|
55202
|
-
startCompile(commandLine, argv0, id, sourcePath,
|
|
55203
|
-
const compile = new CompileJob(commandLine, argv0, id, this, sourcePath,
|
|
55211
|
+
startCompile(commandLine, argv0, id, sourcePath, cwd) {
|
|
55212
|
+
const compile = new CompileJob(commandLine, argv0, id, this, sourcePath, cwd);
|
|
55204
55213
|
this.compiles[compile.id] = compile;
|
|
55205
55214
|
// console.log("startCompile " + compile.id);
|
|
55206
55215
|
return compile;
|
|
@@ -55211,7 +55220,10 @@ class VM extends EventEmitter__default["default"] {
|
|
|
55211
55220
|
}
|
|
55212
55221
|
|
|
55213
55222
|
const Version = 5;
|
|
55214
|
-
|
|
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;
|
|
55215
55227
|
function cacheDir(option) {
|
|
55216
55228
|
let dir = option("cache-dir");
|
|
55217
55229
|
if (!dir) {
|
|
@@ -55265,9 +55277,16 @@ function validateObjectCache(option) {
|
|
|
55265
55277
|
buf.writeUInt32BE(ObjectCacheFormatVersion);
|
|
55266
55278
|
fs$3.writeFileSync(file, buf);
|
|
55267
55279
|
}
|
|
55268
|
-
|
|
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) {
|
|
55269
55286
|
validateCache(option);
|
|
55270
|
-
|
|
55287
|
+
if (hasObjectCache) {
|
|
55288
|
+
validateObjectCache(option);
|
|
55289
|
+
}
|
|
55271
55290
|
return {
|
|
55272
55291
|
cacheDir: cacheDir.bind(undefined, option),
|
|
55273
55292
|
Version,
|
|
@@ -60125,25 +60144,25 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
|
60125
60144
|
console.log(`Usage: fisk-builder [options]
|
|
60126
60145
|
|
|
60127
60146
|
Options:
|
|
60128
|
-
--scheduler=URL
|
|
60129
|
-
--port=PORT
|
|
60130
|
-
--slots=N
|
|
60131
|
-
--debug
|
|
60132
|
-
--object-cache-size=SIZE
|
|
60133
|
-
--object-cache-dir=PATH
|
|
60134
|
-
--object-cache-purge-size=N
|
|
60147
|
+
--scheduler=URL Scheduler URL (default: localhost:8097)
|
|
60148
|
+
--port=PORT Listen port (default: 8096)
|
|
60149
|
+
--slots=N Number of compile slots
|
|
60150
|
+
--debug Enable debug logging
|
|
60151
|
+
--object-cache-size=SIZE Object cache size (e.g. "10gb")
|
|
60152
|
+
--object-cache-dir=PATH Object cache directory
|
|
60153
|
+
--object-cache-purge-size=N Size to purge cache down to
|
|
60135
60154
|
--restart-on-new-environments Restart when new environments arrive
|
|
60136
|
-
--name=NAME
|
|
60137
|
-
--hostname=HOST
|
|
60138
|
-
--labels=LABELS
|
|
60139
|
-
--npm-version-file=PATH
|
|
60140
|
-
--keep-compiles
|
|
60141
|
-
--vm-user=USER
|
|
60142
|
-
--inform-delay=MS
|
|
60143
|
-
--quit-on-error-delay=MS
|
|
60144
|
-
--loadInterval=MS
|
|
60145
|
-
--backlog=N
|
|
60146
|
-
--cache-dir=PATH
|
|
60155
|
+
--name=NAME Builder name
|
|
60156
|
+
--hostname=HOST Builder hostname (default: os.hostname())
|
|
60157
|
+
--labels=LABELS Builder labels
|
|
60158
|
+
--npm-version-file=PATH Path to npm version file
|
|
60159
|
+
--keep-compiles Keep compile directories after completion
|
|
60160
|
+
--vm-user=USER User for VM processes
|
|
60161
|
+
--inform-delay=MS Delay before informing scheduler (default: 5000)
|
|
60162
|
+
--quit-on-error-delay=MS Delay before quitting on error
|
|
60163
|
+
--loadInterval=MS Load reporting interval (default: 1000)
|
|
60164
|
+
--backlog=N Listen backlog (default: 50)
|
|
60165
|
+
--cache-dir=PATH Cache directory (default: ~/.cache/fisk/builder)
|
|
60147
60166
|
|
|
60148
60167
|
Config files: ~/.config/fisk/builder.conf, /etc/xdg/fisk/builder.conf
|
|
60149
60168
|
Environment variables: FISK_BUILDER_SCHEDULER, FISK_BUILDER_PORT, etc.`);
|
|
@@ -60154,7 +60173,7 @@ const option = createOptions({
|
|
|
60154
60173
|
noApplicationPath: true,
|
|
60155
60174
|
additionalFiles: ["fisk/builder.conf.override"]
|
|
60156
60175
|
});
|
|
60157
|
-
const common = common$2(option);
|
|
60176
|
+
const common = common$2(option, true);
|
|
60158
60177
|
if (process.getuid() !== 0) {
|
|
60159
60178
|
console.error("fisk builder needs to run as root to be able to chroot");
|
|
60160
60179
|
process.exit(1);
|
|
@@ -60922,7 +60941,7 @@ server.on("job", (job) => {
|
|
|
60922
60941
|
console.log("Starting job", j.id, jobJob.sourcePath, "for", jobJob.ip, jobJob.name, "wait", jobJob.wait);
|
|
60923
60942
|
assert__default["default"](jobJob.commandLine, "Must have commandLine");
|
|
60924
60943
|
assert__default["default"](jobJob.argv0, "Must have argv0");
|
|
60925
|
-
j.op = vm.startCompile(jobJob.commandLine, jobJob.argv0, jobJob.id, jobJob.sourcePath, jobJob.
|
|
60944
|
+
j.op = vm.startCompile(jobJob.commandLine, jobJob.argv0, jobJob.id, jobJob.sourcePath, jobJob.cwd);
|
|
60926
60945
|
if (j.buffer) {
|
|
60927
60946
|
j.op.feed(j.buffer);
|
|
60928
60947
|
j.buffer = undefined;
|
|
@@ -60988,8 +61007,13 @@ server.on("job", (job) => {
|
|
|
60988
61007
|
success: event.success,
|
|
60989
61008
|
exitCode: event.exitCode,
|
|
60990
61009
|
sha1: jobJob.sha1,
|
|
60991
|
-
|
|
60992
|
-
|
|
61010
|
+
// Just the basename. This goes into the object cache and is
|
|
61011
|
+
// replayed on every hit, so it has to still mean something
|
|
61012
|
+
// later: the /compiles/<id> directory belongs to this one
|
|
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,
|
|
60993
61017
|
stderr: j.stderr,
|
|
60994
61018
|
stdout: j.stdout
|
|
60995
61019
|
};
|
package/daemon/fisk-daemon.js
CHANGED
|
@@ -2,62 +2,30 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var crypto = require('crypto');
|
|
5
|
-
var child_process = require('child_process');
|
|
6
|
-
var require$$1 = require('fs');
|
|
7
|
-
var require$$4 = require('util');
|
|
8
|
-
var path$h = require('path');
|
|
9
5
|
var EventEmitter = require('events');
|
|
6
|
+
var require$$1 = require('fs');
|
|
10
7
|
var require$$0 = require('constants');
|
|
11
8
|
var require$$0$1 = require('stream');
|
|
9
|
+
var require$$4 = require('util');
|
|
12
10
|
var assert$1 = require('assert');
|
|
11
|
+
var path$h = require('path');
|
|
13
12
|
var os$1 = require('os');
|
|
14
13
|
var net = require('net');
|
|
15
14
|
var require$$1$1 = require('module');
|
|
16
15
|
|
|
17
16
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
18
17
|
|
|
19
|
-
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
|
|
20
|
-
var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
|
|
21
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$h);
|
|
22
18
|
var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
|
|
19
|
+
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
|
|
23
20
|
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
|
|
24
21
|
var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$1);
|
|
22
|
+
var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
|
|
25
23
|
var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert$1);
|
|
24
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$h);
|
|
26
25
|
var os__default = /*#__PURE__*/_interopDefaultLegacy(os$1);
|
|
27
26
|
var net__default = /*#__PURE__*/_interopDefaultLegacy(net);
|
|
28
27
|
var require$$1__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$1$1);
|
|
29
28
|
|
|
30
|
-
/******************************************************************************
|
|
31
|
-
Copyright (c) Microsoft Corporation.
|
|
32
|
-
|
|
33
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
34
|
-
purpose with or without fee is hereby granted.
|
|
35
|
-
|
|
36
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
37
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
38
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
39
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
40
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
41
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
42
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
43
|
-
***************************************************************************** */
|
|
44
|
-
|
|
45
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
46
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
47
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
48
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
49
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
50
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
51
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
56
|
-
var e = new Error(message);
|
|
57
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const execFileAsync = require$$4.promisify(child_process.execFile);
|
|
61
29
|
// Fingerprinting strategy:
|
|
62
30
|
//
|
|
63
31
|
// A compiler's "identity" for distributed-compile purposes is the set of
|
|
@@ -85,8 +53,17 @@ const execFileAsync = require$$4.promisify(child_process.execFile);
|
|
|
85
53
|
// macros. Those strings are frozen at compiler-build time, not install
|
|
86
54
|
// time, so they are identical across machines that installed the same
|
|
87
55
|
// compiler package.
|
|
56
|
+
//
|
|
57
|
+
// Who runs the probes:
|
|
58
|
+
//
|
|
59
|
+
// The daemon never executes a compiler. The compiler generally lives inside
|
|
60
|
+
// the client's container and its path does not resolve in the daemon's mount
|
|
61
|
+
// namespace, so the daemon cannot stat it let alone run it. Instead the daemon
|
|
62
|
+
// asks one client to run the probes and send the raw output back, and the
|
|
63
|
+
// daemon does the parsing and hashing here. Keeping canonicalisation on this
|
|
64
|
+
// side means there is exactly one implementation of it -- a second one in the
|
|
65
|
+
// client would eventually diverge and silently break client/builder matching.
|
|
88
66
|
const PROBE_TIMEOUT_MS = 10000;
|
|
89
|
-
const PROBE_MAX_BUFFER = 4 * 1024 * 1024;
|
|
90
67
|
const PROBES = [
|
|
91
68
|
{ label: "dumpmachine", args: ["-dumpmachine"], required: true },
|
|
92
69
|
{ label: "dumpversion", args: ["-dumpversion"], required: true },
|
|
@@ -94,23 +71,6 @@ const PROBES = [
|
|
|
94
71
|
{ label: "builtins-c", args: ["-x", "c", "-E", "-dM", "/dev/null"], required: true },
|
|
95
72
|
{ label: "builtins-cxx", args: ["-x", "c++", "-E", "-dM", "/dev/null"], required: true }
|
|
96
73
|
];
|
|
97
|
-
function runProbe(exec, probe) {
|
|
98
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
-
try {
|
|
100
|
-
const { stdout, stderr } = yield execFileAsync(exec, [...probe.args], {
|
|
101
|
-
timeout: PROBE_TIMEOUT_MS,
|
|
102
|
-
maxBuffer: PROBE_MAX_BUFFER
|
|
103
|
-
});
|
|
104
|
-
return `${stdout}${stderr}`;
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
if (probe.required) {
|
|
108
|
-
throw new Error(`Probe '${probe.label}' failed for ${exec}: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
74
|
// Emulate the C++ sscanf cascade "%d.%d.%d" -> "%d.%d" -> "%d".
|
|
115
75
|
function parseVersion(text) {
|
|
116
76
|
const three = /^(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
@@ -182,19 +142,26 @@ function versionFromMacros(macros, type) {
|
|
|
182
142
|
}
|
|
183
143
|
return { major: 0, minor: 0, patch: 0 };
|
|
184
144
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
145
|
+
// Turn a client's reported results into the shape the fingerprint wants,
|
|
146
|
+
// failing if a required probe is missing. A client that reports nothing for a
|
|
147
|
+
// required probe is telling us it could not identify the compiler, which must
|
|
148
|
+
// not silently become a fingerprint of empty strings -- every such compiler
|
|
149
|
+
// would hash the same.
|
|
150
|
+
function toProbeOutputs(results) {
|
|
151
|
+
for (const probe of PROBES) {
|
|
152
|
+
if (probe.required && !results[probe.label]) {
|
|
153
|
+
throw new Error(`Required probe '${probe.label}' produced no output`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const value = (label) => { var _a; return (_a = results[label]) !== null && _a !== void 0 ? _a : ""; };
|
|
157
|
+
const full = results.dumpfullversion;
|
|
158
|
+
return {
|
|
159
|
+
dumpmachine: value("dumpmachine").trim(),
|
|
160
|
+
dumpversion: value("dumpversion").trim(),
|
|
161
|
+
dumpfullversion: full ? full.trim() : null,
|
|
162
|
+
builtinsC: value("builtins-c"),
|
|
163
|
+
builtinsCxx: value("builtins-cxx")
|
|
164
|
+
};
|
|
198
165
|
}
|
|
199
166
|
// Build the canonical fingerprint blob whose SHA becomes the compiler hash.
|
|
200
167
|
// Fields are separated by NUL to avoid ambiguity if any probe output
|
|
@@ -218,76 +185,163 @@ function canonicalFingerprint(p) {
|
|
|
218
185
|
];
|
|
219
186
|
return Buffer.from(parts.join("\0"), "utf8");
|
|
220
187
|
}
|
|
221
|
-
function createCompilerInfo(
|
|
188
|
+
function createCompilerInfo(results) {
|
|
222
189
|
var _a;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
190
|
+
const probes = toProbeOutputs(results);
|
|
191
|
+
const type = detectTypeFromMacros(probes.builtinsC);
|
|
192
|
+
const versionFromMac = versionFromMacros(probes.builtinsC, type);
|
|
193
|
+
const version = versionFromMac.major !== 0
|
|
194
|
+
? versionFromMac
|
|
195
|
+
: parseVersion((_a = probes.dumpfullversion) !== null && _a !== void 0 ? _a : probes.dumpversion);
|
|
196
|
+
const blob = canonicalFingerprint(probes);
|
|
197
|
+
const hash = crypto.createHash("sha1").update(blob).digest("hex").toUpperCase();
|
|
198
|
+
// `input` is retained for debug/traceability: it lets a human see what
|
|
199
|
+
// went into the hash without needing to re-probe the compiler. Keep it
|
|
200
|
+
// small: just the identifying strings, not the full macro dumps.
|
|
201
|
+
const input = [
|
|
202
|
+
`type=${type}`,
|
|
203
|
+
`target=${probes.dumpmachine}`,
|
|
204
|
+
`version=${version.major}.${version.minor}.${version.patch}`,
|
|
205
|
+
`dumpversion=${probes.dumpversion}`,
|
|
206
|
+
probes.dumpfullversion ? `dumpfullversion=${probes.dumpfullversion}` : ""
|
|
207
|
+
]
|
|
208
|
+
.filter((s) => s.length > 0)
|
|
209
|
+
.join("\n");
|
|
210
|
+
return { hash, input, type, version };
|
|
211
|
+
}
|
|
212
|
+
// A client-supplied key must not be trusted to be small: it lands in a Map
|
|
213
|
+
// that lives as long as the daemon.
|
|
214
|
+
const MAX_KEY_LENGTH = 256;
|
|
215
|
+
function clearTimer(entry) {
|
|
216
|
+
if (entry.timer) {
|
|
217
|
+
clearTimeout(entry.timer);
|
|
218
|
+
entry.timer = undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Caches compiler fingerprints, obtaining them from clients rather than by
|
|
222
|
+
// running anything.
|
|
223
|
+
//
|
|
224
|
+
// The key is opaque here and comes from the client -- it identifies "the same
|
|
225
|
+
// compiler file" well enough to decide whether to re-probe. It deliberately is
|
|
226
|
+
// not the fingerprint: we need something cheap to compute *before* probing.
|
|
227
|
+
//
|
|
228
|
+
// Only one client is asked per key. Everyone else waits on the same answer,
|
|
229
|
+
// which is what keeps a cold parallel build from probing the same compiler
|
|
230
|
+
// once per job. Callers get a promise, so the daemon's existing "await the
|
|
231
|
+
// info, then hand back a slot" flow already holds those clients' slots for
|
|
232
|
+
// the duration without any extra slot bookkeeping.
|
|
233
|
+
class CompilerInfoStore {
|
|
234
|
+
constructor(timeoutMs = PROBE_TIMEOUT_MS * 2, log = () => {
|
|
235
|
+
/* quiet by default */
|
|
236
|
+
}) {
|
|
237
|
+
this.timeoutMs = timeoutMs;
|
|
238
|
+
this.log = log;
|
|
249
239
|
this.cache = new Map();
|
|
250
240
|
this.pending = new Map();
|
|
251
241
|
}
|
|
252
|
-
get(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
242
|
+
get(key, requester) {
|
|
243
|
+
if (typeof key !== "string" || key.length === 0 || key.length > MAX_KEY_LENGTH) {
|
|
244
|
+
return Promise.reject(new Error("compiler key must be a non-empty string of sane length"));
|
|
245
|
+
}
|
|
246
|
+
const cached = this.cache.get(key);
|
|
247
|
+
if (cached) {
|
|
248
|
+
return Promise.resolve(cached);
|
|
249
|
+
}
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
let entry = this.pending.get(key);
|
|
252
|
+
if (!entry) {
|
|
253
|
+
entry = { waiters: [], triedIds: new Set() };
|
|
254
|
+
this.pending.set(key, entry);
|
|
265
255
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
entry.waiters.push({ requester, resolve, reject });
|
|
257
|
+
// Someone is already probing this compiler; just wait for them.
|
|
258
|
+
if (entry.electedId === undefined) {
|
|
259
|
+
this.elect(key, entry);
|
|
269
260
|
}
|
|
270
|
-
const compute = CompilerInfoCache.compute(absPath).then((info) => {
|
|
271
|
-
this.cache.set(key, info);
|
|
272
|
-
return info;
|
|
273
|
-
});
|
|
274
|
-
this.pending.set(key, compute);
|
|
275
|
-
// Clean up the pending map on both success and failure so a failed
|
|
276
|
-
// lookup doesn't wedge the key forever.
|
|
277
|
-
compute
|
|
278
|
-
.finally(() => {
|
|
279
|
-
this.pending.delete(key);
|
|
280
|
-
})
|
|
281
|
-
.catch(() => {
|
|
282
|
-
/* rejection observed by caller via the returned promise */
|
|
283
|
-
});
|
|
284
|
-
return compute;
|
|
285
261
|
});
|
|
286
262
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
263
|
+
// The elected client reported probe output.
|
|
264
|
+
provide(key, results) {
|
|
265
|
+
const entry = this.pending.get(key);
|
|
266
|
+
let info;
|
|
267
|
+
try {
|
|
268
|
+
info = createCompilerInfo(results);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
272
|
+
this.log("compilerInfo for", key, "was unusable:", message);
|
|
273
|
+
if (entry) {
|
|
274
|
+
this.reelect(key, entry, message);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.cache.set(key, info);
|
|
279
|
+
if (!entry) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.finish(key, entry);
|
|
283
|
+
for (const waiter of entry.waiters) {
|
|
284
|
+
waiter.resolve(info);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// The elected client could not probe the compiler.
|
|
288
|
+
fail(key, error) {
|
|
289
|
+
const entry = this.pending.get(key);
|
|
290
|
+
if (entry) {
|
|
291
|
+
this.reelect(key, entry, error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// A client went away. If it owed us an answer, ask someone else.
|
|
295
|
+
clientGone(requester) {
|
|
296
|
+
for (const [key, entry] of this.pending) {
|
|
297
|
+
entry.waiters = entry.waiters.filter((w) => w.requester.id !== requester.id);
|
|
298
|
+
if (entry.electedId === requester.id) {
|
|
299
|
+
this.reelect(key, entry, "client disconnected before reporting compiler info");
|
|
300
|
+
}
|
|
301
|
+
else if (!entry.waiters.length) {
|
|
302
|
+
this.finish(key, entry);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
elect(key, entry) {
|
|
307
|
+
var _a, _b;
|
|
308
|
+
const next = entry.waiters.find((w) => !entry.triedIds.has(w.requester.id));
|
|
309
|
+
if (!next) {
|
|
310
|
+
// Nobody left who has not already failed us.
|
|
311
|
+
const waiters = entry.waiters;
|
|
312
|
+
this.finish(key, entry);
|
|
313
|
+
const err = new Error("no client could provide compiler info");
|
|
314
|
+
for (const waiter of waiters) {
|
|
315
|
+
waiter.reject(err);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
entry.electedId = next.requester.id;
|
|
320
|
+
entry.triedIds.add(next.requester.id);
|
|
321
|
+
entry.timer = setTimeout(() => {
|
|
322
|
+
this.log("compilerInfo probe timed out for", key, "client", next.requester.id);
|
|
323
|
+
this.reelect(key, entry, "timed out waiting for compiler info");
|
|
324
|
+
}, this.timeoutMs);
|
|
325
|
+
// Do not let a pending probe hold the process open.
|
|
326
|
+
(_b = (_a = entry.timer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
327
|
+
this.log("asking client", next.requester.id, "to probe compiler", key);
|
|
328
|
+
try {
|
|
329
|
+
next.requester.requestCompilerInfo(key, PROBES, PROBE_TIMEOUT_MS);
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
this.log("failed to ask client", next.requester.id, err);
|
|
333
|
+
this.reelect(key, entry, "could not ask client to probe");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
reelect(key, entry, why) {
|
|
337
|
+
this.log("re-electing for", key, "-", why);
|
|
338
|
+
clearTimer(entry);
|
|
339
|
+
entry.electedId = undefined;
|
|
340
|
+
this.elect(key, entry);
|
|
341
|
+
}
|
|
342
|
+
finish(key, entry) {
|
|
343
|
+
clearTimer(entry);
|
|
344
|
+
this.pending.delete(key);
|
|
291
345
|
}
|
|
292
346
|
}
|
|
293
347
|
|
|
@@ -3762,7 +3816,10 @@ class Slots extends EventEmitter__default["default"] {
|
|
|
3762
3816
|
}
|
|
3763
3817
|
|
|
3764
3818
|
const Version = 5;
|
|
3765
|
-
|
|
3819
|
+
// 6: objects now carry the client's real source path and compilation dir,
|
|
3820
|
+
// baked in at compile time instead of the builder's /compiles paths, and the
|
|
3821
|
+
// stored response no longer keeps the paths the client used to patch with.
|
|
3822
|
+
const ObjectCacheFormatVersion = 6;
|
|
3766
3823
|
function cacheDir(option) {
|
|
3767
3824
|
let dir = option("cache-dir");
|
|
3768
3825
|
if (!dir) {
|
|
@@ -3816,9 +3873,16 @@ function validateObjectCache(option) {
|
|
|
3816
3873
|
buf.writeUInt32BE(ObjectCacheFormatVersion);
|
|
3817
3874
|
fs.writeFileSync(file, buf);
|
|
3818
3875
|
}
|
|
3819
|
-
|
|
3876
|
+
// Only the builder keeps an object cache on disk. The scheduler tracks which
|
|
3877
|
+
// builder holds which sha1 in memory, and the daemon uses cacheDir purely for
|
|
3878
|
+
// the default socket path -- validating an object cache for either created a
|
|
3879
|
+
// directory they never read and, on a format bump, tried to destroy one they do
|
|
3880
|
+
// not necessarily own.
|
|
3881
|
+
function common$1(option, hasObjectCache = false) {
|
|
3820
3882
|
validateCache(option);
|
|
3821
|
-
|
|
3883
|
+
if (hasObjectCache) {
|
|
3884
|
+
validateObjectCache(option);
|
|
3885
|
+
}
|
|
3822
3886
|
return {
|
|
3823
3887
|
cacheDir: cacheDir.bind(undefined, option),
|
|
3824
3888
|
Version,
|
|
@@ -4536,7 +4600,9 @@ const localSlotCount = option.int("local-slots", 0);
|
|
|
4536
4600
|
const localSlots = new Slots(localSlotCount, "local", debug);
|
|
4537
4601
|
const localSlotsMaxLoad = option("local-slots-max-load") || 0;
|
|
4538
4602
|
console.log(`cpp slots: ${cppSlots.capacity}, compile slots: ${compileSlots.capacity}, local slots: ${localSlots.capacity}, local max load: ${localSlotsMaxLoad}`);
|
|
4539
|
-
const
|
|
4603
|
+
const compilerInfoStore = new CompilerInfoStore(undefined, (...args) => {
|
|
4604
|
+
console.log("compilerInfo:", ...args);
|
|
4605
|
+
});
|
|
4540
4606
|
const slotSubscribers = [];
|
|
4541
4607
|
function slotsInfo() {
|
|
4542
4608
|
return {
|
|
@@ -4659,18 +4725,27 @@ server.on("compile", (compile) => {
|
|
|
4659
4725
|
compileSlots.release(compile.id);
|
|
4660
4726
|
}
|
|
4661
4727
|
});
|
|
4728
|
+
// The daemon cannot see, let alone run, the compiler: it usually lives in
|
|
4729
|
+
// the client's container. Clients identify it with a key they compute
|
|
4730
|
+
// themselves and run the probes on our behalf when asked.
|
|
4731
|
+
const requester = {
|
|
4732
|
+
id: compile.id,
|
|
4733
|
+
requestCompilerInfo(key, probes, timeoutMs) {
|
|
4734
|
+
compile.send({ type: "compilerInfoRequest", key, probes, timeoutMs });
|
|
4735
|
+
}
|
|
4736
|
+
};
|
|
4662
4737
|
compile.on("acquireSlot", (msg) => {
|
|
4663
4738
|
console.log("acquireSlot", msg);
|
|
4664
|
-
const
|
|
4665
|
-
const infoResult =
|
|
4666
|
-
?
|
|
4739
|
+
const compilerKey = msg && typeof msg.compilerKey === "string" && msg.compilerKey.length > 0 ? msg.compilerKey : null;
|
|
4740
|
+
const infoResult = compilerKey
|
|
4741
|
+
? compilerInfoStore.get(compilerKey, requester).then((info) => ({ info, error: null }), (err) => {
|
|
4667
4742
|
const message = err instanceof Error ? err.message : String(err);
|
|
4668
4743
|
if (debug) {
|
|
4669
|
-
console.log("acquireSlot ->
|
|
4744
|
+
console.log("acquireSlot -> compilerInfoStore failed", compilerKey, message);
|
|
4670
4745
|
}
|
|
4671
4746
|
return { info: null, error: message };
|
|
4672
4747
|
})
|
|
4673
|
-
: Promise.resolve({ info: null, error: "acquireSlot missing compiler
|
|
4748
|
+
: Promise.resolve({ info: null, error: "acquireSlot missing compiler key" });
|
|
4674
4749
|
infoResult
|
|
4675
4750
|
.then(({ info, error }) => {
|
|
4676
4751
|
if (compileClosed) {
|
|
@@ -4710,6 +4785,22 @@ server.on("compile", (compile) => {
|
|
|
4710
4785
|
console.error("acquireSlot handler failed unexpectedly", err);
|
|
4711
4786
|
});
|
|
4712
4787
|
});
|
|
4788
|
+
compile.on("compilerInfoResponse", (msg) => {
|
|
4789
|
+
const key = msg && typeof msg.key === "string" ? msg.key : "";
|
|
4790
|
+
if (!key) {
|
|
4791
|
+
console.error("compilerInfoResponse without a key from", compile.id);
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
if (typeof (msg === null || msg === void 0 ? void 0 : msg.error) === "string" && msg.error.length) {
|
|
4795
|
+
compilerInfoStore.fail(key, msg.error);
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
4798
|
+
if (!(msg === null || msg === void 0 ? void 0 : msg.results) || typeof msg.results !== "object") {
|
|
4799
|
+
compilerInfoStore.fail(key, "compilerInfoResponse without results");
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
compilerInfoStore.provide(key, msg.results);
|
|
4803
|
+
});
|
|
4713
4804
|
compile.on("releaseLocalSlot", () => {
|
|
4714
4805
|
if (debug) {
|
|
4715
4806
|
console.log("releaseLocalSlot");
|
|
@@ -4725,6 +4816,8 @@ server.on("compile", (compile) => {
|
|
|
4725
4816
|
console.error("Got error from fiskc", compile.id, compile.pid, err);
|
|
4726
4817
|
}
|
|
4727
4818
|
compileClosed = true;
|
|
4819
|
+
// If this client owed us compiler info, hand the job to another waiter.
|
|
4820
|
+
compilerInfoStore.clientGone(requester);
|
|
4728
4821
|
if (requestedCppSlot) {
|
|
4729
4822
|
requestedCppSlot = false;
|
|
4730
4823
|
cppSlots.release(compile.id);
|
|
@@ -4743,6 +4836,8 @@ server.on("compile", (compile) => {
|
|
|
4743
4836
|
console.log("got end from", compile.id, compile.pid);
|
|
4744
4837
|
}
|
|
4745
4838
|
compileClosed = true;
|
|
4839
|
+
// If this client owed us compiler info, hand the job to another waiter.
|
|
4840
|
+
compilerInfoStore.clientGone(requester);
|
|
4746
4841
|
if (requestedCppSlot) {
|
|
4747
4842
|
requestedCppSlot = false;
|
|
4748
4843
|
cppSlots.release(compile.id);
|
package/package.json
CHANGED
|
@@ -56152,6 +56152,9 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56152
56152
|
ws.close();
|
|
56153
56153
|
return;
|
|
56154
56154
|
}
|
|
56155
|
+
// Exact match: this is a wire format, not a compatibility surface. A peer
|
|
56156
|
+
// on a different version is not something to negotiate with, and NaN from
|
|
56157
|
+
// a missing or unparseable header fails this comparison too.
|
|
56155
56158
|
const configVersion = parseInt(header(req, "x-fisk-config-version") || "");
|
|
56156
56159
|
if (configVersion !== this.configVersion) {
|
|
56157
56160
|
ws.send(`{"error": "Bad config version, expected ${this.configVersion}, got ${configVersion}"}`);
|
|
@@ -56418,7 +56421,10 @@ class Server extends require$$0__default$2["default"] {
|
|
|
56418
56421
|
}
|
|
56419
56422
|
|
|
56420
56423
|
const Version = 5;
|
|
56421
|
-
|
|
56424
|
+
// 6: objects now carry the client's real source path and compilation dir,
|
|
56425
|
+
// baked in at compile time instead of the builder's /compiles paths, and the
|
|
56426
|
+
// stored response no longer keeps the paths the client used to patch with.
|
|
56427
|
+
const ObjectCacheFormatVersion = 6;
|
|
56422
56428
|
function cacheDir(option) {
|
|
56423
56429
|
let dir = option("cache-dir");
|
|
56424
56430
|
if (!dir) {
|
|
@@ -56472,9 +56478,16 @@ function validateObjectCache(option) {
|
|
|
56472
56478
|
buf.writeUInt32BE(ObjectCacheFormatVersion);
|
|
56473
56479
|
fs$4.writeFileSync(file, buf);
|
|
56474
56480
|
}
|
|
56475
|
-
|
|
56481
|
+
// Only the builder keeps an object cache on disk. The scheduler tracks which
|
|
56482
|
+
// builder holds which sha1 in memory, and the daemon uses cacheDir purely for
|
|
56483
|
+
// the default socket path -- validating an object cache for either created a
|
|
56484
|
+
// directory they never read and, on a format bump, tried to destroy one they do
|
|
56485
|
+
// not necessarily own.
|
|
56486
|
+
function common$1(option, hasObjectCache = false) {
|
|
56476
56487
|
validateCache(option);
|
|
56477
|
-
|
|
56488
|
+
if (hasObjectCache) {
|
|
56489
|
+
validateObjectCache(option);
|
|
56490
|
+
}
|
|
56478
56491
|
return {
|
|
56479
56492
|
cacheDir: cacheDir.bind(undefined, option),
|
|
56480
56493
|
Version,
|
|
@@ -57998,7 +58011,32 @@ const option = options({
|
|
|
57998
58011
|
const common = common$1(option);
|
|
57999
58012
|
let nextCommandId = 0;
|
|
58000
58013
|
const server = new Server(option, common.Version);
|
|
58001
|
-
|
|
58014
|
+
// 5.0.14 is the first client that folds its debug paths into the object cache
|
|
58015
|
+
// key and asks the builder to bake those paths in at compile time. An older
|
|
58016
|
+
// client produces objects carrying the builder's /compiles paths -- unpatchable
|
|
58017
|
+
// for LTO bitcode -- and, because it does not key on those paths, stores them
|
|
58018
|
+
// under a key up-to-date clients also use. One stale client therefore poisons
|
|
58019
|
+
// everyone's backtraces, so it is refused rather than allowed to contribute.
|
|
58020
|
+
const clientMinimumVersion = "5.0.14";
|
|
58021
|
+
// compareVersions throws on an empty or malformed version rather than returning
|
|
58022
|
+
// an ordering, and npmVersion is "" for any client that sends no
|
|
58023
|
+
// x-fisk-npm-version header. Thrown out of the compile handler that would be a
|
|
58024
|
+
// silent hang: uncaughtException keeps the scheduler up, but the request is
|
|
58025
|
+
// abandoned half-handled, so the client waits for a reply that never comes
|
|
58026
|
+
// instead of being told to update. A version we cannot read is not >= the
|
|
58027
|
+
// minimum, so treat it as too old and reject it properly.
|
|
58028
|
+
function clientTooOld(npmVersion) {
|
|
58029
|
+
if (!npmVersion) {
|
|
58030
|
+
return true;
|
|
58031
|
+
}
|
|
58032
|
+
try {
|
|
58033
|
+
return compareVersions(clientMinimumVersion, npmVersion) >= 1;
|
|
58034
|
+
}
|
|
58035
|
+
catch (err) {
|
|
58036
|
+
console.error(`Unparseable client npm version: "${npmVersion}"`, err);
|
|
58037
|
+
return true;
|
|
58038
|
+
}
|
|
58039
|
+
}
|
|
58002
58040
|
const serverStartTime = Date.now();
|
|
58003
58041
|
// A stalled event loop stops calling accept(), the listen backlog fills and the
|
|
58004
58042
|
// kernel then silently drops SYNs, which clients see as a connect timeout rather
|
|
@@ -58848,7 +58886,7 @@ function requestEnvironment(compile) {
|
|
|
58848
58886
|
return true;
|
|
58849
58887
|
}
|
|
58850
58888
|
server.on("clientVerify", (clientVerify) => {
|
|
58851
|
-
if (
|
|
58889
|
+
if (clientTooOld(clientVerify.npmVersion)) {
|
|
58852
58890
|
clientVerify.send("version_mismatch", { minimum_version: `${clientMinimumVersion}` });
|
|
58853
58891
|
}
|
|
58854
58892
|
else {
|
|
@@ -58859,7 +58897,7 @@ server.on("compile", (compile) => {
|
|
|
58859
58897
|
compile.on("log", (event) => {
|
|
58860
58898
|
addLogFile({ source: "client", ip: compile.ip, contents: event.message });
|
|
58861
58899
|
});
|
|
58862
|
-
if (
|
|
58900
|
+
if (clientTooOld(compile.npmVersion)) {
|
|
58863
58901
|
++jobsFailed;
|
|
58864
58902
|
compile.send("version_mismatch", { minimum_version: `${clientMinimumVersion}` });
|
|
58865
58903
|
return;
|