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 +1 -1
- package/secrets/config.js +4 -0
- package/secrets/files.js +15 -5
- package/test/secrets.test.js +27 -2
package/package.json
CHANGED
package/secrets/config.js
CHANGED
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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();
|
package/test/secrets.test.js
CHANGED
|
@@ -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");
|