@andersbakken/fisk 5.0.7 → 5.0.11

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.
@@ -3027,8 +3027,20 @@ var output = {
3027
3027
 
3028
3028
  var fs = libExports;
3029
3029
 
3030
+ // Padded canonical prefixes for DWARF path patching. The fisk client scans
3031
+ // compiled objects for these byte patterns and overwrites them in-place with
3032
+ // the real paths. Because the replacement is always shorter, a NUL terminator
3033
+ // followed by NUL fill fits inside the original region. This works on both ELF
3034
+ // objects and LTO bitcode.
3035
+ //
3036
+ // The length is PATH_MAX so any path the client could legally hand us fits
3037
+ // without truncation.
3038
+ // Keep in sync with FISK_PAD_LENGTH in src/client/FiskPathPatcher.cpp.
3039
+ const FISK_PAD_LENGTH = 4096;
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
3042
  class Compile extends EventEmitter__default["default"] {
3031
- constructor(args, argv0, dir, debug) {
3043
+ constructor(args, argv0, dir, debug, sourceFileName, paddedPaths) {
3032
3044
  super();
3033
3045
  if (!args || !args.length || !dir || !argv0) {
3034
3046
  console.error(argv0, args, dir);
@@ -3141,7 +3153,10 @@ class Compile extends EventEmitter__default["default"] {
3141
3153
  throw new Error("More than one source file");
3142
3154
  }
3143
3155
  sourcePath = args[i];
3144
- args[i] = path__default["default"].join(dir, "sourcefile");
3156
+ if (!sourceFileName) {
3157
+ sourceFileName = path__default["default"].basename(sourcePath);
3158
+ }
3159
+ args[i] = path__default["default"].join(dir, sourceFileName);
3145
3160
  }
3146
3161
  break;
3147
3162
  }
@@ -3149,6 +3164,27 @@ class Compile extends EventEmitter__default["default"] {
3149
3164
  if (!sourcePath) {
3150
3165
  throw new Error("No sourcefile");
3151
3166
  }
3167
+ const sourceFileInDir = path__default["default"].join(dir, sourceFileName || path__default["default"].basename(sourcePath));
3168
+ if (paddedPaths) {
3169
+ // -grecord-command-line embeds our own argv verbatim into
3170
+ // DW_AT_producer, which would (a) add two PATH_MAX pads to every CU
3171
+ // and (b) leave the builder's /compiles path in the producer, where
3172
+ // the client's byte scan would hit the embedded pad and NUL-truncate
3173
+ // the rest of the recorded command line. The recorded line would be
3174
+ // the builder's rewritten argv anyway -- not the client's -- so drop
3175
+ // it rather than record something both wrong and mangled.
3176
+ for (let i = args.length - 1; i >= 0; --i) {
3177
+ if (args[i] === "-grecord-command-line" || args[i] === "-frecord-command-line") {
3178
+ args.splice(i, 1);
3179
+ }
3180
+ }
3181
+ args.push("-gno-record-command-line");
3182
+ // The more specific source-file rule must come last: both clang and
3183
+ // gcc let a later -fdebug-prefix-map win over an earlier one, and
3184
+ // the directory rule is a prefix of the source-file rule.
3185
+ args.push(`-fdebug-prefix-map=${dir}=${FISK_CDIR_PAD}`);
3186
+ args.push(`-fdebug-prefix-map=${sourceFileInDir}=${FISK_NAME_PAD}`);
3187
+ }
3152
3188
  if (!hasDashX) {
3153
3189
  switch (path__default["default"].extname(sourcePath)) {
3154
3190
  case ".C":
@@ -3216,7 +3252,14 @@ class Compile extends EventEmitter__default["default"] {
3216
3252
  if (!fs.existsSync("/usr/bin/as")) {
3217
3253
  this.emit("stderr", "as doesn't exist");
3218
3254
  }
3219
- console.log(`Compiling source file: ${sourcePath}\n${[compiler, ...args].join(" ")}`);
3255
+ console.log(`Compiling source file: ${sourcePath}\n${[compiler, ...args]
3256
+ .map((x) => {
3257
+ if (x.startsWith("-fdebug-prefix-map=")) {
3258
+ x = x.replace(/_+$/, "___");
3259
+ }
3260
+ return x;
3261
+ })
3262
+ .join(" ")}`);
3220
3263
  // const env = Object.assign({ TMPDIR: dir, TEMPDIR: dir, TEMP: dir }, process.env);
3221
3264
  const proc = child_process__default["default"].spawn(compiler, args, {
3222
3265
  /*env: env, */ cwd: dir // , maxBuffer: 1024 * 1024 * 16
@@ -3239,8 +3282,9 @@ class Compile extends EventEmitter__default["default"] {
3239
3282
  let addDirError;
3240
3283
  const addDir = (directory, prefix) => {
3241
3284
  try {
3285
+ const sourceBaseName = sourceFileName || path__default["default"].basename(sourcePath);
3242
3286
  fs.readdirSync(directory).forEach((file) => {
3243
- if (file === "sourcefile") {
3287
+ if (file === sourceBaseName) {
3244
3288
  return;
3245
3289
  }
3246
3290
  try {
@@ -3727,7 +3771,7 @@ process.on("message", (msg) => {
3727
3771
  if (argv.debug) {
3728
3772
  console.log("Creating new compile", msg.commandLine, msg.argv0, msg.dir);
3729
3773
  }
3730
- const compile = new Compile(msg.commandLine, msg.argv0, msg.dir, argv.debug);
3774
+ const compile = new Compile(msg.commandLine, msg.argv0, msg.dir, argv.debug, msg.sourceFileName, msg.paddedPaths);
3731
3775
  // console.log("running thing", msg.commandLine);
3732
3776
  compile.on("stdout", (data) => {
3733
3777
  send({ type: "compileStdOut", id: msg.id, data: data });
@@ -8171,6 +8171,7 @@ 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;
8174
8175
  this.supportsCompressedResponse = data.supportsCompressedResponse;
8175
8176
  }
8176
8177
  get readyState() {
@@ -54954,6 +54955,7 @@ class Server extends EventEmitter__default["default"] {
54954
54955
  sourcePath: String(req.headers["x-fisk-sourcefile"]),
54955
54956
  user: String(req.headers["x-fisk-user"]),
54956
54957
  supportsCompressedResponse: req.headers["x-fisk-supports-compressed-response"] === "true",
54958
+ paddedPaths: req.headers["x-fisk-padded-paths"] === "true",
54957
54959
  ws
54958
54960
  });
54959
54961
  break;
@@ -55049,16 +55051,18 @@ class Server extends EventEmitter__default["default"] {
55049
55051
  }
55050
55052
 
55051
55053
  class CompileJob extends EventEmitter__default["default"] {
55052
- constructor(commandLine, argv0, id, vm) {
55054
+ constructor(commandLine, argv0, id, vm, sourcePath, paddedPaths) {
55053
55055
  super();
55054
55056
  this.commandLine = commandLine;
55055
55057
  this.argv0 = argv0;
55056
55058
  this.id = id;
55057
55059
  this.vm = vm;
55060
+ this.paddedPaths = paddedPaths;
55058
55061
  this.dir = path__default["default"].join(vm.root, "compiles", String(this.id));
55059
55062
  this.vmDir = path__default["default"].join("/", "compiles", String(this.id));
55063
+ this.sourceFileName = sourcePath ? path__default["default"].basename(sourcePath) : "sourcefile";
55060
55064
  fs$3.mkdirpSync(this.dir);
55061
- this.fd = fs$3.openSync(path__default["default"].join(this.dir, "sourcefile"), "w");
55065
+ this.fd = fs$3.openSync(path__default["default"].join(this.dir, this.sourceFileName), "w");
55062
55066
  this.cppSize = 0;
55063
55067
  this.startCompile = undefined;
55064
55068
  }
@@ -55084,7 +55088,7 @@ class CompileJob extends EventEmitter__default["default"] {
55084
55088
  this.startCompile = Date.now();
55085
55089
  fs$3.closeSync(this.fd);
55086
55090
  this.fd = undefined;
55087
- this.vm.child.send({ type: "compile", commandLine: this.commandLine, argv0: this.argv0, id: this.id, dir: this.vmDir }, this.sendCallback.bind(this));
55091
+ this.vm.child.send({ type: "compile", commandLine: this.commandLine, argv0: this.argv0, id: this.id, dir: this.vmDir, sourceFileName: this.sourceFileName, paddedPaths: this.paddedPaths }, this.sendCallback.bind(this));
55088
55092
  }
55089
55093
  cancel() {
55090
55094
  this.vm.child.send({ type: "cancel", id: this.id }, this.sendCallback.bind(this));
@@ -55195,8 +55199,8 @@ class VM extends EventEmitter__default["default"] {
55195
55199
  }
55196
55200
  });
55197
55201
  }
55198
- startCompile(commandLine, argv0, id) {
55199
- const compile = new CompileJob(commandLine, argv0, id, this);
55202
+ startCompile(commandLine, argv0, id, sourcePath, paddedPaths) {
55203
+ const compile = new CompileJob(commandLine, argv0, id, this, sourcePath, paddedPaths);
55200
55204
  this.compiles[compile.id] = compile;
55201
55205
  // console.log("startCompile " + compile.id);
55202
55206
  return compile;
@@ -55207,7 +55211,7 @@ class VM extends EventEmitter__default["default"] {
55207
55211
  }
55208
55212
 
55209
55213
  const Version = 5;
55210
- const ObjectCacheFormatVersion = 2;
55214
+ const ObjectCacheFormatVersion = 5;
55211
55215
  function cacheDir(option) {
55212
55216
  let dir = option("cache-dir");
55213
55217
  if (!dir) {
@@ -60918,7 +60922,7 @@ server.on("job", (job) => {
60918
60922
  console.log("Starting job", j.id, jobJob.sourcePath, "for", jobJob.ip, jobJob.name, "wait", jobJob.wait);
60919
60923
  assert__default["default"](jobJob.commandLine, "Must have commandLine");
60920
60924
  assert__default["default"](jobJob.argv0, "Must have argv0");
60921
- j.op = vm.startCompile(jobJob.commandLine, jobJob.argv0, jobJob.id);
60925
+ j.op = vm.startCompile(jobJob.commandLine, jobJob.argv0, jobJob.id, jobJob.sourcePath, jobJob.paddedPaths);
60922
60926
  if (j.buffer) {
60923
60927
  j.op.feed(j.buffer);
60924
60928
  j.buffer = undefined;
@@ -60984,6 +60988,8 @@ server.on("job", (job) => {
60984
60988
  success: event.success,
60985
60989
  exitCode: event.exitCode,
60986
60990
  sha1: jobJob.sha1,
60991
+ sourcePath: path__default["default"].join(j.op.vmDir, j.op.sourceFileName),
60992
+ originalSourcePath: jobJob.sourcePath,
60987
60993
  stderr: j.stderr,
60988
60994
  stdout: j.stdout
60989
60995
  };
@@ -61008,7 +61014,6 @@ server.on("job", (job) => {
61008
61014
  uncompressedSize: item.uncompressed.byteLength
61009
61015
  };
61010
61016
  }) });
61011
- cacheResponse.sourcePath = jobJob.sourcePath;
61012
61017
  cacheResponse.commandLine = jobJob.commandLine;
61013
61018
  cacheResponse.environment = jobJob.hash;
61014
61019
  objectCache.add(cacheResponse, contents);
@@ -3762,7 +3762,7 @@ class Slots extends EventEmitter__default["default"] {
3762
3762
  }
3763
3763
 
3764
3764
  const Version = 5;
3765
- const ObjectCacheFormatVersion = 2;
3765
+ const ObjectCacheFormatVersion = 5;
3766
3766
  function cacheDir(option) {
3767
3767
  let dir = option("cache-dir");
3768
3768
  if (!dir) {
@@ -4511,7 +4511,7 @@ const option = createOptions({
4511
4511
  const common = common$1(option);
4512
4512
  const debug = option("debug");
4513
4513
  process.on("unhandledRejection", (reason, p) => {
4514
- console.log("Unhandled Rejection at: Promise", p, "reason:", reason === null || reason === void 0 ? void 0 : reason.stack);
4514
+ console.error("Unhandled Rejection at: Promise", p, "reason:", reason === null || reason === void 0 ? void 0 : reason.stack);
4515
4515
  process.exit();
4516
4516
  // if (client)
4517
4517
  // client.send('log', { message: `Unhandled Rejection at: Promise ${p}, reason: ${reason.stack}` });
@@ -4535,6 +4535,7 @@ const compileSlots = new Slots(option.int("slots", Math.max(os__default["default
4535
4535
  const localSlotCount = option.int("local-slots", 0);
4536
4536
  const localSlots = new Slots(localSlotCount, "local", debug);
4537
4537
  const localSlotsMaxLoad = option("local-slots-max-load") || 0;
4538
+ console.log(`cpp slots: ${cppSlots.capacity}, compile slots: ${compileSlots.capacity}, local slots: ${localSlots.capacity}, local max load: ${localSlotsMaxLoad}`);
4538
4539
  const compilerInfoCache = new CompilerInfoCache();
4539
4540
  const slotSubscribers = [];
4540
4541
  function slotsInfo() {
@@ -4659,9 +4660,7 @@ server.on("compile", (compile) => {
4659
4660
  }
4660
4661
  });
4661
4662
  compile.on("acquireSlot", (msg) => {
4662
- if (debug) {
4663
- console.log("acquireSlot", msg);
4664
- }
4663
+ console.log("acquireSlot", msg);
4665
4664
  const compilerPath = msg && typeof msg.compiler === "string" && msg.compiler.length > 0 ? msg.compiler : null;
4666
4665
  const infoResult = compilerPath
4667
4666
  ? compilerInfoCache.get(compilerPath).then((info) => ({ info, error: null }), (err) => {
@@ -4688,7 +4687,7 @@ server.on("compile", (compile) => {
4688
4687
  }
4689
4688
  compile.send(response);
4690
4689
  };
4691
- if (canAcquireLocalSlot() && localSlots.tryAcquire(compile.id, { pid: compile.pid })) {
4690
+ if (!(msg === null || msg === void 0 ? void 0 : msg["no-local"]) && canAcquireLocalSlot() && localSlots.tryAcquire(compile.id, { pid: compile.pid })) {
4692
4691
  if (debug) {
4693
4692
  console.log("acquireSlot -> local slot granted");
4694
4693
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andersbakken/fisk",
3
- "version": "5.0.7",
3
+ "version": "5.0.11",
4
4
  "description": "Fisk, a distributed compile system",
5
5
  "scripts": {
6
6
  "lint": "eslint . --ext .ts",
@@ -56418,7 +56418,7 @@ class Server extends require$$0__default$2["default"] {
56418
56418
  }
56419
56419
 
56420
56420
  const Version = 5;
56421
- const ObjectCacheFormatVersion = 2;
56421
+ const ObjectCacheFormatVersion = 5;
56422
56422
  function cacheDir(option) {
56423
56423
  let dir = option("cache-dir");
56424
56424
  if (!dir) {
@@ -57979,6 +57979,9 @@ Options:
57979
57979
  --max-file-descriptors=N File descriptor limit
57980
57980
  --ping-interval=MS WebSocket ping interval
57981
57981
  --monitor-log=PATH Log file for monitor events
57982
+ --event-loop-lag-interval=MS Event loop lag sample interval (0 disables)
57983
+ --event-loop-lag-threshold=MS Log a stall at or above this lag
57984
+ --event-loop-lag-summary-interval=MS Lag summary interval (0 disables)
57982
57985
  --env-dir=PATH Directory for compiler environments
57983
57986
  --ui=PATH Path to scheduler UI static files
57984
57987
  --cache-dir=PATH Cache directory (default: ~/.cache/fisk/scheduler)
@@ -57997,6 +58000,41 @@ let nextCommandId = 0;
57997
58000
  const server = new Server(option, common.Version);
57998
58001
  const clientMinimumVersion = "5.0.6";
57999
58002
  const serverStartTime = Date.now();
58003
+ // A stalled event loop stops calling accept(), the listen backlog fills and the
58004
+ // kernel then silently drops SYNs, which clients see as a connect timeout rather
58005
+ // than a refusal. It leaves no trace by the time anyone runs ss(8), so record it
58006
+ // here with timestamps that can be correlated against client-side timeouts.
58007
+ const intervalMs = option.int("event-loop-lag-interval", 100);
58008
+ const thresholdMs = option.int("event-loop-lag-threshold", 250);
58009
+ const summaryIntervalMs = option.int("event-loop-lag-summary-interval", 60000);
58010
+ if (intervalMs > 0) {
58011
+ let maxLag = 0;
58012
+ let stalls = 0;
58013
+ let expected = Date.now() + intervalMs;
58014
+ const tick = () => {
58015
+ const now = Date.now();
58016
+ const lag = now - expected;
58017
+ if (lag > maxLag) {
58018
+ maxLag = lag;
58019
+ }
58020
+ if (lag >= thresholdMs) {
58021
+ ++stalls;
58022
+ console.log(`event-loop-lag stall ${lag}ms at ${new Date(now).toISOString()}`);
58023
+ }
58024
+ // Schedule off "now" rather than accumulating on expected, otherwise a
58025
+ // single long stall reports as a stall on every subsequent tick.
58026
+ expected = now + intervalMs;
58027
+ setTimeout(tick, intervalMs).unref();
58028
+ };
58029
+ setTimeout(tick, intervalMs).unref();
58030
+ if (summaryIntervalMs > 0) {
58031
+ setInterval(() => {
58032
+ console.log(`event-loop-lag summary max=${maxLag}ms stalls=${stalls} over last ${summaryIntervalMs}ms (threshold ${thresholdMs}ms)`);
58033
+ maxLag = 0;
58034
+ stalls = 0;
58035
+ }, summaryIntervalMs).unref();
58036
+ }
58037
+ }
58000
58038
  process.on("unhandledRejection", (reason, p) => {
58001
58039
  console.error("Unhandled Rejection at: Promise", p, "reason:", reason === null || reason === void 0 ? void 0 : reason.stack);
58002
58040
  addLogFile({ source: "no source file", ip: "self", contents: `reason: ${reason.stack} promise` });