@zukhruf/mutex 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +155 -2
- package/dist/fencing/counter-token-source.d.ts +11 -0
- package/dist/fencing/counter-token-source.d.ts.map +1 -0
- package/dist/fencing/counter-token-source.js +13 -0
- package/dist/fencing/counter-token-source.js.map +1 -0
- package/dist/fencing/epoch-token-source.d.ts +13 -0
- package/dist/fencing/epoch-token-source.d.ts.map +1 -0
- package/dist/fencing/epoch-token-source.js +27 -0
- package/dist/fencing/epoch-token-source.js.map +1 -0
- package/dist/fencing/fencing-token.d.ts +12 -0
- package/dist/fencing/fencing-token.d.ts.map +1 -0
- package/dist/fencing/fencing-token.js +18 -0
- package/dist/fencing/fencing-token.js.map +1 -0
- package/dist/fencing/file-token-source.d.ts +13 -0
- package/dist/fencing/file-token-source.d.ts.map +1 -0
- package/dist/fencing/file-token-source.js +35 -0
- package/dist/fencing/file-token-source.js.map +1 -0
- package/dist/fencing/monotonic-clock-token-source.d.ts +12 -0
- package/dist/fencing/monotonic-clock-token-source.d.ts.map +1 -0
- package/dist/fencing/monotonic-clock-token-source.js +15 -0
- package/dist/fencing/monotonic-clock-token-source.js.map +1 -0
- package/dist/fencing/token-source.d.ts +9 -0
- package/dist/fencing/token-source.d.ts.map +1 -0
- package/dist/fencing/token-source.js +2 -0
- package/dist/fencing/token-source.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/leader-election/index.d.ts +3 -0
- package/dist/leader-election/index.d.ts.map +1 -0
- package/dist/leader-election/index.js +3 -0
- package/dist/leader-election/index.js.map +1 -0
- package/dist/leader-election/leader-election.d.ts +23 -0
- package/dist/leader-election/leader-election.d.ts.map +1 -0
- package/dist/leader-election/leader-election.js +77 -0
- package/dist/leader-election/leader-election.js.map +1 -0
- package/dist/leader-election/leadership.d.ts +14 -0
- package/dist/leader-election/leadership.d.ts.map +1 -0
- package/dist/leader-election/leadership.js +23 -0
- package/dist/leader-election/leadership.js.map +1 -0
- package/dist/lock-stores/file-system/file-lock-store.d.ts +35 -0
- package/dist/lock-stores/file-system/file-lock-store.d.ts.map +1 -0
- package/dist/lock-stores/file-system/file-lock-store.js +64 -0
- package/dist/lock-stores/file-system/file-lock-store.js.map +1 -0
- package/dist/lock-stores/file-system/lock-file-store.d.ts +11 -0
- package/dist/lock-stores/file-system/lock-file-store.d.ts.map +1 -0
- package/dist/lock-stores/file-system/lock-file-store.js +44 -0
- package/dist/lock-stores/file-system/lock-file-store.js.map +1 -0
- package/dist/lock-stores/file-system/owner.d.ts +16 -0
- package/dist/lock-stores/file-system/owner.d.ts.map +1 -0
- package/dist/lock-stores/file-system/owner.js +47 -0
- package/dist/lock-stores/file-system/owner.js.map +1 -0
- package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts +16 -0
- package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts.map +1 -0
- package/dist/lock-stores/file-system/ticket-queue-file-store.js +111 -0
- package/dist/lock-stores/file-system/ticket-queue-file-store.js.map +1 -0
- package/dist/lock-stores/ipc/child-process-connection.d.ts +18 -0
- package/dist/lock-stores/ipc/child-process-connection.d.ts.map +1 -0
- package/dist/lock-stores/ipc/child-process-connection.js +46 -0
- package/dist/lock-stores/ipc/child-process-connection.js.map +1 -0
- package/dist/lock-stores/ipc/ipc-lock-coordinator.d.ts +24 -0
- package/dist/lock-stores/ipc/ipc-lock-coordinator.d.ts.map +1 -0
- package/dist/lock-stores/ipc/ipc-lock-coordinator.js +27 -0
- package/dist/lock-stores/ipc/ipc-lock-coordinator.js.map +1 -0
- package/dist/lock-stores/ipc/ipc-store.d.ts +15 -0
- package/dist/lock-stores/ipc/ipc-store.d.ts.map +1 -0
- package/dist/lock-stores/ipc/ipc-store.js +34 -0
- package/dist/lock-stores/ipc/ipc-store.js.map +1 -0
- package/dist/lock-stores/ipc/process-channel-connection.d.ts +18 -0
- package/dist/lock-stores/ipc/process-channel-connection.d.ts.map +1 -0
- package/dist/lock-stores/ipc/process-channel-connection.js +52 -0
- package/dist/lock-stores/ipc/process-channel-connection.js.map +1 -0
- package/dist/lock-stores/memory/memory-store.d.ts +14 -0
- package/dist/lock-stores/memory/memory-store.d.ts.map +1 -0
- package/dist/lock-stores/memory/memory-store.js +41 -0
- package/dist/lock-stores/memory/memory-store.js.map +1 -0
- package/dist/lock-stores/remote/connection.d.ts +17 -0
- package/dist/lock-stores/remote/connection.d.ts.map +1 -0
- package/dist/lock-stores/remote/connection.js +2 -0
- package/dist/lock-stores/remote/connection.js.map +1 -0
- package/dist/lock-stores/remote/connector.d.ts +12 -0
- package/dist/lock-stores/remote/connector.d.ts.map +1 -0
- package/dist/lock-stores/remote/connector.js +2 -0
- package/dist/lock-stores/remote/connector.js.map +1 -0
- package/dist/lock-stores/remote/coordinator-unavailable-error.d.ts +6 -0
- package/dist/lock-stores/remote/coordinator-unavailable-error.d.ts.map +1 -0
- package/dist/lock-stores/remote/coordinator-unavailable-error.js +10 -0
- package/dist/lock-stores/remote/coordinator-unavailable-error.js.map +1 -0
- package/dist/lock-stores/remote/envelope.d.ts +7 -0
- package/dist/lock-stores/remote/envelope.d.ts.map +1 -0
- package/dist/lock-stores/remote/envelope.js +11 -0
- package/dist/lock-stores/remote/envelope.js.map +1 -0
- package/dist/lock-stores/remote/lock-coordinator.d.ts +34 -0
- package/dist/lock-stores/remote/lock-coordinator.d.ts.map +1 -0
- package/dist/lock-stores/remote/lock-coordinator.js +150 -0
- package/dist/lock-stores/remote/lock-coordinator.js.map +1 -0
- package/dist/lock-stores/remote/protocol.d.ts +36 -0
- package/dist/lock-stores/remote/protocol.d.ts.map +1 -0
- package/dist/lock-stores/remote/protocol.js +33 -0
- package/dist/lock-stores/remote/protocol.js.map +1 -0
- package/dist/lock-stores/remote/remote-lock-client.d.ts +17 -0
- package/dist/lock-stores/remote/remote-lock-client.d.ts.map +1 -0
- package/dist/lock-stores/remote/remote-lock-client.js +172 -0
- package/dist/lock-stores/remote/remote-lock-client.js.map +1 -0
- package/dist/lock-stores/socket/electing-connector.d.ts +21 -0
- package/dist/lock-stores/socket/electing-connector.d.ts.map +1 -0
- package/dist/lock-stores/socket/electing-connector.js +45 -0
- package/dist/lock-stores/socket/electing-connector.js.map +1 -0
- package/dist/lock-stores/socket/lock-server.d.ts +23 -0
- package/dist/lock-stores/socket/lock-server.d.ts.map +1 -0
- package/dist/lock-stores/socket/lock-server.js +67 -0
- package/dist/lock-stores/socket/lock-server.js.map +1 -0
- package/dist/lock-stores/socket/socket-connection.d.ts +18 -0
- package/dist/lock-stores/socket/socket-connection.d.ts.map +1 -0
- package/dist/lock-stores/socket/socket-connection.js +54 -0
- package/dist/lock-stores/socket/socket-connection.js.map +1 -0
- package/dist/lock-stores/socket/socket-store.d.ts +35 -0
- package/dist/lock-stores/socket/socket-store.d.ts.map +1 -0
- package/dist/lock-stores/socket/socket-store.js +84 -0
- package/dist/lock-stores/socket/socket-store.js.map +1 -0
- package/dist/lock-stores/sqlite/sqlite-store.d.ts +21 -0
- package/dist/lock-stores/sqlite/sqlite-store.d.ts.map +1 -0
- package/dist/lock-stores/sqlite/sqlite-store.js +105 -0
- package/dist/lock-stores/sqlite/sqlite-store.js.map +1 -0
- package/dist/lock-stores/thread/parent-port-connection.d.ts +20 -0
- package/dist/lock-stores/thread/parent-port-connection.d.ts.map +1 -0
- package/dist/lock-stores/thread/parent-port-connection.js +44 -0
- package/dist/lock-stores/thread/parent-port-connection.js.map +1 -0
- package/dist/lock-stores/thread/thread-lock-coordinator.d.ts +22 -0
- package/dist/lock-stores/thread/thread-lock-coordinator.d.ts.map +1 -0
- package/dist/lock-stores/thread/thread-lock-coordinator.js +25 -0
- package/dist/lock-stores/thread/thread-lock-coordinator.js.map +1 -0
- package/dist/lock-stores/thread/thread-store.d.ts +13 -0
- package/dist/lock-stores/thread/thread-store.d.ts.map +1 -0
- package/dist/lock-stores/thread/thread-store.js +37 -0
- package/dist/lock-stores/thread/thread-store.js.map +1 -0
- package/dist/lock-stores/thread/worker-connection.d.ts +19 -0
- package/dist/lock-stores/thread/worker-connection.d.ts.map +1 -0
- package/dist/lock-stores/thread/worker-connection.js +45 -0
- package/dist/lock-stores/thread/worker-connection.js.map +1 -0
- package/dist/mutex/acquire-mode.d.ts +30 -0
- package/dist/mutex/acquire-mode.d.ts.map +1 -0
- package/dist/mutex/acquire-mode.js +2 -0
- package/dist/mutex/acquire-mode.js.map +1 -0
- package/dist/mutex/acquire-modes/modes.d.ts +9 -0
- package/dist/mutex/acquire-modes/modes.d.ts.map +1 -0
- package/dist/mutex/acquire-modes/modes.js +9 -0
- package/dist/mutex/acquire-modes/modes.js.map +1 -0
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts +15 -0
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts.map +1 -0
- package/dist/mutex/acquire-modes/skip-if-busy-mode.js +27 -0
- package/dist/mutex/acquire-modes/skip-if-busy-mode.js.map +1 -0
- package/dist/mutex/acquire-modes/wait-mode.d.ts +9 -0
- package/dist/mutex/acquire-modes/wait-mode.d.ts.map +1 -0
- package/dist/mutex/acquire-modes/wait-mode.js +8 -0
- package/dist/mutex/acquire-modes/wait-mode.js.map +1 -0
- package/dist/mutex/key.d.ts +22 -0
- package/dist/mutex/key.d.ts.map +1 -0
- package/dist/mutex/key.js +21 -0
- package/dist/mutex/key.js.map +1 -0
- package/dist/mutex/lease.d.ts +9 -0
- package/dist/mutex/lease.d.ts.map +1 -0
- package/dist/mutex/lease.js +12 -0
- package/dist/mutex/lease.js.map +1 -0
- package/dist/mutex/lock-lost-error.d.ts +6 -0
- package/dist/mutex/lock-lost-error.d.ts.map +1 -0
- package/dist/mutex/lock-lost-error.js +10 -0
- package/dist/mutex/lock-lost-error.js.map +1 -0
- package/dist/mutex/lock-store.d.ts +13 -0
- package/dist/mutex/lock-store.d.ts.map +1 -0
- package/dist/mutex/lock-store.js +2 -0
- package/dist/mutex/lock-store.js.map +1 -0
- package/dist/mutex/mutex.d.ts +25 -0
- package/dist/mutex/mutex.d.ts.map +1 -0
- package/dist/mutex/mutex.js +41 -0
- package/dist/mutex/mutex.js.map +1 -0
- package/dist/shared/fs/atomic-write.d.ts +3 -0
- package/dist/shared/fs/atomic-write.d.ts.map +1 -0
- package/dist/shared/fs/atomic-write.js +34 -0
- package/dist/shared/fs/atomic-write.js.map +1 -0
- package/dist/shared/fs/create-exclusive.d.ts +6 -0
- package/dist/shared/fs/create-exclusive.d.ts.map +1 -0
- package/dist/shared/fs/create-exclusive.js +24 -0
- package/dist/shared/fs/create-exclusive.js.map +1 -0
- package/dist/shared/fs/errno.d.ts +2 -0
- package/dist/shared/fs/errno.d.ts.map +1 -0
- package/dist/shared/fs/errno.js +4 -0
- package/dist/shared/fs/errno.js.map +1 -0
- package/dist/shared/fs/safe-file-name.d.ts +8 -0
- package/dist/shared/fs/safe-file-name.d.ts.map +1 -0
- package/dist/shared/fs/safe-file-name.js +10 -0
- package/dist/shared/fs/safe-file-name.js.map +1 -0
- package/dist/shared/is-record.d.ts +3 -0
- package/dist/shared/is-record.d.ts.map +1 -0
- package/dist/shared/is-record.js +3 -0
- package/dist/shared/is-record.js.map +1 -0
- package/dist/shared/sqlite/is-busy.d.ts +3 -0
- package/dist/shared/sqlite/is-busy.d.ts.map +1 -0
- package/dist/shared/sqlite/is-busy.js +10 -0
- package/dist/shared/sqlite/is-busy.js.map +1 -0
- package/dist/shared/until-aborted.d.ts +3 -0
- package/dist/shared/until-aborted.d.ts.map +1 -0
- package/dist/shared/until-aborted.js +19 -0
- package/dist/shared/until-aborted.js.map +1 -0
- package/package.json +50 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { FencingToken } from "../../fencing/fencing-token.js";
|
|
3
|
+
import { LockLostError } from "../../mutex/lock-lost-error.js";
|
|
4
|
+
import { untilAborted } from "../../shared/until-aborted.js";
|
|
5
|
+
import { CoordinatorUnavailableError } from "./coordinator-unavailable-error.js";
|
|
6
|
+
/**
|
|
7
|
+
* Asks a coordinator for keys over a connection. When the connection drops, it
|
|
8
|
+
* asks its connector for another, reasserts the keys it holds and re-requests
|
|
9
|
+
* the ones it waits for. A held key whose reassertion is refused is lost.
|
|
10
|
+
*/
|
|
11
|
+
export class RemoteLockClient {
|
|
12
|
+
#connector;
|
|
13
|
+
#pending = new Map();
|
|
14
|
+
#held = new Map();
|
|
15
|
+
#lost = new Set();
|
|
16
|
+
#connection;
|
|
17
|
+
#current;
|
|
18
|
+
#closed = false;
|
|
19
|
+
constructor(connector) {
|
|
20
|
+
this.#connector = connector;
|
|
21
|
+
}
|
|
22
|
+
async acquire(key, { signal } = {}) {
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
const id = randomUUID();
|
|
25
|
+
try {
|
|
26
|
+
const token = await untilAborted(this.#request(id, key, 'acquire'), signal);
|
|
27
|
+
// The coordinator answers 'busy' only to a try; an acquire waits for its grant.
|
|
28
|
+
if (!token) {
|
|
29
|
+
throw new Error(`The lock coordinator answered 'busy' to an acquire of key "${key}".`);
|
|
30
|
+
}
|
|
31
|
+
return this.#hold(id, key, token);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (signal?.aborted && this.#current) {
|
|
35
|
+
void this.#send(this.#current, { op: 'cancel', id });
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
this.#pending.delete(id);
|
|
41
|
+
this.#updateRef();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async tryAcquire(key) {
|
|
45
|
+
const id = randomUUID();
|
|
46
|
+
try {
|
|
47
|
+
const token = await this.#request(id, key, 'try');
|
|
48
|
+
return token && this.#hold(id, key, token);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
this.#pending.delete(id);
|
|
52
|
+
this.#updateRef();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async #request(id, key, attempt) {
|
|
56
|
+
if (this.#closed)
|
|
57
|
+
throw new Error('This lock client is closed.');
|
|
58
|
+
const answer = Promise.withResolvers();
|
|
59
|
+
this.#pending.set(id, { key, attempt, answer });
|
|
60
|
+
this.#updateRef();
|
|
61
|
+
this.#connection ??= this.#open();
|
|
62
|
+
const connection = await this.#connection;
|
|
63
|
+
if (!connection)
|
|
64
|
+
throw new CoordinatorUnavailableError(key);
|
|
65
|
+
void this.#send(connection, { op: attempt, id, key });
|
|
66
|
+
return answer.promise;
|
|
67
|
+
}
|
|
68
|
+
#hold(id, key, token) {
|
|
69
|
+
this.#held.set(id, { key, token });
|
|
70
|
+
return {
|
|
71
|
+
token,
|
|
72
|
+
[Symbol.asyncDispose]: async () => {
|
|
73
|
+
// Forget the key first, so a reconnect cannot reassert a released lease.
|
|
74
|
+
const wasHeld = this.#held.delete(id);
|
|
75
|
+
if (this.#lost.delete(id))
|
|
76
|
+
throw new LockLostError(key);
|
|
77
|
+
if (wasHeld && this.#current) {
|
|
78
|
+
await this.#send(this.#current, { op: 'release', id });
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async #open() {
|
|
84
|
+
const connection = await this.#connector.connect();
|
|
85
|
+
if (!connection) {
|
|
86
|
+
this.#connection = undefined;
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
this.#current = connection;
|
|
90
|
+
connection.listen({
|
|
91
|
+
message: (response) => this.#receive(connection, response),
|
|
92
|
+
close: () => this.#reconnect(connection),
|
|
93
|
+
});
|
|
94
|
+
this.#updateRef();
|
|
95
|
+
return connection;
|
|
96
|
+
}
|
|
97
|
+
#receive(connection, response) {
|
|
98
|
+
const pending = this.#pending.get(response.id);
|
|
99
|
+
switch (response.op) {
|
|
100
|
+
case 'granted':
|
|
101
|
+
if (pending) {
|
|
102
|
+
pending.answer.resolve(new FencingToken(BigInt(response.token)));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Nobody waits for this grant any more (the request was cancelled), so give the key back.
|
|
106
|
+
void this.#send(connection, { op: 'release', id: response.id });
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
case 'busy':
|
|
110
|
+
pending?.answer.resolve(undefined);
|
|
111
|
+
return;
|
|
112
|
+
case 'rejected':
|
|
113
|
+
if (this.#held.delete(response.id))
|
|
114
|
+
this.#lost.add(response.id);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async #send(connection, request) {
|
|
119
|
+
try {
|
|
120
|
+
await connection.send(request);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
this.#reconnect(connection);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Disconnects for good; the coordinator releases whatever this client still holds. */
|
|
127
|
+
close() {
|
|
128
|
+
this.#closed = true;
|
|
129
|
+
const current = this.#current;
|
|
130
|
+
this.#current = undefined;
|
|
131
|
+
current?.close();
|
|
132
|
+
}
|
|
133
|
+
#reconnect(lost) {
|
|
134
|
+
if (this.#closed || this.#current !== lost)
|
|
135
|
+
return;
|
|
136
|
+
this.#current = undefined;
|
|
137
|
+
lost.close();
|
|
138
|
+
this.#connection = this.#open();
|
|
139
|
+
void this.#connection.then((connection) => this.#resume(connection));
|
|
140
|
+
}
|
|
141
|
+
async #resume(connection) {
|
|
142
|
+
if (!connection) {
|
|
143
|
+
// Held keys stay exclusive: no coordinator is left to grant them to anyone else.
|
|
144
|
+
for (const { key, answer } of this.#pending.values()) {
|
|
145
|
+
answer.reject(new CoordinatorUnavailableError(key));
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
for (const [id, { key, token }] of this.#held) {
|
|
150
|
+
await this.#send(connection, {
|
|
151
|
+
op: 'reassert',
|
|
152
|
+
id,
|
|
153
|
+
key,
|
|
154
|
+
token: token.toString(),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
for (const [id, { key, attempt, answer }] of this.#pending) {
|
|
158
|
+
// A try is one attempt; the coordinator that would have answered it is gone.
|
|
159
|
+
if (attempt === 'try')
|
|
160
|
+
answer.resolve(undefined);
|
|
161
|
+
else
|
|
162
|
+
await this.#send(connection, { op: 'acquire', id, key });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
#updateRef() {
|
|
166
|
+
if (this.#pending.size > 0)
|
|
167
|
+
this.#current?.ref();
|
|
168
|
+
else
|
|
169
|
+
this.#current?.unref();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=remote-lock-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-lock-client.js","sourceRoot":"","sources":["../../../src/lock-stores/remote/remote-lock-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LeaderElection } from '../../leader-election/leader-election.ts';
|
|
2
|
+
import type { Leadership } from '../../leader-election/leadership.ts';
|
|
3
|
+
import type { ClientConnection, Connector } from '../remote/connector.ts';
|
|
4
|
+
export interface ElectingConnectorOptions {
|
|
5
|
+
socketPath: string;
|
|
6
|
+
election: LeaderElection;
|
|
7
|
+
pollInterval: number;
|
|
8
|
+
/** Starts serving for a term this process just won. */
|
|
9
|
+
serve(leadership: Leadership): Promise<void>;
|
|
10
|
+
connected(): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reaches the leader's socket, and when nobody serves it, campaigns to become
|
|
14
|
+
* the leader. Never gives up: a lost leader is always replaced by a candidate.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ElectingConnector implements Connector {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(options: ElectingConnectorOptions);
|
|
19
|
+
connect(): Promise<ClientConnection>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=electing-connector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electing-connector.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/socket/electing-connector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAQ1E,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,SAAS,IAAI,IAAI,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,SAAS;;gBAGrC,OAAO,EAAE,wBAAwB;IAIvC,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAiB3C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { connect } from 'node:net';
|
|
2
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
+
import { isLockResponse, } from "../remote/protocol.js";
|
|
4
|
+
import { SocketConnection } from "./socket-connection.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reaches the leader's socket, and when nobody serves it, campaigns to become
|
|
7
|
+
* the leader. Never gives up: a lost leader is always replaced by a candidate.
|
|
8
|
+
*/
|
|
9
|
+
export class ElectingConnector {
|
|
10
|
+
#options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.#options = options;
|
|
13
|
+
}
|
|
14
|
+
async connect() {
|
|
15
|
+
const { socketPath, election, pollInterval, serve, connected } = this.#options;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const socket = await reach(socketPath);
|
|
18
|
+
if (socket) {
|
|
19
|
+
connected();
|
|
20
|
+
return new SocketConnection(socket, isLockResponse);
|
|
21
|
+
}
|
|
22
|
+
const leadership = await election.campaign({ timeout: pollInterval });
|
|
23
|
+
if (leadership)
|
|
24
|
+
await serve(leadership);
|
|
25
|
+
else
|
|
26
|
+
await delay(pollInterval);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Resolves `undefined` when nothing listens yet (no file, or a dead leader's file). */
|
|
31
|
+
function reach(socketPath) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const socket = connect(socketPath);
|
|
34
|
+
const fail = () => {
|
|
35
|
+
socket.destroy();
|
|
36
|
+
resolve(undefined);
|
|
37
|
+
};
|
|
38
|
+
socket.once('error', fail);
|
|
39
|
+
socket.once('connect', () => {
|
|
40
|
+
socket.off('error', fail);
|
|
41
|
+
resolve(socket);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=electing-connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electing-connector.js","sourceRoot":"","sources":["../../../src/lock-stores/socket/electing-connector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAK3D,OAAO,EAGL,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW1D;;;GAGG;AACH,MAAM,OAAO,iBAAiB;IACnB,QAAQ,CAA2B;IAE5C,YAAY,OAAiC;QAC3C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAC5D,IAAI,CAAC,QAAQ,CAAC;QAChB,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;YACvC,IAAI,MAAM,EAAE,CAAC;gBACX,SAAS,EAAE,CAAC;gBACZ,OAAO,IAAI,gBAAgB,CACzB,MAAM,EACN,cAAc,CACf,CAAC;YACJ,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;YACtE,IAAI,UAAU;gBAAE,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;;gBACnC,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;CACF;AAED,wFAAwF;AACxF,SAAS,KAAK,CAAC,UAAkB;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC1B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC1B,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Leadership } from '../../leader-election/leadership.ts';
|
|
2
|
+
export interface LockServerOptions {
|
|
3
|
+
/** Milliseconds the new term grants nothing, so holders from the last term can reassert. */
|
|
4
|
+
graceWindow: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Serves a `LockCoordinator` on a Unix socket for one leadership term. Tokens
|
|
8
|
+
* carry the term's epoch, so they outrank every token of earlier terms.
|
|
9
|
+
*/
|
|
10
|
+
export declare class LockServer {
|
|
11
|
+
#private;
|
|
12
|
+
private constructor();
|
|
13
|
+
static start(socketPath: string, leadership: Leadership, { graceWindow }: LockServerOptions): Promise<LockServer>;
|
|
14
|
+
/**
|
|
15
|
+
* Hands leadership over. Dropping every connection tells followers to elect
|
|
16
|
+
* a successor and reassert their keys during its grace window; `close` alone
|
|
17
|
+
* would wait for followers that never disconnect on their own. The term ends
|
|
18
|
+
* only after the socket is gone: in the other order, this close could remove
|
|
19
|
+
* a successor's socket file.
|
|
20
|
+
*/
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=lock-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-server.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/socket/lock-server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AAUtE,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,UAAU;;IAKrB,OAAO;WAUM,KAAK,CAChB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,UAAU,EACtB,EAAE,WAAW,EAAE,EAAE,iBAAiB,GACjC,OAAO,CAAC,UAAU,CAAC;IAiCtB;;;;;;OAMG;IACG,KAAK;CAQZ"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { unlink } from 'node:fs/promises';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import { EpochTokenSource } from "../../fencing/epoch-token-source.js";
|
|
4
|
+
import { isErrno } from "../../shared/fs/errno.js";
|
|
5
|
+
import { LockCoordinator } from "../remote/lock-coordinator.js";
|
|
6
|
+
import { isLockRequest, } from "../remote/protocol.js";
|
|
7
|
+
import { SocketConnection } from "./socket-connection.js";
|
|
8
|
+
/**
|
|
9
|
+
* Serves a `LockCoordinator` on a Unix socket for one leadership term. Tokens
|
|
10
|
+
* carry the term's epoch, so they outrank every token of earlier terms.
|
|
11
|
+
*/
|
|
12
|
+
export class LockServer {
|
|
13
|
+
#server;
|
|
14
|
+
#connections;
|
|
15
|
+
#leadership;
|
|
16
|
+
constructor(server, connections, leadership) {
|
|
17
|
+
this.#server = server;
|
|
18
|
+
this.#connections = connections;
|
|
19
|
+
this.#leadership = leadership;
|
|
20
|
+
}
|
|
21
|
+
static async start(socketPath, leadership, { graceWindow }) {
|
|
22
|
+
// Only the leader gets here, so removing a dead leader's socket file cannot race another server.
|
|
23
|
+
// Windows removes a named pipe when its process stops, so there is no file to remove.
|
|
24
|
+
if (process.platform !== 'win32') {
|
|
25
|
+
await unlink(socketPath).catch((error) => {
|
|
26
|
+
if (!isErrno(error, 'ENOENT'))
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const coordinator = new LockCoordinator({
|
|
31
|
+
tokens: new EpochTokenSource(leadership.epoch),
|
|
32
|
+
graceWindow,
|
|
33
|
+
});
|
|
34
|
+
const connections = new Set();
|
|
35
|
+
// Serving others never keeps the leader alive: when its own work ends, it exits and another process takes over.
|
|
36
|
+
const server = createServer((socket) => {
|
|
37
|
+
socket.unref();
|
|
38
|
+
connections.add(socket);
|
|
39
|
+
socket.once('close', () => connections.delete(socket));
|
|
40
|
+
coordinator.serve(new SocketConnection(socket, isLockRequest));
|
|
41
|
+
});
|
|
42
|
+
await new Promise((resolve, reject) => {
|
|
43
|
+
server.once('error', reject);
|
|
44
|
+
server.listen(socketPath, () => {
|
|
45
|
+
server.off('error', reject);
|
|
46
|
+
resolve();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
server.unref();
|
|
50
|
+
return new LockServer(server, connections, leadership);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Hands leadership over. Dropping every connection tells followers to elect
|
|
54
|
+
* a successor and reassert their keys during its grace window; `close` alone
|
|
55
|
+
* would wait for followers that never disconnect on their own. The term ends
|
|
56
|
+
* only after the socket is gone: in the other order, this close could remove
|
|
57
|
+
* a successor's socket file.
|
|
58
|
+
*/
|
|
59
|
+
async close() {
|
|
60
|
+
const closed = new Promise((resolve) => this.#server.close(() => resolve()));
|
|
61
|
+
for (const socket of this.#connections)
|
|
62
|
+
socket.destroy();
|
|
63
|
+
await closed;
|
|
64
|
+
await this.#leadership.resign();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=lock-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-server.js","sourceRoot":"","sources":["../../../src/lock-stores/socket/lock-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Socket } from 'node:net';
|
|
2
|
+
import type { Connection, ConnectionHandlers } from '../remote/connection.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Newline-delimited JSON over a stream socket. A stream has no message
|
|
5
|
+
* boundaries (writes arrive merged and split), and JSON escapes newlines inside
|
|
6
|
+
* strings, so one line is always one message. `isIncoming` checks each message
|
|
7
|
+
* from the peer; a line that is not one closes the connection.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SocketConnection<Outgoing, Incoming> implements Connection<Outgoing, Incoming> {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(socket: Socket, isIncoming: (message: unknown) => message is Incoming);
|
|
12
|
+
send(message: Outgoing): Promise<void>;
|
|
13
|
+
listen({ message, close }: ConnectionHandlers<Incoming>): void;
|
|
14
|
+
ref(): void;
|
|
15
|
+
unref(): void;
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=socket-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-connection.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/socket/socket-connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAE,YAAW,UAAU,CACrE,QAAQ,EACR,QAAQ,CACT;;gBAKG,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,IAAI,QAAQ;IAQvD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,kBAAkB,CAAC,QAAQ,CAAC;IAgBvD,GAAG;IAIH,KAAK;IAIL,KAAK;CAGN"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
/**
|
|
3
|
+
* Newline-delimited JSON over a stream socket. A stream has no message
|
|
4
|
+
* boundaries (writes arrive merged and split), and JSON escapes newlines inside
|
|
5
|
+
* strings, so one line is always one message. `isIncoming` checks each message
|
|
6
|
+
* from the peer; a line that is not one closes the connection.
|
|
7
|
+
*/
|
|
8
|
+
export class SocketConnection {
|
|
9
|
+
#socket;
|
|
10
|
+
#isIncoming;
|
|
11
|
+
constructor(socket, isIncoming) {
|
|
12
|
+
this.#socket = socket;
|
|
13
|
+
this.#isIncoming = isIncoming;
|
|
14
|
+
// Every error is followed by `close`, which is where the peer's loss is handled.
|
|
15
|
+
socket.on('error', () => { });
|
|
16
|
+
}
|
|
17
|
+
send(message) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
if (!this.#socket.writable) {
|
|
20
|
+
reject(new Error('The socket is closed.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.#socket.write(`${JSON.stringify(message)}\n`, (error) => error ? reject(error) : resolve());
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
listen({ message, close }) {
|
|
27
|
+
createInterface({ input: this.#socket, crlfDelay: Infinity })
|
|
28
|
+
.on('line', (line) => {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(line);
|
|
31
|
+
if (this.#isIncoming(parsed))
|
|
32
|
+
message(parsed);
|
|
33
|
+
else
|
|
34
|
+
this.close();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.close();
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
// readline re-emits socket errors (e.g. EPIPE when the peer died); `close` reports the loss.
|
|
41
|
+
.on('error', () => { });
|
|
42
|
+
this.#socket.once('close', close);
|
|
43
|
+
}
|
|
44
|
+
ref() {
|
|
45
|
+
this.#socket.ref();
|
|
46
|
+
}
|
|
47
|
+
unref() {
|
|
48
|
+
this.#socket.unref();
|
|
49
|
+
}
|
|
50
|
+
close() {
|
|
51
|
+
this.#socket.destroy();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=socket-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-connection.js","sourceRoot":"","sources":["../../../src/lock-stores/socket/socket-connection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIhD;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAIlB,OAAO,CAAS;IAChB,WAAW,CAA4C;IAEhE,YACE,MAAc,EACd,UAAqD;QAErD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,iFAAiF;QACjF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,CAAC,OAAiB;QACpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;gBAC3C,OAAO;YACT,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAC3D,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAClC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAgC;QACrD,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;aAC1D,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACnB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACzC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAAE,OAAO,CAAC,MAAM,CAAC,CAAC;;oBACzC,IAAI,CAAC,KAAK,EAAE,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;QACH,CAAC,CAAC;YACF,6FAA6F;aAC5F,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,GAAG;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Lease } from '../../mutex/lease.ts';
|
|
3
|
+
import type { AcquireOptions, LockStore } from '../../mutex/lock-store.ts';
|
|
4
|
+
export type SocketRole = 'leader' | 'follower';
|
|
5
|
+
export interface SocketStoreOptions {
|
|
6
|
+
/** Milliseconds between connection and campaign attempts. */
|
|
7
|
+
pollInterval?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Milliseconds a new leader grants nothing, so holders from the previous
|
|
10
|
+
* term can reassert first. Must exceed how long a holder takes to reconnect.
|
|
11
|
+
*/
|
|
12
|
+
graceWindow?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Host-wide locks granted by one elected process over a Unix socket in
|
|
16
|
+
* `directory`. Every process using the store is a candidate: the first one to
|
|
17
|
+
* need a lock with no leader serving wins the election and serves the rest
|
|
18
|
+
* until it exits. When the leader dies, the kernel closes every connection,
|
|
19
|
+
* a survivor takes over, and holders reassert their keys during the new
|
|
20
|
+
* leader's grace window. Tokens carry the term's epoch, so a holder that
|
|
21
|
+
* missed the window (for example while frozen) is fenced off by any resource
|
|
22
|
+
* that checks tokens, and its lease ends with `LockLostError`.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SocketStore extends EventEmitter<{
|
|
25
|
+
role: [SocketRole];
|
|
26
|
+
}> implements LockStore, AsyncDisposable {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(directory: string, { pollInterval, graceWindow }?: SocketStoreOptions);
|
|
29
|
+
/** Whether this process serves the locks, or `undefined` before it first needed one. */
|
|
30
|
+
get role(): SocketRole | undefined;
|
|
31
|
+
acquire(key: string, options?: AcquireOptions): Promise<Lease>;
|
|
32
|
+
tryAcquire(key: string): Promise<Lease | undefined>;
|
|
33
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=socket-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/socket/socket-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAQ3E,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,qBAAa,WACX,SAAQ,YAAY,CAAC;IAAE,IAAI,EAAE,CAAC,UAAU,CAAC,CAAA;CAAE,CAC3C,YAAW,SAAS,EAAE,eAAe;;gBAOnC,SAAS,EAAE,MAAM,EACjB,EAAE,YAAiB,EAAE,WAAiB,EAAE,GAAE,kBAAuB;IAuBnE,wFAAwF;IACxF,IAAI,IAAI,IAAI,UAAU,GAAG,SAAS,CAEjC;IAED,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAI7C,CAAC,MAAM,CAAC,YAAY,CAAC;CAU5B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { LeaderElection } from "../../leader-election/leader-election.js";
|
|
5
|
+
import { RemoteLockClient } from "../remote/remote-lock-client.js";
|
|
6
|
+
import { ElectingConnector } from "./electing-connector.js";
|
|
7
|
+
import { LockServer } from "./lock-server.js";
|
|
8
|
+
/** macOS allows 104 bytes including the terminating NUL; Linux allows 108. */
|
|
9
|
+
const SOCKET_PATH_LIMIT = 103;
|
|
10
|
+
/**
|
|
11
|
+
* Host-wide locks granted by one elected process over a Unix socket in
|
|
12
|
+
* `directory`. Every process using the store is a candidate: the first one to
|
|
13
|
+
* need a lock with no leader serving wins the election and serves the rest
|
|
14
|
+
* until it exits. When the leader dies, the kernel closes every connection,
|
|
15
|
+
* a survivor takes over, and holders reassert their keys during the new
|
|
16
|
+
* leader's grace window. Tokens carry the term's epoch, so a holder that
|
|
17
|
+
* missed the window (for example while frozen) is fenced off by any resource
|
|
18
|
+
* that checks tokens, and its lease ends with `LockLostError`.
|
|
19
|
+
*/
|
|
20
|
+
export class SocketStore extends EventEmitter {
|
|
21
|
+
#client;
|
|
22
|
+
#server;
|
|
23
|
+
#role;
|
|
24
|
+
constructor(directory, { pollInterval = 10, graceWindow = 500 } = {}) {
|
|
25
|
+
super();
|
|
26
|
+
const socketPath = socketPathFor(directory);
|
|
27
|
+
this.#client = new RemoteLockClient(new ElectingConnector({
|
|
28
|
+
socketPath,
|
|
29
|
+
election: new LeaderElection(directory, { pollInterval }),
|
|
30
|
+
pollInterval,
|
|
31
|
+
serve: async (leadership) => {
|
|
32
|
+
this.#server = await LockServer.start(socketPath, leadership, {
|
|
33
|
+
// The first term of a directory has no predecessor to wait for.
|
|
34
|
+
graceWindow: leadership.epoch > 1n ? graceWindow : 0,
|
|
35
|
+
});
|
|
36
|
+
this.#setRole('leader');
|
|
37
|
+
},
|
|
38
|
+
connected: () => {
|
|
39
|
+
if (!this.#server)
|
|
40
|
+
this.#setRole('follower');
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** Whether this process serves the locks, or `undefined` before it first needed one. */
|
|
45
|
+
get role() {
|
|
46
|
+
return this.#role;
|
|
47
|
+
}
|
|
48
|
+
acquire(key, options) {
|
|
49
|
+
return this.#client.acquire(key, options);
|
|
50
|
+
}
|
|
51
|
+
tryAcquire(key) {
|
|
52
|
+
return this.#client.tryAcquire(key);
|
|
53
|
+
}
|
|
54
|
+
async [Symbol.asyncDispose]() {
|
|
55
|
+
this.#client.close();
|
|
56
|
+
await this.#server?.close();
|
|
57
|
+
}
|
|
58
|
+
#setRole(role) {
|
|
59
|
+
if (this.#role === role)
|
|
60
|
+
return;
|
|
61
|
+
this.#role = role;
|
|
62
|
+
this.emit('role', role);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A Unix socket file in the directory on macOS and Linux. On Windows, a named
|
|
67
|
+
* pipe: pipe names are global, so the name comes from the directory, and
|
|
68
|
+
* Windows paths ignore case, so the path is lowercased first.
|
|
69
|
+
*/
|
|
70
|
+
function socketPathFor(directory) {
|
|
71
|
+
if (process.platform === 'win32') {
|
|
72
|
+
const id = createHash('sha256')
|
|
73
|
+
.update(resolve(directory).toLowerCase())
|
|
74
|
+
.digest('hex')
|
|
75
|
+
.slice(0, 32);
|
|
76
|
+
return `\\\\.\\pipe\\mutex-${id}`;
|
|
77
|
+
}
|
|
78
|
+
const socketPath = join(directory, 'lock.sock');
|
|
79
|
+
if (Buffer.byteLength(socketPath) > SOCKET_PATH_LIMIT) {
|
|
80
|
+
throw new RangeError(`The socket path ${socketPath} exceeds ${SOCKET_PATH_LIMIT} bytes; choose a shorter directory.`);
|
|
81
|
+
}
|
|
82
|
+
return socketPath;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=socket-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-store.js","sourceRoot":"","sources":["../../../src/lock-stores/socket/socket-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Lease } from '../../mutex/lease.ts';
|
|
2
|
+
import type { AcquireOptions } from '../../mutex/lock-store.ts';
|
|
3
|
+
import { FileLockStore } from '../file-system/file-lock-store.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Holds an exclusive SQLite transaction on the key's database file. SQLite
|
|
6
|
+
* locks through the kernel, which releases them when the holder dies, so a
|
|
7
|
+
* crash never leaves a key locked. Keep the default rollback journal: in WAL
|
|
8
|
+
* mode an exclusive transaction no longer blocks readers.
|
|
9
|
+
*
|
|
10
|
+
* Callers in this process first line up in a queue in memory, so only the
|
|
11
|
+
* first one opens a database connection: one open file per key per process,
|
|
12
|
+
* however many callers wait, and first-come order within the process.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SqliteStore extends FileLockStore {
|
|
15
|
+
#private;
|
|
16
|
+
acquire(key: string, options?: AcquireOptions): Promise<Lease>;
|
|
17
|
+
tryAcquire(key: string): Promise<Lease | undefined>;
|
|
18
|
+
protected lock(path: string, signal: AbortSignal | undefined): Promise<AsyncDisposable>;
|
|
19
|
+
protected tryLock(path: string): Promise<AsyncDisposable | undefined>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=sqlite-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/sqlite/sqlite-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE;;;;;;;;;GASG;AACH,qBAAa,WAAY,SAAQ,aAAa;;IAG7B,OAAO,CACpB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,KAAK,CAAC;IAWF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;cAclD,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAaX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAW5E"}
|