@andersbakken/fisk 4.0.57 → 4.0.61

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.
@@ -3225,6 +3225,7 @@ class Compile extends EventEmitter__default["default"] {
3225
3225
  proc.on("exit", (exitCode) => {
3226
3226
  // try {
3227
3227
  const files = [];
3228
+ let addDirError;
3228
3229
  const addDir = (directory, prefix) => {
3229
3230
  try {
3230
3231
  fs.readdirSync(directory).forEach((file) => {
@@ -3241,21 +3242,21 @@ class Compile extends EventEmitter__default["default"] {
3241
3242
  if (file === outputFileName) {
3242
3243
  files.push({ path: output, mapped: path__default["default"].join(prefix, file) });
3243
3244
  }
3244
- else if (path__default["default"].extname(file) === ".gcno") {
3245
- // console.log("mapping", output, prefix, file);
3246
- files.push({
3247
- path: output.substring(0, output.length - 1) + "gcno",
3248
- mapped: path__default["default"].join(prefix, file)
3249
- });
3250
- }
3251
- else if (path__default["default"].extname(file) === ".gcda") {
3252
- files.push({
3253
- path: output.substring(0, output.length - 1) + "gcda",
3254
- mapped: path__default["default"].join(prefix, file)
3255
- });
3256
- }
3257
3245
  else {
3258
- files.push({ path: path__default["default"].join(prefix, file) });
3246
+ const ext = path__default["default"].extname(file);
3247
+ switch (ext) {
3248
+ case ".gcno":
3249
+ case ".gcda":
3250
+ case ".dwo":
3251
+ files.push({
3252
+ path: output.substring(0, output.length - 2) + ext,
3253
+ mapped: path__default["default"].join(prefix, file)
3254
+ });
3255
+ break;
3256
+ default:
3257
+ files.push({ path: path__default["default"].join(prefix, file) });
3258
+ break;
3259
+ }
3259
3260
  }
3260
3261
  if (debug) {
3261
3262
  console.log("Added file", file, files[files.length - 1]);
@@ -3269,23 +3270,26 @@ class Compile extends EventEmitter__default["default"] {
3269
3270
  }
3270
3271
  catch (err) {
3271
3272
  console.error("Got an error processing outputs for", sourcePath, err);
3272
- assert__default["default"](sourcePath !== undefined, "Must have sourcePath");
3273
- const errorExitEvent = {
3274
- exitCode: 110,
3275
- files: [],
3276
- error: err.toString(),
3277
- sourcePath
3278
- };
3279
- this.emit("exit", errorExitEvent);
3273
+ addDirError = err;
3280
3274
  }
3281
3275
  };
3282
3276
  if (exitCode === 0) {
3283
3277
  addDir(dir, dir);
3284
3278
  }
3279
+ assert__default["default"](sourcePath !== undefined, "Must have sourcePath4");
3280
+ if (addDirError) {
3281
+ const errorExitEvent = {
3282
+ exitCode: 110,
3283
+ files: [],
3284
+ error: addDirError.toString(),
3285
+ sourcePath
3286
+ };
3287
+ this.emit("exit", errorExitEvent);
3288
+ return;
3289
+ }
3285
3290
  if (exitCode === null) {
3286
3291
  exitCode = 111;
3287
3292
  }
3288
- assert__default["default"](sourcePath !== undefined, "Must have sourcePath4");
3289
3293
  const exitEvent = { exitCode, files, sourcePath };
3290
3294
  this.emit("exit", exitEvent);
3291
3295
  });
@@ -7965,7 +7965,10 @@ class ObjectCache extends EventEmitter__default["default"] {
7965
7965
  }
7966
7966
  loadFile(filePath, fileSize) {
7967
7967
  const fileName = path__default["default"].basename(filePath);
7968
- // console.log("got file", filePath, fileSize);
7968
+ // Skip if already cached — makes loadFile idempotent
7969
+ if (fileName in this.cache) {
7970
+ return;
7971
+ }
7969
7972
  let fd;
7970
7973
  let jsonBuffer;
7971
7974
  try {
@@ -7993,6 +7996,9 @@ class ObjectCache extends EventEmitter__default["default"] {
7993
7996
  this.size += item.fileSize;
7994
7997
  this.cache[fileName] = item;
7995
7998
  this.emit("added", { sha1: response.sha1, sourcePath: response.sourcePath, fileSize: stat.size });
7999
+ if (this.size > this.maxSize) {
8000
+ this.purge(this.purgeSize);
8001
+ }
7996
8002
  }
7997
8003
  else {
7998
8004
  throw new Error("Unexpected file " + fileName);
@@ -8010,7 +8016,6 @@ class ObjectCache extends EventEmitter__default["default"] {
8010
8016
  console.error("Can't even delete this one", doubleError);
8011
8017
  }
8012
8018
  }
8013
- return undefined;
8014
8019
  }
8015
8020
  state(sha1) {
8016
8021
  if (sha1 in this.cache) {
@@ -8068,13 +8073,12 @@ class ObjectCache extends EventEmitter__default["default"] {
8068
8073
  throw new Error(`Wrong file size for ${path__default["default"].join(this.dir, response.sha1)}, should have been ${cacheItem.fileSize} but ended up being ${stat.size}`);
8069
8074
  }
8070
8075
  this.cache[response.sha1] = cacheItem;
8071
- // console.log(response);
8076
+ this.size += cacheItem.fileSize;
8072
8077
  this.emit("added", {
8073
8078
  sha1: response.sha1,
8074
8079
  sourcePath: response.sourcePath,
8075
8080
  fileSize: cacheItem.fileSize
8076
8081
  });
8077
- this.size += cacheItem.fileSize;
8078
8082
  if (this.size > this.maxSize) {
8079
8083
  this.purge(this.purgeSize);
8080
8084
  }
@@ -55000,7 +55004,7 @@ class CompileJob extends EventEmitter__default["default"] {
55000
55004
  fs$3.writeSync(this.fd, data);
55001
55005
  this.cppSize += data.length;
55002
55006
  this.startCompile = Date.now();
55003
- fs$3.close(this.fd);
55007
+ fs$3.closeSync(this.fd);
55004
55008
  this.fd = undefined;
55005
55009
  this.vm.child.send({ type: "compile", commandLine: this.commandLine, argv0: this.argv0, id: this.id, dir: this.vmDir }, this.sendCallback.bind(this));
55006
55010
  }
@@ -60063,6 +60067,7 @@ process.on("uncaughtException", (err) => {
60063
60067
  });
60064
60068
  let debug = option("debug");
60065
60069
  let objectCache;
60070
+ let fetchSequence = 0;
60066
60071
  function getFromCache(job, cb) {
60067
60072
  // console.log("got job", job.sha1, objectCache ? objectCache.state(job.sha1) : false);
60068
60073
  // if (objectCache)
@@ -60216,71 +60221,88 @@ client.on("fetch_cache_objects", (msg) => {
60216
60221
  for (let idx = 0; idx < max; ++idx) {
60217
60222
  promises.push(Promise.resolve());
60218
60223
  }
60224
+ const fetchTimeoutMs = option.int("object-cache-fetch-timeout", 5 * 60 * 1000);
60219
60225
  message.objects.forEach((operation, idx) => {
60220
60226
  promises[idx % promises.length] = promises[idx % promises.length].then(() => {
60221
60227
  return new Promise((resolve) => {
60222
60228
  assert__default["default"](objectCache, "Must have objectCache");
60223
- const file = path__default["default"].join(objectCache.dir, operation.sha1);
60229
+ if (objectCache.state(operation.sha1) !== "none") {
60230
+ console.log("Skipping", operation.sha1, "already", objectCache.state(operation.sha1));
60231
+ resolve();
60232
+ return;
60233
+ }
60234
+ const finalPath = path__default["default"].join(objectCache.dir, operation.sha1);
60235
+ const tmpPath = finalPath + ".tmp." + process.pid + "." + ++fetchSequence;
60224
60236
  const url = `http://${operation.source}/objectcache/${operation.sha1}`;
60225
- console.log("Downloading", url, "->", file);
60237
+ console.log("Downloading", url, "->", tmpPath);
60226
60238
  let expectedSize;
60227
60239
  let writeStream;
60228
- try {
60229
- writeStream = fs$3.createWriteStream(file);
60230
- }
60231
- catch (err) {
60232
- console.error("Got some error from write stream", err);
60240
+ let resolved = false;
60241
+ const cleanup = () => {
60233
60242
  try {
60234
- fs$3.unlinkSync(file);
60243
+ fs$3.unlinkSync(tmpPath);
60235
60244
  }
60236
60245
  catch (e) {
60237
60246
  /* */
60238
60247
  }
60239
- resolve();
60248
+ if (!resolved) {
60249
+ resolved = true;
60250
+ resolve();
60251
+ }
60252
+ };
60253
+ try {
60254
+ writeStream = fs$3.createWriteStream(tmpPath);
60255
+ }
60256
+ catch (err) {
60257
+ console.error("Failed to create write stream for", tmpPath, err);
60258
+ cleanup();
60240
60259
  return;
60241
60260
  }
60242
- axios({ method: "get", url: url, responseType: "stream" })
60261
+ axios({ method: "get", url: url, responseType: "stream", timeout: fetchTimeoutMs })
60243
60262
  .then((response) => {
60244
60263
  expectedSize = parseInt(response.headers["content-length"]);
60245
60264
  response.data.pipe(writeStream);
60246
60265
  response.data.on("error", (err) => {
60247
- console.error("Got some error from stream", err);
60266
+ console.error("Got http stream error for", operation.sha1, err);
60248
60267
  writeStream.destroy(new Error("http stream error " + err.toString()));
60249
60268
  });
60250
- // console
60251
60269
  })
60252
60270
  .catch((err) => {
60253
- console.error("Got some error", err);
60271
+ console.error("Got http error fetching", operation.sha1, err);
60254
60272
  writeStream.destroy(new Error("http error " + err.toString()));
60255
60273
  });
60256
60274
  writeStream.on("finish", () => {
60257
- console.log("Finished writing file", file);
60258
60275
  let stat;
60259
60276
  try {
60260
- stat = fs$3.statSync(file);
60277
+ stat = fs$3.statSync(tmpPath);
60261
60278
  }
60262
60279
  catch (err) {
60263
60280
  /* */
60264
60281
  }
60265
60282
  if (!stat || stat.size !== expectedSize) {
60266
- console.log("Got wrong size for", file, url, "\nGot", stat ? stat.size : -1, "expected", expectedSize);
60283
+ console.log("Got wrong size for", operation.sha1, url, "\nGot", stat ? stat.size : -1, "expected", expectedSize);
60284
+ cleanup();
60285
+ }
60286
+ else {
60267
60287
  try {
60268
- fs$3.unlinkSync(file);
60288
+ fs$3.renameSync(tmpPath, finalPath);
60289
+ ++filesReceived;
60290
+ assert__default["default"](objectCache, "Must have objectcache");
60291
+ objectCache.loadFile(finalPath, stat.size);
60269
60292
  }
60270
60293
  catch (err) {
60271
- // console.log("Got error unlinking file", file, err);
60294
+ console.error("Failed to rename", tmpPath, "to", finalPath, err);
60295
+ cleanup();
60272
60296
  }
60273
60297
  }
60274
- else {
60275
- ++filesReceived;
60276
- assert__default["default"](objectCache, "Must have objectcache");
60277
- objectCache.loadFile(file, stat.size);
60298
+ if (!resolved) {
60299
+ resolved = true;
60300
+ resolve();
60278
60301
  }
60279
- resolve();
60280
60302
  });
60281
60303
  writeStream.on("error", (err) => {
60282
- console.error("Got stream error", err);
60283
- resolve();
60304
+ console.error("Got stream error for", operation.sha1, err);
60305
+ cleanup();
60284
60306
  });
60285
60307
  });
60286
60308
  });
@@ -60288,10 +60310,6 @@ client.on("fetch_cache_objects", (msg) => {
60288
60310
  Promise.all(promises).then(() => {
60289
60311
  console.log("got results", filesReceived);
60290
60312
  });
60291
- // chain.then(() => {
60292
- // console.log("Received", filesReceived, "files. Restarting");
60293
- // process.exit();
60294
- // });
60295
60313
  });
60296
60314
  const environmentsRoot = path__default["default"].join(common.cacheDir(), "environments");
60297
60315
  function exec(command, opts) {
@@ -60454,7 +60472,10 @@ client.on("getEnvironments", (message) => {
60454
60472
  base += "/environment/";
60455
60473
  const work = () => {
60456
60474
  if (!message.environments.length) {
60457
- const restart = option("restart-on-new-environments");
60475
+ let restart = option("restart-on-new-environments");
60476
+ if (restart === "false" || restart === "0") {
60477
+ restart = false;
60478
+ }
60458
60479
  if (!restart) {
60459
60480
  setTimeout(() => {
60460
60481
  client.send("environments", { environments: Object.keys(environments) });
@@ -60607,7 +60628,7 @@ server.on("headers", (headers, req) => {
60607
60628
  wait = true;
60608
60629
  }
60609
60630
  else if (jobQueue.length >= client.slots) {
60610
- const priority = parseInt(req.headers["x-fisk-sha1"]) || 0;
60631
+ const priority = parseInt(String(req.headers["x-fisk-priority"])) || 0;
60611
60632
  let idx = jobQueue.length - 1;
60612
60633
  while (idx >= client.slots) {
60613
60634
  const job = jobQueue[idx].job;
@@ -60659,7 +60680,13 @@ server.on("listen", (app) => {
60659
60680
  const rstream = fs$3.createReadStream(file);
60660
60681
  rstream.on("error", (err) => {
60661
60682
  console.error("Got read stream error for", file, err);
60662
- rstream.close();
60683
+ rstream.destroy();
60684
+ if (!res.headersSent) {
60685
+ res.sendStatus(500);
60686
+ }
60687
+ else {
60688
+ res.destroy();
60689
+ }
60663
60690
  });
60664
60691
  rstream.pipe(res);
60665
60692
  }
@@ -60818,11 +60845,12 @@ server.on("job", (job) => {
60818
60845
  const original = contents.find((c) => c.path === item.path);
60819
60846
  assert__default["default"](original, "Must have original contents");
60820
60847
  assert__default["default"](original.uncompressed, "Must have uncompressed data");
60821
- return {
60848
+ const ret = {
60822
60849
  path: item.path,
60823
60850
  bytes: item.contents.length,
60824
60851
  uncompressedSize: original.uncompressed.byteLength
60825
60852
  };
60853
+ return ret;
60826
60854
  }),
60827
60855
  success: event.success,
60828
60856
  exitCode: event.exitCode,
@@ -6143,8 +6143,9 @@ clientBox.on("select", (ev) => {
6143
6143
  if (jobs) {
6144
6144
  // clientBox.current = clientKey;
6145
6145
  let str = "";
6146
- const data = [["Source file", "Builder", "Start time"]];
6147
- const widest = [data[0][0].length + 1, data[0][1].length + 1];
6146
+ const header = ["Source file", "Builder", "Start time"];
6147
+ const rows = [];
6148
+ const widest = [header[0].length + 1, header[1].length + 1];
6148
6149
  const now = Date.now();
6149
6150
  for (const [jobKey, jobValue] of jobs) {
6150
6151
  if (jobKey === "total") {
@@ -6152,15 +6153,19 @@ clientBox.on("select", (ev) => {
6152
6153
  }
6153
6154
  const sourceBasename = path__default["default"].basename(jobValue.sourcePath || jobValue.sourceFile || "");
6154
6155
  widest[0] = Math.max(sourceBasename.length + 1, widest[0]);
6155
- data.push([
6156
- sourceBasename,
6157
- jobValue.builder.ip + ":" + jobValue.builder.port,
6158
- humanizeDuration(now - jobValue.time)
6159
- ]);
6160
- widest[1] = Math.max(widest[1], data[data.length - 1][1].length + 1);
6156
+ const ageMs = now - jobValue.time;
6157
+ const row = {
6158
+ source: sourceBasename,
6159
+ builder: jobValue.builder.ip + ":" + jobValue.builder.port,
6160
+ humanizedAge: humanizeDuration(ageMs),
6161
+ ageMs
6162
+ };
6163
+ rows.push(row);
6164
+ widest[1] = Math.max(widest[1], row.builder.length + 1);
6161
6165
  }
6162
- data.sort((a, b) => parseInt(a[2]) - parseInt(b[2]));
6163
- data.forEach((line, idx) => {
6166
+ rows.sort((a, b) => a.ageMs - b.ageMs);
6167
+ const lines = [header, ...rows.map((r) => [r.source, r.builder, r.humanizedAge])];
6168
+ lines.forEach((line, idx) => {
6164
6169
  if (!idx) {
6165
6170
  str += "{bold}";
6166
6171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andersbakken/fisk",
3
- "version": "4.0.57",
3
+ "version": "4.0.61",
4
4
  "description": "Fisk, a distributed compile system",
5
5
  "scripts": {
6
6
  "lint": "eslint . --ext .ts",
@@ -87,11 +87,21 @@ class Database {
87
87
  records = {};
88
88
  }
89
89
  records[key] = value;
90
- fs__default["default"].writeFile(this.path + ".tmp", JSON.stringify(records) + "\n", () => {
91
- fs__default["default"].rename(this.path + ".tmp", this.path, () => {
92
- resolve();
90
+ fs__default["default"].writeFile(this.path + ".tmp", JSON.stringify(records) + "\n", (writeErr) => {
91
+ if (writeErr) {
92
+ reject(writeErr);
93
+ this.finishedOperation();
94
+ return;
95
+ }
96
+ fs__default["default"].rename(this.path + ".tmp", this.path, (renameErr) => {
97
+ if (renameErr) {
98
+ reject(renameErr);
99
+ }
100
+ else {
101
+ resolve();
102
+ }
103
+ this.finishedOperation();
93
104
  });
94
- this.finishedOperation();
95
105
  });
96
106
  })
97
107
  .catch((err) => {
@@ -3768,9 +3778,11 @@ class Environments {
3768
3778
  }
3769
3779
  else {
3770
3780
  const targets = this._links[srcHash];
3771
- targets.unset(targetHash);
3772
- if (!targets.size) {
3773
- delete this._links[srcHash];
3781
+ if (targets) {
3782
+ targets.unset(targetHash);
3783
+ if (!targets.size) {
3784
+ delete this._links[srcHash];
3785
+ }
3774
3786
  }
3775
3787
  }
3776
3788
  return this.syncLinks();
@@ -3796,15 +3808,19 @@ class Environments {
3796
3808
  }
3797
3809
  remove(hash) {
3798
3810
  // ### this should be promisified
3811
+ const env = this._data[hash];
3812
+ if (!env) {
3813
+ return;
3814
+ }
3799
3815
  try {
3800
- fs$4.removeSync(this._data[hash].path);
3816
+ fs$4.removeSync(env.path);
3801
3817
  delete this._data[hash];
3802
3818
  this.unlink(hash);
3803
3819
  this.unlink(undefined, hash);
3804
3820
  this.syncLinks();
3805
3821
  }
3806
3822
  catch (err) {
3807
- console.error("Failed to remove environment", this._data[hash].path, err);
3823
+ console.error("Failed to remove environment", env.path, err);
3808
3824
  }
3809
3825
  }
3810
3826
  }
@@ -3953,7 +3969,9 @@ function prettySize(bytes) {
3953
3969
  function addToSHA1Map(bySHA1, sha1, fileSize, node) {
3954
3970
  const data = bySHA1.get(sha1);
3955
3971
  if (data) {
3956
- data.nodes.push(node);
3972
+ if (data.nodes.indexOf(node) === -1) {
3973
+ data.nodes.push(node);
3974
+ }
3957
3975
  return data.nodes.length;
3958
3976
  }
3959
3977
  bySHA1.set(sha1, new SHA1Data(fileSize, node));
@@ -3977,21 +3995,32 @@ function removeFromSHA1Map(bySHA1, sha1, node) {
3977
3995
  console.error("We don't have", sha1);
3978
3996
  }
3979
3997
  }
3998
+ function pendingKey(sha1, node) {
3999
+ return sha1 + ":" + node.ip + ":" + node.port;
4000
+ }
3980
4001
  class ObjectCacheManager extends require$$0__default$2["default"] {
3981
4002
  constructor(option) {
3982
4003
  super();
3983
4004
  this.hits = 0;
3984
4005
  this.bySHA1 = new Map();
3985
4006
  this.byNode = new Map();
4007
+ this.pendingTransfers = new Set();
4008
+ this.pendingTransferTimers = new Map();
3986
4009
  this.redundancy = option.int("object-cache-redundancy", 1);
3987
4010
  if (this.redundancy <= 0) {
3988
4011
  this.redundancy = 1;
3989
4012
  }
4013
+ this.pendingTransferTimeoutMs = option.int("object-cache-transfer-timeout", 5 * 60 * 1000);
3990
4014
  this.distributeOnInsertion = Boolean(option("distribute-object-cache-on-insertion"));
3991
4015
  this.distributeOnCacheHit = Boolean(option("distribute-object-cache-on-cache-hit"));
3992
4016
  }
3993
4017
  clear() {
3994
4018
  this.hits = 0;
4019
+ for (const timer of this.pendingTransferTimers.values()) {
4020
+ clearTimeout(timer);
4021
+ }
4022
+ this.pendingTransfers.clear();
4023
+ this.pendingTransferTimers.clear();
3995
4024
  this.emit("cleared");
3996
4025
  }
3997
4026
  hit(sha1) {
@@ -4008,7 +4037,10 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4008
4037
  const nodeData = this.byNode.get(node);
4009
4038
  console.log("adding", msg.sourcePath, msg.sha1, "for", node.ip + ":" + node.port, nodeData ? nodeData.sha1s.length : -1);
4010
4039
  if (nodeData) {
4011
- nodeData.sha1s.push(msg.sha1);
4040
+ this.clearPendingTransfer(msg.sha1, node);
4041
+ if (nodeData.sha1s.indexOf(msg.sha1) === -1) {
4042
+ nodeData.sha1s.push(msg.sha1);
4043
+ }
4012
4044
  nodeData.size = msg.cacheSize;
4013
4045
  const count = addToSHA1Map(this.bySHA1, msg.sha1, msg.fileSize, node);
4014
4046
  if (this.distributeOnInsertion && count - 1 < this.redundancy) {
@@ -4057,6 +4089,7 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4057
4089
  return;
4058
4090
  }
4059
4091
  this.byNode.delete(node);
4092
+ this.clearAllPendingForNode(node);
4060
4093
  nodeData.sha1s.forEach((sha1) => {
4061
4094
  removeFromSHA1Map(this.bySHA1, sha1, node);
4062
4095
  });
@@ -4081,7 +4114,7 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4081
4114
  data.name = key.name;
4082
4115
  }
4083
4116
  if (key.hostname) {
4084
- data.name = key.hostname;
4117
+ data.hostname = key.hostname;
4085
4118
  }
4086
4119
  nodes[key.ip + ":" + key.port] = data;
4087
4120
  });
@@ -4110,11 +4143,12 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4110
4143
  if (isNaN(max) || max <= 0) {
4111
4144
  max = undefined;
4112
4145
  }
4113
- const sha1 = String(query.sha1);
4146
+ const sha1 = query.sha1 && typeof query.sha1 === "string" ? query.sha1 : undefined;
4147
+ const nodeRestriction = query.node && typeof query.node === "string" ? query.node : undefined;
4114
4148
  let ret;
4115
4149
  if (res) {
4116
4150
  ret = { type: "fetch_cache_objects", dry: dry, commands: {} };
4117
- console.log("distribute called with redundancy of", redundancy, "and max of", max, "dry", dry, "sha1", sha1);
4151
+ console.log("distribute called with redundancy of", redundancy, "and max of", max, "dry", dry, "sha1", sha1 || "(all)");
4118
4152
  }
4119
4153
  const nodes = Array.from(this.byNode.keys());
4120
4154
  let nodeIdx = 0;
@@ -4124,23 +4158,20 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4124
4158
  commands.set(key, { objects: [], available: value.maxSize - value.size });
4125
4159
  });
4126
4160
  }
4127
- const nodeRestriction = query.node;
4128
4161
  let count = 0;
4129
- // console.log(commands);
4130
4162
  if (this.byNode.size >= 2) {
4131
- // let max = 1;
4132
4163
  let roundRobinIndex = 0;
4133
4164
  const processObject = (sha, value) => {
4134
4165
  if (max !== undefined && max <= 0) {
4135
4166
  return;
4136
4167
  }
4137
- const needed = Math.min(redundancy + 1 - value.nodes.length, this.byNode.size - 1);
4168
+ const pendingCount = this.pendingCountForSha1(sha);
4169
+ const totalCopies = value.nodes.length + pendingCount;
4170
+ const needed = Math.min(redundancy + 1 - totalCopies, this.byNode.size - 1);
4138
4171
  if (needed > 0) {
4139
- const needed2 = redundancy + 1 - value.nodes.length;
4140
- // console.log("should distribute", key, "to", needed, "nodes");
4141
4172
  let firstIdx;
4142
4173
  let found = 0;
4143
- while (found < needed2) {
4174
+ while (found < needed) {
4144
4175
  if (++nodeIdx === nodes.length) {
4145
4176
  nodeIdx = 0;
4146
4177
  }
@@ -4154,6 +4185,12 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4154
4185
  if (value.nodes.indexOf(node) !== -1) {
4155
4186
  continue;
4156
4187
  }
4188
+ if (this.hasPendingTransfer(sha, node)) {
4189
+ continue;
4190
+ }
4191
+ if (nodeRestriction && node.ip + ":" + node.port !== nodeRestriction) {
4192
+ continue;
4193
+ }
4157
4194
  let data = commands.get(node);
4158
4195
  let available;
4159
4196
  if (data) {
@@ -4179,7 +4216,6 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4179
4216
  break;
4180
4217
  }
4181
4218
  }
4182
- // console.log("found candidates", candidates.map(node => node.ip + ":" + node.port));
4183
4219
  }
4184
4220
  };
4185
4221
  if (sha1) {
@@ -4198,14 +4234,16 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4198
4234
  }
4199
4235
  }
4200
4236
  commands.forEach((value, key) => {
4201
- // console.log(key.ip + ": " + key.port, "will receive", value.objects);
4202
- if (value.objects.length && (!nodeRestriction || key.ip + ":" + key.port === nodeRestriction)) {
4237
+ if (value.objects.length) {
4203
4238
  console.log(`sending ${value.objects.length}/${this.bySHA1.size} fetch_cache_objects to ${key.ip}:${key.port}`);
4204
4239
  if (ret) {
4205
- ret[`${key.ip}:${key.port}`] = value.objects;
4240
+ ret.commands[`${key.ip}:${key.port}`] = value.objects;
4206
4241
  }
4207
4242
  count += value.objects.length;
4208
4243
  if (!dry) {
4244
+ for (const obj of value.objects) {
4245
+ this.addPendingTransfer(obj.sha1, key);
4246
+ }
4209
4247
  key.send({ type: "fetch_cache_objects", objects: value.objects });
4210
4248
  }
4211
4249
  }
@@ -4213,9 +4251,52 @@ class ObjectCacheManager extends require$$0__default$2["default"] {
4213
4251
  if (ret && res) {
4214
4252
  ret.count = count;
4215
4253
  res.send(JSON.stringify(ret, null, 4) + "\n");
4216
- // } else if (res) {
4217
4254
  }
4218
4255
  }
4256
+ addPendingTransfer(sha1, node) {
4257
+ const key = pendingKey(sha1, node);
4258
+ this.pendingTransfers.add(key);
4259
+ const timer = setTimeout(() => {
4260
+ this.pendingTransfers.delete(key);
4261
+ this.pendingTransferTimers.delete(key);
4262
+ }, this.pendingTransferTimeoutMs);
4263
+ timer.unref();
4264
+ this.pendingTransferTimers.set(key, timer);
4265
+ }
4266
+ clearPendingTransfer(sha1, node) {
4267
+ const key = pendingKey(sha1, node);
4268
+ this.pendingTransfers.delete(key);
4269
+ const timer = this.pendingTransferTimers.get(key);
4270
+ if (timer) {
4271
+ clearTimeout(timer);
4272
+ this.pendingTransferTimers.delete(key);
4273
+ }
4274
+ }
4275
+ clearAllPendingForNode(node) {
4276
+ const suffix = ":" + node.ip + ":" + node.port;
4277
+ for (const key of this.pendingTransfers) {
4278
+ if (key.endsWith(suffix)) {
4279
+ this.pendingTransfers.delete(key);
4280
+ const timer = this.pendingTransferTimers.get(key);
4281
+ if (timer) {
4282
+ clearTimeout(timer);
4283
+ this.pendingTransferTimers.delete(key);
4284
+ }
4285
+ }
4286
+ }
4287
+ }
4288
+ pendingCountForSha1(sha1) {
4289
+ let count = 0;
4290
+ for (const key of this.pendingTransfers) {
4291
+ if (key.startsWith(sha1 + ":")) {
4292
+ ++count;
4293
+ }
4294
+ }
4295
+ return count;
4296
+ }
4297
+ hasPendingTransfer(sha1, node) {
4298
+ return this.pendingTransfers.has(pendingKey(sha1, node));
4299
+ }
4219
4300
  }
4220
4301
 
4221
4302
  class Peak {
@@ -56039,7 +56120,6 @@ class Server extends require$$0__default$2["default"] {
56039
56120
  }
56040
56121
  this.emit("compile", client);
56041
56122
  const remaining = {};
56042
- client.ws.on("close", (status, reason) => client.emit("close", status, reason));
56043
56123
  client.ws.on("error", (err) => client.emit("error", err));
56044
56124
  client.ws.on("close", (code, reason) => {
56045
56125
  if (remaining.bytes) {
@@ -57843,7 +57923,7 @@ process.on("uncaughtException", (err) => {
57843
57923
  });
57844
57924
  const monitorsLog = option("monitor-log");
57845
57925
  server.on("error", (error) => {
57846
- throw new error();
57926
+ throw error;
57847
57927
  });
57848
57928
  let schedulerNpmVersion;
57849
57929
  try {
@@ -58305,7 +58385,6 @@ server.on("listen", (app) => {
58305
58385
  }
58306
58386
  else if (req.query && "distribute" in req.query) {
58307
58387
  objectCache.distribute(req.query, res);
58308
- res.sendStatus(200);
58309
58388
  }
58310
58389
  else {
58311
58390
  const pretty = req.query && req.query.unpretty ? undefined : 4;
@@ -58605,8 +58684,9 @@ function requestEnvironment(compile) {
58605
58684
  file = undefined;
58606
58685
  // send any new environments to builders
58607
58686
  delete pendingEnvironments[hsh];
58608
- purgeEnvironmentsToMaxSize();
58687
+ return purgeEnvironmentsToMaxSize();
58609
58688
  }
58689
+ return undefined;
58610
58690
  })
58611
58691
  .then(() => {
58612
58692
  if (env.last) {
@@ -58697,20 +58777,24 @@ server.on("compile", (compile) => {
58697
58777
  if (!filterBuilder(s)) {
58698
58778
  return;
58699
58779
  }
58780
+ let candidateEnv;
58781
+ for (let i = 0; i < usableEnvs.length; ++i) {
58782
+ if (usableEnvs[i] in s.environments) {
58783
+ candidateEnv = usableEnvs[i];
58784
+ break;
58785
+ }
58786
+ }
58787
+ if (!candidateEnv) {
58788
+ return;
58789
+ }
58700
58790
  const builderScore = score(s);
58701
58791
  if (!builder ||
58702
58792
  builderScore > bestScore ||
58703
- (builderScore === bestScore && builder.lastJob < s.lastJob)) {
58793
+ (builderScore === bestScore && s.lastJob < builder.lastJob)) {
58704
58794
  bestScore = builderScore;
58705
58795
  builder = s;
58706
58796
  foundInCache = true;
58707
- // Find which usable environment this builder has
58708
- for (let i = 0; i < usableEnvs.length; ++i) {
58709
- if (usableEnvs[i] in s.environments) {
58710
- env = usableEnvs[i];
58711
- break;
58712
- }
58713
- }
58797
+ env = candidateEnv;
58714
58798
  }
58715
58799
  });
58716
58800
  }
@@ -58949,14 +59033,22 @@ server.on("monitor", (client) => {
58949
59033
  monitors.forEach((monitor) => {
58950
59034
  monitor.send(info);
58951
59035
  });
59036
+ })
59037
+ .catch((err) => {
59038
+ console.error("Failed to link environments", err);
58952
59039
  });
58953
59040
  break;
58954
59041
  case "unlinkEnvironments":
58955
- Environments.instance.unlink(message.srcHash, message.targetHash).then(() => {
59042
+ Environments.instance
59043
+ .unlink(message.srcHash, message.targetHash)
59044
+ .then(() => {
58956
59045
  const info = { type: "listEnvironments", environments: environmentsInfo() };
58957
59046
  monitors.forEach((monitor) => {
58958
59047
  monitor.send(info);
58959
59048
  });
59049
+ })
59050
+ .catch((err) => {
59051
+ console.error("Failed to unlink environments", err);
58960
59052
  });
58961
59053
  break;
58962
59054
  case "listUsers": {
@@ -59042,7 +59134,7 @@ server.on("monitor", (client) => {
59042
59134
  else if (users[message.user].cookieIp !== client.ip) {
59043
59135
  throw new Error("Wrong ip address");
59044
59136
  }
59045
- else if (users[message.user].cookieExpiration || 0 <= Date.now()) {
59137
+ else if ((users[message.user].cookieExpiration || 0) <= Date.now()) {
59046
59138
  throw new Error("Cookie expired");
59047
59139
  }
59048
59140
  else {