configre 2.1.6 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configre",
3
- "version": "2.1.6",
3
+ "version": "2.1.8",
4
4
  "description": "🔧 Effortlessly Tailor Your Settings",
5
5
  "type": "module",
6
6
  "engines": {
package/secrets/config.js CHANGED
@@ -1,5 +1,9 @@
1
1
  export default {
2
2
  git: {
3
3
  timeoutMs: 30_000
4
+ },
5
+ lock: {
6
+ timeoutMs: 5_000,
7
+ retryIntervalMs: 25
4
8
  }
5
9
  };
package/secrets/files.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import crypto from "node:crypto";
3
+ import { performance } from "node:perf_hooks";
4
+ import config from "./config.js";
3
5
 
4
6
  function stat(filename) {
5
7
  try {
@@ -37,12 +39,20 @@ function readJSON(filename) {
37
39
 
38
40
  function withLock(filename, action) {
39
41
  const lockPath = filename + ".lock";
42
+ const deadline = performance.now() + config.lock.timeoutMs;
43
+ const sleeper = new Int32Array(new SharedArrayBuffer(4));
40
44
  let fd;
41
- try {
42
- fd = fs.openSync(lockPath, "wx", 0o600);
43
- } catch (error) {
44
- if (error.code !== "EEXIST") throw error;
45
- throw new Error(`Configre secrets: another writer holds ${lockPath}; retry after it finishes, or remove the lock only if the writer has stopped`);
45
+ while (fd === undefined) {
46
+ try {
47
+ fd = fs.openSync(lockPath, "wx", 0o600);
48
+ } catch (error) {
49
+ if (error.code !== "EEXIST") throw error;
50
+ const remaining = deadline - performance.now();
51
+ if (remaining <= 0) {
52
+ throw new Error(`Configre secrets: another writer holds ${lockPath}; timed out waiting for it to finish; remove the lock only if the writer has stopped`);
53
+ }
54
+ Atomics.wait(sleeper, 0, 0, Math.min(config.lock.retryIntervalMs, remaining));
55
+ }
46
56
  }
47
57
  try {
48
58
  return action();
@@ -3,7 +3,8 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
6
- import { spawnSync } from "node:child_process";
6
+ import { spawn, spawnSync } from "node:child_process";
7
+ import { once } from "node:events";
7
8
  import { pathToFileURL } from "node:url";
8
9
  import test from "node:test";
9
10
  import Configre from "../index.js";
@@ -927,6 +928,30 @@ test("registration rejects profile collisions, unsafe names and damaged encrypte
927
928
  assert.equal(git(f.remote, ["rev-parse", "main"]), remoteHead);
928
929
  });
929
930
 
931
+ test("a parallel loader waits for the writer and reads its completed update", async t => {
932
+ const f = fixture(t);
933
+ writeSettings(f);
934
+ load(f);
935
+ const child = spawn(process.execPath, ["--input-type=module", "-e", `
936
+ import fs from 'node:fs';
937
+ import { withLock } from ${JSON.stringify(pathToFileURL(path.resolve("secrets/files.js")).href)};
938
+ withLock(process.argv[1], () => {
939
+ process.stdout.write('locked');
940
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 300);
941
+ fs.writeFileSync(process.argv[2], 'module.exports = { api: { key: "parallel-update" } };');
942
+ });
943
+ `, f.encrypted, f.local], { stdio: ["ignore", "pipe", "inherit"] });
944
+ t.after(() => child.kill());
945
+ const exited = once(child, "exit");
946
+ await once(child.stdout, "data");
947
+ assert.equal(fs.existsSync(f.encrypted + ".lock"), true);
948
+ assert.equal(load(f).api.key, "parallel-update");
949
+ assert.deepEqual(await exited, [0, null]);
950
+ assert.equal(fs.existsSync(f.encrypted + ".lock"), false);
951
+ fs.unlinkSync(f.local);
952
+ assert.equal(load(f).api.key, "parallel-update");
953
+ });
954
+
930
955
  test("writer locks and failed atomic replacement preserve the last encrypted version", t => {
931
956
  const f = fixture(t);
932
957
  writeSettings(f);
@@ -934,7 +959,7 @@ test("writer locks and failed atomic replacement preserve the last encrypted ver
934
959
  const original = fs.readFileSync(f.encrypted);
935
960
  writeSettings(f, { api: { key: sentinel + "-new" } });
936
961
  fs.writeFileSync(f.encrypted + ".lock", "");
937
- assert.throws(() => load(f), /another writer/);
962
+ assert.throws(() => load(f), /another writer.*timed out/);
938
963
  assert.equal(fs.existsSync(f.encrypted + ".lock"), true);
939
964
  assert.deepEqual(fs.readFileSync(f.encrypted), original);
940
965
  fs.unlinkSync(f.encrypted + ".lock");