@zukhruf/mutex 0.3.0 → 0.3.2
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/README.md +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lock-stores/file-system/caller.d.ts +15 -0
- package/dist/lock-stores/file-system/caller.d.ts.map +1 -0
- package/dist/lock-stores/file-system/caller.js +35 -0
- package/dist/lock-stores/file-system/caller.js.map +1 -0
- package/dist/lock-stores/file-system/file-lock-store.d.ts +7 -6
- package/dist/lock-stores/file-system/file-lock-store.d.ts.map +1 -1
- package/dist/lock-stores/file-system/file-lock-store.js +40 -14
- package/dist/lock-stores/file-system/file-lock-store.js.map +1 -1
- package/dist/lock-stores/file-system/lock-file-store.d.ts.map +1 -1
- package/dist/lock-stores/file-system/lock-file-store.js +41 -15
- package/dist/lock-stores/file-system/lock-file-store.js.map +1 -1
- package/dist/lock-stores/file-system/presence.d.ts +37 -0
- package/dist/lock-stores/file-system/presence.d.ts.map +1 -0
- package/dist/lock-stores/file-system/presence.js +112 -0
- package/dist/lock-stores/file-system/presence.js.map +1 -0
- package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts +1 -1
- package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts.map +1 -1
- package/dist/lock-stores/file-system/ticket-queue-file-store.js +78 -50
- package/dist/lock-stores/file-system/ticket-queue-file-store.js.map +1 -1
- package/dist/lock-stores/socket/electing-connector.d.ts.map +1 -1
- package/dist/lock-stores/socket/electing-connector.js +61 -5
- package/dist/lock-stores/socket/electing-connector.js.map +1 -1
- package/dist/lock-stores/socket/handshake.d.ts +27 -0
- package/dist/lock-stores/socket/handshake.d.ts.map +1 -0
- package/dist/lock-stores/socket/handshake.js +80 -0
- package/dist/lock-stores/socket/handshake.js.map +1 -0
- package/dist/lock-stores/socket/lock-server.d.ts.map +1 -1
- package/dist/lock-stores/socket/lock-server.js +7 -1
- package/dist/lock-stores/socket/lock-server.js.map +1 -1
- package/dist/lock-stores/socket/protocol-version-error.d.ts +12 -0
- package/dist/lock-stores/socket/protocol-version-error.d.ts.map +1 -0
- package/dist/lock-stores/socket/protocol-version-error.js +19 -0
- package/dist/lock-stores/socket/protocol-version-error.js.map +1 -0
- package/dist/shared/sqlite/is-cant-open.d.ts +3 -0
- package/dist/shared/sqlite/is-cant-open.d.ts.map +1 -0
- package/dist/shared/sqlite/is-cant-open.js +9 -0
- package/dist/shared/sqlite/is-cant-open.js.map +1 -0
- package/dist/shared/sqlite/is-not-a-database.d.ts +3 -0
- package/dist/shared/sqlite/is-not-a-database.d.ts.map +1 -0
- package/dist/shared/sqlite/is-not-a-database.js +9 -0
- package/dist/shared/sqlite/is-not-a-database.js.map +1 -0
- package/package.json +2 -1
- package/dist/lock-stores/file-system/owner.d.ts +0 -16
- package/dist/lock-stores/file-system/owner.d.ts.map +0 -1
- package/dist/lock-stores/file-system/owner.js +0 -47
- package/dist/lock-stores/file-system/owner.js.map +0 -1
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ See [Fencing tokens](./docs/concepts/fencing-tokens.md) and the recipe [Protect
|
|
|
100
100
|
- [Leader election](./docs/concepts/leader-election.md): how `SocketStore` selects its coordinator.
|
|
101
101
|
- [Failure modes](./docs/concepts/failure-modes.md): what each lock store does when something stops.
|
|
102
102
|
|
|
103
|
-
**Lock stores**: one page for each lock store, with What, Why, When, When not, How it works, Failure modes, Options, and Evidence. See the table above.
|
|
103
|
+
**Lock stores**: one page for each lock store, with What, Why, When, When not, How it works, Acquire modes, Failure modes, Options, and Evidence. See the table above.
|
|
104
104
|
|
|
105
105
|
**Recipes**: one use case each, with a full program that you can run.
|
|
106
106
|
|
package/dist/index.d.ts
CHANGED
|
@@ -24,5 +24,6 @@ export { FileLockStore, type FileLockStoreOptions, } from './lock-stores/file-sy
|
|
|
24
24
|
export { TicketQueueFileStore } from './lock-stores/file-system/ticket-queue-file-store.ts';
|
|
25
25
|
export { LockFileStore } from './lock-stores/file-system/lock-file-store.ts';
|
|
26
26
|
export { SqliteStore } from './lock-stores/sqlite/sqlite-store.ts';
|
|
27
|
+
export { ProtocolVersionError } from './lock-stores/socket/protocol-version-error.ts';
|
|
27
28
|
export { SocketStore, type SocketRole, type SocketStoreOptions, } from './lock-stores/socket/socket-store.ts';
|
|
28
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,OAAO,EACP,cAAc,GACf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,KAAK,iBAAiB,GACvB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,kBAAkB,GACxB,MAAM,sCAAsC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,OAAO,EACP,cAAc,GACf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,KAAK,iBAAiB,GACvB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gDAAgD,CAAC;AACtF,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,kBAAkB,GACxB,MAAM,sCAAsC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21,5 +21,6 @@ export { FileLockStore, } from "./lock-stores/file-system/file-lock-store.js";
|
|
|
21
21
|
export { TicketQueueFileStore } from "./lock-stores/file-system/ticket-queue-file-store.js";
|
|
22
22
|
export { LockFileStore } from "./lock-stores/file-system/lock-file-store.js";
|
|
23
23
|
export { SqliteStore } from "./lock-stores/sqlite/sqlite-store.js";
|
|
24
|
+
export { ProtocolVersionError } from "./lock-stores/socket/protocol-version-error.js";
|
|
24
25
|
export { SocketStore, } from "./lock-stores/socket/socket-store.js";
|
|
25
26
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAc,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAS3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EACL,cAAc,GAEf,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EACL,WAAW,GAEZ,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,qBAAqB,GAEtB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,kBAAkB,GAEnB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EACL,aAAa,GAEd,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,WAAW,GAGZ,MAAM,sCAAsC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAc,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAS3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EACL,cAAc,GAEf,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EACL,WAAW,GAEZ,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,qBAAqB,GAEtB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EACL,kBAAkB,GAEnB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EACL,aAAa,GAEd,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gDAAgD,CAAC;AACtF,OAAO,EACL,WAAW,GAGZ,MAAM,sCAAsC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifies one request for a key and the process that made it. The process
|
|
3
|
+
* ID and host name are for people who read a lock file; whether the caller
|
|
4
|
+
* still runs is told by its presence.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Caller {
|
|
7
|
+
readonly pid: number;
|
|
8
|
+
readonly host: string;
|
|
9
|
+
readonly id: string;
|
|
10
|
+
constructor(pid: number, host: string, id: string);
|
|
11
|
+
static current(): Caller;
|
|
12
|
+
static parse(serialized: string): Caller;
|
|
13
|
+
serialize(): string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/caller.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;IAMjD,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAaxC,SAAS,IAAI,MAAM;CAGpB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
import { isRecord } from "../../shared/is-record.js";
|
|
4
|
+
/**
|
|
5
|
+
* Identifies one request for a key and the process that made it. The process
|
|
6
|
+
* ID and host name are for people who read a lock file; whether the caller
|
|
7
|
+
* still runs is told by its presence.
|
|
8
|
+
*/
|
|
9
|
+
export class Caller {
|
|
10
|
+
pid;
|
|
11
|
+
host;
|
|
12
|
+
id;
|
|
13
|
+
constructor(pid, host, id) {
|
|
14
|
+
this.pid = pid;
|
|
15
|
+
this.host = host;
|
|
16
|
+
this.id = id;
|
|
17
|
+
}
|
|
18
|
+
static current() {
|
|
19
|
+
return new Caller(process.pid, hostname(), randomUUID());
|
|
20
|
+
}
|
|
21
|
+
static parse(serialized) {
|
|
22
|
+
const parsed = JSON.parse(serialized);
|
|
23
|
+
if (!isRecord(parsed) ||
|
|
24
|
+
typeof parsed.pid !== 'number' ||
|
|
25
|
+
typeof parsed.host !== 'string' ||
|
|
26
|
+
typeof parsed.id !== 'string') {
|
|
27
|
+
throw new SyntaxError(`Not a lock caller: ${serialized}`);
|
|
28
|
+
}
|
|
29
|
+
return new Caller(parsed.pid, parsed.host, parsed.id);
|
|
30
|
+
}
|
|
31
|
+
serialize() {
|
|
32
|
+
return JSON.stringify({ pid: this.pid, host: this.host, id: this.id });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/caller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD;;;;GAIG;AACH,MAAM,OAAO,MAAM;IACR,GAAG,CAAS;IACZ,IAAI,CAAS;IACb,EAAE,CAAS;IAEpB,YAAY,GAAW,EAAE,IAAY,EAAE,EAAU;QAC/C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,OAAO;QACZ,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAkB;QAC7B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/C,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAC7B,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;CACF"}
|
|
@@ -10,8 +10,6 @@ export interface FileLockStoreOptions {
|
|
|
10
10
|
export interface PollOptions {
|
|
11
11
|
/** Stops between attempts when it aborts; the poll then rejects with `signal.reason`. */
|
|
12
12
|
signal?: AbortSignal | undefined;
|
|
13
|
-
/** Whether the wait between attempts keeps the process alive. */
|
|
14
|
-
keepAlive?: boolean;
|
|
15
13
|
}
|
|
16
14
|
/** Shares one lock directory between processes: each key maps to `<directory>/<key>.lock`. */
|
|
17
15
|
export declare abstract class FileLockStore implements LockStore {
|
|
@@ -23,12 +21,15 @@ export declare abstract class FileLockStore implements LockStore {
|
|
|
23
21
|
protected abstract lock(path: string, signal: AbortSignal | undefined): Promise<AsyncDisposable>;
|
|
24
22
|
/** Holds the lock at `path` only if no other holder has it now. */
|
|
25
23
|
protected abstract tryLock(path: string): Promise<AsyncDisposable | undefined>;
|
|
26
|
-
protected poll<T>(attempt: () => Promise<T | undefined>, { signal
|
|
24
|
+
protected poll<T>(attempt: () => Promise<T | undefined>, { signal }?: PollOptions): Promise<T>;
|
|
27
25
|
/**
|
|
28
26
|
* Serializes evicting a dead holder. Without it, two waiters that saw the
|
|
29
|
-
* same dead holder could each remove a lock the other had just taken.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* same dead holder could each remove a lock the other had just taken. The
|
|
28
|
+
* kernel holds this lock, so a waiter that dies while it evicts frees it at
|
|
29
|
+
* once. Its file stays for the next eviction: deleting it while waiters use
|
|
30
|
+
* it would let two of them lock two different files. Nothing may open that
|
|
31
|
+
* file except through SQLite: closing any other handle to it drops this
|
|
32
|
+
* process's lock without a word.
|
|
32
33
|
*/
|
|
33
34
|
protected withReclaimLock(path: string, task: () => Promise<void>): Promise<void>;
|
|
34
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-lock-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAK3E,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED,8FAA8F;AAC9F,8BAAsB,aAAc,YAAW,SAAS;;gBAMpD,SAAS,EAAE,MAAM,EACjB,EACE,YAAiB,EACjB,MAAuC,GACxC,GAAE,oBAAyB;IAOxB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAMrE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAKzD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,CAAC,IAAI,CACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;IAE3B,mEAAmE;IACnE,SAAS,CAAC,QAAQ,CAAC,OAAO,CACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;cAEvB,IAAI,CAAC,CAAC,EACpB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EACrC,EAAE,MAAM,EAAE,GAAE,WAAgB,GAC3B,OAAO,CAAC,CAAC,CAAC;IAab;;;;;;;;OAQG;cACa,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;CAoBxE"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { mkdir
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
3
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
5
|
import { FileTokenSource } from "../../fencing/file-token-source.js";
|
|
5
6
|
import { assertLocalDirectory } from "../../local-directory/local-directory.js";
|
|
6
7
|
import { leaseFor } from "../../mutex/lease.js";
|
|
7
|
-
import { createExclusive } from "../../shared/fs/create-exclusive.js";
|
|
8
|
-
import { patiently } from "../../shared/fs/patiently.js";
|
|
9
8
|
import { safeFileName } from "../../shared/fs/safe-file-name.js";
|
|
10
|
-
import {
|
|
9
|
+
import { isBusy } from "../../shared/sqlite/is-busy.js";
|
|
10
|
+
import { isNotADatabase } from "../../shared/sqlite/is-not-a-database.js";
|
|
11
11
|
/** Shares one lock directory between processes: each key maps to `<directory>/<key>.lock`. */
|
|
12
12
|
export class FileLockStore {
|
|
13
13
|
#directory;
|
|
@@ -27,13 +27,13 @@ export class FileLockStore {
|
|
|
27
27
|
const held = await this.tryLock(await this.#pathFor(key));
|
|
28
28
|
return held && leaseFor(key, held, this.#tokens);
|
|
29
29
|
}
|
|
30
|
-
async poll(attempt, { signal
|
|
30
|
+
async poll(attempt, { signal } = {}) {
|
|
31
31
|
for (;;) {
|
|
32
32
|
const result = await attempt();
|
|
33
33
|
if (result !== undefined)
|
|
34
34
|
return result;
|
|
35
35
|
try {
|
|
36
|
-
await delay(this.#pollInterval, undefined, { signal
|
|
36
|
+
await delay(this.#pollInterval, undefined, { signal });
|
|
37
37
|
}
|
|
38
38
|
catch (error) {
|
|
39
39
|
signal?.throwIfAborted();
|
|
@@ -43,19 +43,28 @@ export class FileLockStore {
|
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Serializes evicting a dead holder. Without it, two waiters that saw the
|
|
46
|
-
* same dead holder could each remove a lock the other had just taken.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* same dead holder could each remove a lock the other had just taken. The
|
|
47
|
+
* kernel holds this lock, so a waiter that dies while it evicts frees it at
|
|
48
|
+
* once. Its file stays for the next eviction: deleting it while waiters use
|
|
49
|
+
* it would let two of them lock two different files. Nothing may open that
|
|
50
|
+
* file except through SQLite: closing any other handle to it drops this
|
|
51
|
+
* process's lock without a word.
|
|
49
52
|
*/
|
|
50
53
|
async withReclaimLock(path, task) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
+
const reclaimPath = `${path}.reclaim`;
|
|
55
|
+
const reclaim = new DatabaseSync(reclaimPath, { timeout: 0 });
|
|
54
56
|
try {
|
|
55
|
-
|
|
57
|
+
if (!claim(reclaim, reclaimPath))
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
await task();
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
reclaim.exec('ROLLBACK');
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
finally {
|
|
58
|
-
|
|
67
|
+
reclaim.close();
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
70
|
async #pathFor(key) {
|
|
@@ -64,4 +73,21 @@ export class FileLockStore {
|
|
|
64
73
|
return join(this.#directory, `${safeFileName(key)}.lock`);
|
|
65
74
|
}
|
|
66
75
|
}
|
|
76
|
+
/** Whether this connection now holds the reclaim lock; another waiter is evicting if not. */
|
|
77
|
+
function claim(reclaim, path) {
|
|
78
|
+
try {
|
|
79
|
+
// A journal on disk would outlive a waiter that dies while it holds the lock.
|
|
80
|
+
reclaim.exec('PRAGMA journal_mode = MEMORY');
|
|
81
|
+
reclaim.exec('BEGIN EXCLUSIVE');
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (isBusy(error))
|
|
86
|
+
return false;
|
|
87
|
+
if (isNotADatabase(error)) {
|
|
88
|
+
throw new Error(`The reclaim file ${JSON.stringify(path)} was left by an older version of @zukhruf/mutex. Stop the processes that run that version, then delete the file.`, { cause: error });
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
67
93
|
//# sourceMappingURL=file-lock-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"file-lock-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lock-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;GAGG;AACH,qBAAa,aAAc,SAAQ,aAAa;;IAC9C,SAAS,CAAC,IAAI,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAKX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAmD5E"}
|
|
@@ -2,35 +2,61 @@ import { readFile, unlink } from 'node:fs/promises';
|
|
|
2
2
|
import { createExclusive } from "../../shared/fs/create-exclusive.js";
|
|
3
3
|
import { isErrno } from "../../shared/fs/errno.js";
|
|
4
4
|
import { patiently } from "../../shared/fs/patiently.js";
|
|
5
|
+
import { Caller } from "./caller.js";
|
|
5
6
|
import { FileLockStore } from "./file-lock-store.js";
|
|
6
|
-
import {
|
|
7
|
+
import { Presence } from "./presence.js";
|
|
7
8
|
/**
|
|
8
9
|
* Whoever creates the lock file holds the key; releasing deletes it. Waiters
|
|
9
10
|
* retry in no particular order, so this store is not FIFO.
|
|
10
11
|
*/
|
|
11
12
|
export class LockFileStore extends FileLockStore {
|
|
12
13
|
lock(path, signal) {
|
|
13
|
-
const me =
|
|
14
|
+
const me = Caller.current();
|
|
14
15
|
return this.poll(() => this.#attempt(path, me), { signal });
|
|
15
16
|
}
|
|
16
17
|
async tryLock(path) {
|
|
17
|
-
const me =
|
|
18
|
-
// An attempt that finds a
|
|
18
|
+
const me = Caller.current();
|
|
19
|
+
// An attempt that finds a gone holder removes it, so one more attempt can then succeed.
|
|
19
20
|
return (await this.#attempt(path, me)) ?? this.#attempt(path, me);
|
|
20
21
|
}
|
|
22
|
+
/** A waiter is present only while it tries to create the file, so giving up leaves nothing behind. */
|
|
21
23
|
async #attempt(path, me) {
|
|
22
|
-
if (await createExclusive(path, me.serialize())) {
|
|
23
|
-
return { [Symbol.asyncDispose]: () => removeLockFile(path) };
|
|
24
|
-
}
|
|
25
24
|
const holder = await readHolder(path);
|
|
26
|
-
if (holder
|
|
27
|
-
await this
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
if (holder) {
|
|
26
|
+
await this.#evictIfGone(path, holder);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const presence = Presence.claim(Presence.pathOf(path, me));
|
|
30
|
+
const created = await createExclusive(path, me.serialize()).catch(async (error) => {
|
|
31
|
+
await presence.withdraw();
|
|
32
|
+
throw error;
|
|
33
|
+
});
|
|
34
|
+
if (!created) {
|
|
35
|
+
await presence.withdraw();
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
[Symbol.asyncDispose]: () => presence.releaseAfter(() => removeLockFile(path)),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async #evictIfGone(path, holder) {
|
|
43
|
+
const presence = Presence.pathOf(path, holder);
|
|
44
|
+
const state = Presence.check(presence);
|
|
45
|
+
if (state === 'present')
|
|
46
|
+
return;
|
|
47
|
+
if (state === 'missing') {
|
|
48
|
+
// A release removes the record before the presence file, so a record still in place never had one.
|
|
49
|
+
if ((await readHolder(path))?.id === holder.id) {
|
|
50
|
+
throw Presence.missing(path, presence);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
32
53
|
}
|
|
33
|
-
|
|
54
|
+
await this.withReclaimLock(path, async () => {
|
|
55
|
+
if ((await readHolder(path))?.id !== holder.id)
|
|
56
|
+
return;
|
|
57
|
+
await removeLockFile(path);
|
|
58
|
+
await Presence.delete(presence);
|
|
59
|
+
});
|
|
34
60
|
}
|
|
35
61
|
}
|
|
36
62
|
function removeLockFile(path) {
|
|
@@ -38,7 +64,7 @@ function removeLockFile(path) {
|
|
|
38
64
|
}
|
|
39
65
|
async function readHolder(path) {
|
|
40
66
|
try {
|
|
41
|
-
return
|
|
67
|
+
return Caller.parse(await patiently(() => readFile(path, 'utf8')));
|
|
42
68
|
}
|
|
43
69
|
catch (error) {
|
|
44
70
|
if (isErrno(error, 'ENOENT'))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"lock-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Caller } from './caller.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A caller's presence: an exclusive SQLite transaction on a file of its own,
|
|
4
|
+
* held from before the caller's record (a lock file or a ticket) appears until
|
|
5
|
+
* after it is gone. The kernel ends it when the caller's process or thread
|
|
6
|
+
* stops, however it stops, so a waiter that can read the file knows the caller
|
|
7
|
+
* is gone. Nothing may open the file except through SQLite: closing any other
|
|
8
|
+
* handle to it drops this process's lock without a word.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Presence {
|
|
11
|
+
#private;
|
|
12
|
+
private constructor();
|
|
13
|
+
/** The presence file of `caller`, whose record is at `record`. */
|
|
14
|
+
static pathOf(record: string, caller: Caller): string;
|
|
15
|
+
static claim(path: string): Presence;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the caller behind `path` still runs. Reading needs a lock that the
|
|
18
|
+
* caller's exclusive transaction refuses, and opening read-only never creates
|
|
19
|
+
* the file.
|
|
20
|
+
*/
|
|
21
|
+
static check(path: string): 'present' | 'gone' | 'missing';
|
|
22
|
+
/** Deletes the file of a presence that has ended. */
|
|
23
|
+
static delete(path: string): Promise<void>;
|
|
24
|
+
/** A record names a caller whose presence file does not exist, so nobody can tell whether it runs. */
|
|
25
|
+
static missing(record: string, path: string): Error;
|
|
26
|
+
/** Lets the kernel lock go. The file stays for whoever removes the caller's record. */
|
|
27
|
+
end(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Removes the caller's record, then ends this presence and deletes its file.
|
|
30
|
+
* A record that cannot be removed stays, but this presence ends anyway, so
|
|
31
|
+
* the record reads as gone and a waiter evicts it.
|
|
32
|
+
*/
|
|
33
|
+
releaseAfter(removeRecord: () => Promise<void>): Promise<void>;
|
|
34
|
+
/** Ends a presence whose record never appeared, or is gone already. */
|
|
35
|
+
withdraw(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/presence.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAK1C;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAInB,OAAO;IAKP,kEAAkE;IAClE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIrD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;IAcpC;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS;IAoB1D,qDAAqD;WACxC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,sGAAsG;IACtG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK;IAMnD,uFAAuF;IACvF,GAAG,IAAI,IAAI;IAMX;;;;OAIG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,uEAAuE;IACjE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAIhC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { unlink } from 'node:fs/promises';
|
|
3
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
+
import { patiently } from "../../shared/fs/patiently.js";
|
|
5
|
+
import { isBusy } from "../../shared/sqlite/is-busy.js";
|
|
6
|
+
import { isCantOpen } from "../../shared/sqlite/is-cant-open.js";
|
|
7
|
+
/** Garbage collection closes a connection nobody references, which would end a presence its caller still holds. */
|
|
8
|
+
const open = new Set();
|
|
9
|
+
/**
|
|
10
|
+
* A caller's presence: an exclusive SQLite transaction on a file of its own,
|
|
11
|
+
* held from before the caller's record (a lock file or a ticket) appears until
|
|
12
|
+
* after it is gone. The kernel ends it when the caller's process or thread
|
|
13
|
+
* stops, however it stops, so a waiter that can read the file knows the caller
|
|
14
|
+
* is gone. Nothing may open the file except through SQLite: closing any other
|
|
15
|
+
* handle to it drops this process's lock without a word.
|
|
16
|
+
*/
|
|
17
|
+
export class Presence {
|
|
18
|
+
#path;
|
|
19
|
+
#database;
|
|
20
|
+
constructor(path, database) {
|
|
21
|
+
this.#path = path;
|
|
22
|
+
this.#database = database;
|
|
23
|
+
}
|
|
24
|
+
/** The presence file of `caller`, whose record is at `record`. */
|
|
25
|
+
static pathOf(record, caller) {
|
|
26
|
+
return `${record}.${caller.id}.presence`;
|
|
27
|
+
}
|
|
28
|
+
static claim(path) {
|
|
29
|
+
const database = new DatabaseSync(path, { timeout: 0 });
|
|
30
|
+
try {
|
|
31
|
+
// A journal on disk would outlive a caller that dies while present.
|
|
32
|
+
database.exec('PRAGMA journal_mode = MEMORY');
|
|
33
|
+
database.exec('BEGIN EXCLUSIVE');
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
database.close();
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
open.add(database);
|
|
40
|
+
return new Presence(path, database);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether the caller behind `path` still runs. Reading needs a lock that the
|
|
44
|
+
* caller's exclusive transaction refuses, and opening read-only never creates
|
|
45
|
+
* the file.
|
|
46
|
+
*/
|
|
47
|
+
static check(path) {
|
|
48
|
+
let database;
|
|
49
|
+
try {
|
|
50
|
+
database = new DatabaseSync(path, { readOnly: true, timeout: 0 });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A file that exists but cannot be opened is a fault, not a missing presence.
|
|
54
|
+
if (isCantOpen(error) && !existsSync(path))
|
|
55
|
+
return 'missing';
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
database.prepare('SELECT count(*) FROM sqlite_schema').get();
|
|
60
|
+
return 'gone';
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isBusy(error))
|
|
64
|
+
return 'present';
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
database.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Deletes the file of a presence that has ended. */
|
|
72
|
+
static async delete(path) {
|
|
73
|
+
try {
|
|
74
|
+
await patiently(() => unlink(path));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new Error(`The presence ${JSON.stringify(path)} has ended, but its file could not be deleted. The file locks nothing; delete it by hand.`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** A record names a caller whose presence file does not exist, so nobody can tell whether it runs. */
|
|
81
|
+
static missing(record, path) {
|
|
82
|
+
return new Error(`The lock record ${JSON.stringify(record)} names a caller with no presence file ${JSON.stringify(path)}. An older version of @zukhruf/mutex wrote the record, or someone deleted the file. Stop every process that uses this lock directory, then delete the record.`);
|
|
83
|
+
}
|
|
84
|
+
/** Lets the kernel lock go. The file stays for whoever removes the caller's record. */
|
|
85
|
+
end() {
|
|
86
|
+
open.delete(this.#database);
|
|
87
|
+
this.#database.exec('ROLLBACK');
|
|
88
|
+
this.#database.close();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Removes the caller's record, then ends this presence and deletes its file.
|
|
92
|
+
* A record that cannot be removed stays, but this presence ends anyway, so
|
|
93
|
+
* the record reads as gone and a waiter evicts it.
|
|
94
|
+
*/
|
|
95
|
+
async releaseAfter(removeRecord) {
|
|
96
|
+
try {
|
|
97
|
+
await removeRecord();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
this.end();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
this.end();
|
|
104
|
+
await Presence.delete(this.#path);
|
|
105
|
+
}
|
|
106
|
+
/** Ends a presence whose record never appeared, or is gone already. */
|
|
107
|
+
async withdraw() {
|
|
108
|
+
this.end();
|
|
109
|
+
await Presence.delete(this.#path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/presence.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { FileLockStore } from './file-lock-store.ts';
|
|
|
4
4
|
* lock once its ticket is first.
|
|
5
5
|
*
|
|
6
6
|
* Appends are atomic (O_APPEND), and the file is only ever replaced by the
|
|
7
|
-
* live head releasing or by the reclaim-lock holder evicting a
|
|
7
|
+
* live head releasing or by the reclaim-lock holder evicting a gone head, so
|
|
8
8
|
* two rewrites never race. A rewrite racing an append can drop that ticket;
|
|
9
9
|
* its waiter notices and appends it again.
|
|
10
10
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticket-queue-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ticket-queue-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,aAAa;;cACrC,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAWX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAoD5E"}
|