@zukhruf/mutex 0.3.1 → 0.3.3
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 +23 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- 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 +4 -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 +2 -2
- 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 +85 -52
- package/dist/lock-stores/file-system/ticket-queue-file-store.js.map +1 -1
- package/dist/lock-stores/ipc/ipc-lock-coordinator.d.ts +3 -3
- package/dist/lock-stores/ipc/ipc-lock-coordinator.d.ts.map +1 -1
- package/dist/lock-stores/ipc/ipc-store.d.ts +3 -3
- package/dist/lock-stores/ipc/ipc-store.d.ts.map +1 -1
- package/dist/lock-stores/memory/memory-store.d.ts +3 -3
- package/dist/lock-stores/memory/memory-store.d.ts.map +1 -1
- package/dist/lock-stores/memory/memory-store.js.map +1 -1
- package/dist/lock-stores/remote/lock-coordinator.d.ts +4 -4
- package/dist/lock-stores/remote/lock-coordinator.d.ts.map +1 -1
- package/dist/lock-stores/remote/lock-coordinator.js.map +1 -1
- package/dist/lock-stores/remote/remote-lock-client.d.ts +3 -3
- package/dist/lock-stores/remote/remote-lock-client.d.ts.map +1 -1
- package/dist/lock-stores/remote/remote-lock-client.js +19 -13
- package/dist/lock-stores/remote/remote-lock-client.js.map +1 -1
- package/dist/lock-stores/socket/socket-store.d.ts +4 -4
- package/dist/lock-stores/socket/socket-store.d.ts.map +1 -1
- package/dist/lock-stores/socket/socket-store.js +1 -1
- package/dist/lock-stores/sqlite/sqlite-store.d.ts +3 -3
- package/dist/lock-stores/sqlite/sqlite-store.d.ts.map +1 -1
- package/dist/lock-stores/sqlite/sqlite-store.js +4 -3
- package/dist/lock-stores/sqlite/sqlite-store.js.map +1 -1
- package/dist/lock-stores/thread/thread-lock-coordinator.d.ts +3 -3
- package/dist/lock-stores/thread/thread-lock-coordinator.d.ts.map +1 -1
- package/dist/lock-stores/thread/thread-store.d.ts +3 -3
- package/dist/lock-stores/thread/thread-store.d.ts.map +1 -1
- package/dist/mutex/acquire-mode.d.ts +2 -2
- package/dist/mutex/acquire-mode.d.ts.map +1 -1
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts +2 -2
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts.map +1 -1
- package/dist/mutex/acquire-modes/wait-mode.d.ts +2 -2
- package/dist/mutex/acquire-modes/wait-mode.d.ts.map +1 -1
- package/dist/mutex/lease.d.ts +14 -4
- package/dist/mutex/lease.d.ts.map +1 -1
- package/dist/mutex/lease.js +11 -2
- package/dist/mutex/lease.js.map +1 -1
- package/dist/mutex/lock-lost-error.d.ts +2 -2
- package/dist/mutex/lock-lost-error.d.ts.map +1 -1
- package/dist/mutex/lock-lost-error.js +3 -3
- package/dist/mutex/lock-lost-error.js.map +1 -1
- package/dist/mutex/lock-store.d.ts +3 -3
- package/dist/mutex/lock-store.d.ts.map +1 -1
- package/dist/mutex/mutex.d.ts.map +1 -1
- package/dist/mutex/mutex.js +19 -4
- package/dist/mutex/mutex.js.map +1 -1
- 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/package.json +2 -4
- 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
|
@@ -37,15 +37,15 @@ The full program is in the recipe [Stop two requests from selling the last item]
|
|
|
37
37
|
|
|
38
38
|
First find who writes to the resource. Then select the lock store with that [reach](./docs/concepts/reach.md).
|
|
39
39
|
|
|
40
|
-
| Lock store | Reach | Order | A holder stops
|
|
41
|
-
| ---------------------------------------------------------------- | ----------------------- | --------------------------------------- |
|
|
42
|
-
| [MemoryStore](./docs/stores/memory-store.md) | One object | First come, first served | The locks stop with the process
|
|
43
|
-
| [ThreadStore](./docs/stores/thread-store.md) | One process (threads) | First come, first served | Released when the worker exits
|
|
44
|
-
| [IpcStore](./docs/stores/ipc-store.md) | Parent and its children | First come, first served | Released in approximately 2 ms
|
|
45
|
-
| [TicketQueueFileStore](./docs/stores/ticket-queue-file-store.md) | One host | First come, first served | Released after a
|
|
46
|
-
| [LockFileStore](./docs/stores/lock-file-store.md) | One host | No order | Released after a
|
|
47
|
-
| [SqliteStore](./docs/stores/sqlite-store.md) | One host | First come, first served in one process | Released by the kernel
|
|
48
|
-
| [SocketStore](./docs/stores/socket-store.md) | One host | First come, first served | Released in approximately 2 ms
|
|
40
|
+
| Lock store | Reach | Order | A holder stops | Needs |
|
|
41
|
+
| ---------------------------------------------------------------- | ----------------------- | --------------------------------------- | --------------------------------------------- | --------------------------- |
|
|
42
|
+
| [MemoryStore](./docs/stores/memory-store.md) | One object | First come, first served | The locks stop with the process | Nothing |
|
|
43
|
+
| [ThreadStore](./docs/stores/thread-store.md) | One process (threads) | First come, first served | Released when the worker exits | `adopt(worker)` |
|
|
44
|
+
| [IpcStore](./docs/stores/ipc-store.md) | Parent and its children | First come, first served | Released in approximately 2 ms | `fork()` and `adopt(child)` |
|
|
45
|
+
| [TicketQueueFileStore](./docs/stores/ticket-queue-file-store.md) | One host | First come, first served | Released after a waiter finds that it stopped | A shared directory |
|
|
46
|
+
| [LockFileStore](./docs/stores/lock-file-store.md) | One host | No order | Released after a waiter finds that it stopped | A shared directory |
|
|
47
|
+
| [SqliteStore](./docs/stores/sqlite-store.md) | One host | First come, first served in one process | Released by the kernel | A shared directory |
|
|
48
|
+
| [SocketStore](./docs/stores/socket-store.md) | One host | First come, first served | Released in approximately 2 ms | A shared directory |
|
|
49
49
|
|
|
50
50
|
If you are not sure:
|
|
51
51
|
|
|
@@ -88,6 +88,19 @@ await mutex.acquire('product:42', async (lease) => {
|
|
|
88
88
|
});
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
Sometimes the lock store sees the loss, for example after a failover of `SocketStore`. Then the signal of the lease aborts with `LockLostError`, and the task can stop before its next write. When the task ends, the call rejects with `LockLostError`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await mutex.acquire('product:42', async (lease) => {
|
|
95
|
+
for (const order of orders) {
|
|
96
|
+
lease.signal.throwIfAborted();
|
|
97
|
+
await ship(order, lease.token);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The signal is a warning. A frozen holder can write before its lock store sees the loss, so the fencing token is still the protection.
|
|
103
|
+
|
|
91
104
|
See [Fencing tokens](./docs/concepts/fencing-tokens.md) and the recipe [Protect a database from stale holders](./docs/recipes/fence-a-database.md).
|
|
92
105
|
|
|
93
106
|
## Documentation
|
|
@@ -144,7 +157,7 @@ The tests run from `src/`, not from `dist/`: they start workers and child proces
|
|
|
144
157
|
|
|
145
158
|
```
|
|
146
159
|
src/
|
|
147
|
-
mutex/ Mutex, Key, acquire modes, Lease, LockStore, LockLostError
|
|
160
|
+
mutex/ Mutex, Key, acquire modes, Lease, LockHandle, LockStore, LockLostError
|
|
148
161
|
fencing/ fencing tokens and token sources
|
|
149
162
|
lock-stores/ one folder for each lock store
|
|
150
163
|
remote/ the coordinator and client that ThreadStore, IpcStore and SocketStore share
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Mutex } from './mutex/mutex.ts';
|
|
2
2
|
export { Key } from './mutex/key.ts';
|
|
3
|
-
export { leaseFor, type Lease } from './mutex/lease.ts';
|
|
3
|
+
export { leaseFor, type Lease, type LockHandle } from './mutex/lease.ts';
|
|
4
4
|
export type { AcquireOptions, LockStore } from './mutex/lock-store.ts';
|
|
5
5
|
export { LockLostError } from './mutex/lock-lost-error.ts';
|
|
6
6
|
export type { Acquired, AcquireMode, ModeResult, NotAcquired, Outcome, OutcomeResults, } from './mutex/acquire-mode.ts';
|
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;
|
|
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,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzE,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.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,
|
|
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,EAA+B,MAAM,kBAAkB,CAAC;AAEzE,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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TokenSource } from '../../fencing/token-source.ts';
|
|
2
|
-
import { type
|
|
2
|
+
import { type LockHandle } from '../../mutex/lease.ts';
|
|
3
3
|
import type { AcquireOptions, LockStore } from '../../mutex/lock-store.ts';
|
|
4
4
|
export interface FileLockStoreOptions {
|
|
5
5
|
/** Milliseconds a waiter sleeps between attempts. */
|
|
@@ -10,20 +10,18 @@ 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 {
|
|
18
16
|
#private;
|
|
19
17
|
constructor(directory: string, { pollInterval, tokens, }?: FileLockStoreOptions);
|
|
20
|
-
acquire(key: string, { signal }?: AcquireOptions): Promise<
|
|
21
|
-
tryAcquire(key: string): Promise<
|
|
18
|
+
acquire(key: string, { signal }?: AcquireOptions): Promise<LockHandle>;
|
|
19
|
+
tryAcquire(key: string): Promise<LockHandle | undefined>;
|
|
22
20
|
/** Holds the lock at `path`, waiting for other holders, until `signal` aborts. */
|
|
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
27
|
* same dead holder could each remove a lock the other had just taken. The
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,UAAU,EAAY,MAAM,sBAAsB,CAAC;AACjE,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,CACX,GAAG,EAAE,MAAM,EACX,EAAE,MAAM,EAAE,GAAE,cAAmB,GAC9B,OAAO,CAAC,UAAU,CAAC;IAMhB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAK9D,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"}
|
|
@@ -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();
|
|
@@ -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,kBAAkB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAErE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,
|
|
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,kBAAkB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAErE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAmB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAc1E,8FAA8F;AAC9F,MAAM,OAAgB,aAAa;IACxB,UAAU,CAAS;IACnB,aAAa,CAAS;IACtB,OAAO,CAAc;IAE9B,YACE,SAAiB,EACjB,EACE,YAAY,GAAG,EAAE,EACjB,MAAM,GAAG,IAAI,eAAe,CAAC,SAAS,CAAC,MACf,EAAE;QAE5B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,OAAO,CACX,GAAW,EACX,EAAE,MAAM,KAAqB,EAAE;QAE/B,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAW;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,OAAO,IAAI,IAAI,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;IAaS,KAAK,CAAC,IAAI,CAClB,OAAqC,EACrC,EAAE,MAAM,KAAkB,EAAE;QAE5B,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAC;YAC/B,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACzD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,EAAE,cAAc,EAAE,CAAC;gBACzB,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACO,KAAK,CAAC,eAAe,CAAC,IAAY,EAAE,IAAyB;QACrE,MAAM,WAAW,GAAG,GAAG,IAAI,UAAU,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC;gBAAE,OAAO;YACzC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;oBAAS,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC;CACF;AAED,6FAA6F;AAC7F,SAAS,KAAK,CAAC,OAAqB,EAAE,IAAY;IAChD,IAAI,CAAC;QACH,8EAA8E;QAC9E,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,kHAAkH,EAC1J,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -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":"AAOA,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"}
|