@zukhruf/mutex 0.2.0 → 0.3.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/README.md +4 -2
- package/dist/fencing/file-token-source.js +2 -2
- package/dist/fencing/file-token-source.js.map +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/leader-election/index.d.ts +1 -0
- package/dist/leader-election/index.d.ts.map +1 -1
- package/dist/leader-election/index.js +1 -0
- package/dist/leader-election/index.js.map +1 -1
- package/dist/leader-election/leader-election.d.ts.map +1 -1
- package/dist/leader-election/leader-election.js +4 -2
- package/dist/leader-election/leader-election.js.map +1 -1
- package/dist/local-directory/local-directory.d.ts +7 -0
- package/dist/local-directory/local-directory.d.ts.map +1 -0
- package/dist/local-directory/local-directory.js +75 -0
- package/dist/local-directory/local-directory.js.map +1 -0
- package/dist/local-directory/network-directory-error.d.ts +8 -0
- package/dist/local-directory/network-directory-error.d.ts.map +1 -0
- package/dist/local-directory/network-directory-error.js +13 -0
- package/dist/local-directory/network-directory-error.js.map +1 -0
- package/dist/lock-stores/file-system/file-lock-store.d.ts.map +1 -1
- package/dist/lock-stores/file-system/file-lock-store.js +4 -1
- 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 +9 -4
- package/dist/lock-stores/file-system/lock-file-store.js.map +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 +3 -2
- package/dist/lock-stores/file-system/ticket-queue-file-store.js.map +1 -1
- package/dist/lock-stores/ipc/child-process-connection.d.ts +3 -3
- package/dist/lock-stores/ipc/child-process-connection.d.ts.map +1 -1
- package/dist/lock-stores/ipc/child-process-connection.js +16 -20
- package/dist/lock-stores/ipc/child-process-connection.js.map +1 -1
- package/dist/lock-stores/ipc/ipc-store.d.ts.map +1 -1
- package/dist/lock-stores/ipc/ipc-store.js +7 -5
- package/dist/lock-stores/ipc/ipc-store.js.map +1 -1
- package/dist/lock-stores/ipc/process-channel-connection.d.ts +4 -3
- package/dist/lock-stores/ipc/process-channel-connection.d.ts.map +1 -1
- package/dist/lock-stores/ipc/process-channel-connection.js +6 -14
- package/dist/lock-stores/ipc/process-channel-connection.js.map +1 -1
- package/dist/lock-stores/memory/memory-store.d.ts +1 -1
- package/dist/lock-stores/memory/memory-store.d.ts.map +1 -1
- package/dist/lock-stores/memory/memory-store.js +13 -14
- package/dist/lock-stores/memory/memory-store.js.map +1 -1
- package/dist/lock-stores/remote/connection-supervisor.d.ts +39 -0
- package/dist/lock-stores/remote/connection-supervisor.d.ts.map +1 -0
- package/dist/lock-stores/remote/connection-supervisor.js +195 -0
- package/dist/lock-stores/remote/connection-supervisor.js.map +1 -0
- package/dist/lock-stores/remote/connection.d.ts +11 -7
- package/dist/lock-stores/remote/connection.d.ts.map +1 -1
- package/dist/lock-stores/remote/connector.d.ts +5 -2
- package/dist/lock-stores/remote/connector.d.ts.map +1 -1
- package/dist/lock-stores/remote/lock-coordinator.d.ts +2 -2
- package/dist/lock-stores/remote/lock-coordinator.d.ts.map +1 -1
- package/dist/lock-stores/remote/lock-coordinator.js +140 -69
- package/dist/lock-stores/remote/lock-coordinator.js.map +1 -1
- package/dist/lock-stores/remote/remote-lock-client.d.ts +8 -7
- package/dist/lock-stores/remote/remote-lock-client.d.ts.map +1 -1
- package/dist/lock-stores/remote/remote-lock-client.js +99 -99
- package/dist/lock-stores/remote/remote-lock-client.js.map +1 -1
- package/dist/lock-stores/socket/electing-connector.d.ts +7 -4
- package/dist/lock-stores/socket/electing-connector.d.ts.map +1 -1
- package/dist/lock-stores/socket/electing-connector.js +35 -8
- package/dist/lock-stores/socket/electing-connector.js.map +1 -1
- package/dist/lock-stores/socket/local-directory-connector.d.ts +14 -0
- package/dist/lock-stores/socket/local-directory-connector.d.ts.map +1 -0
- package/dist/lock-stores/socket/local-directory-connector.js +23 -0
- package/dist/lock-stores/socket/local-directory-connector.js.map +1 -0
- package/dist/lock-stores/socket/socket-connection.d.ts +3 -3
- package/dist/lock-stores/socket/socket-connection.d.ts.map +1 -1
- package/dist/lock-stores/socket/socket-connection.js +16 -18
- package/dist/lock-stores/socket/socket-connection.js.map +1 -1
- package/dist/lock-stores/socket/socket-store.d.ts +1 -2
- package/dist/lock-stores/socket/socket-store.d.ts.map +1 -1
- package/dist/lock-stores/socket/socket-store.js +16 -23
- package/dist/lock-stores/socket/socket-store.js.map +1 -1
- package/dist/lock-stores/thread/parent-port-connection.d.ts +4 -3
- package/dist/lock-stores/thread/parent-port-connection.d.ts.map +1 -1
- package/dist/lock-stores/thread/parent-port-connection.js +6 -13
- package/dist/lock-stores/thread/parent-port-connection.js.map +1 -1
- package/dist/lock-stores/thread/thread-store.d.ts.map +1 -1
- package/dist/lock-stores/thread/thread-store.js +5 -8
- package/dist/lock-stores/thread/thread-store.js.map +1 -1
- package/dist/lock-stores/thread/worker-connection.d.ts +3 -3
- package/dist/lock-stores/thread/worker-connection.d.ts.map +1 -1
- package/dist/lock-stores/thread/worker-connection.js +19 -23
- package/dist/lock-stores/thread/worker-connection.js.map +1 -1
- package/dist/mutex/acquire-mode.d.ts +6 -3
- package/dist/mutex/acquire-mode.d.ts.map +1 -1
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts +7 -3
- package/dist/mutex/acquire-modes/skip-if-busy-mode.d.ts.map +1 -1
- package/dist/mutex/acquire-modes/skip-if-busy-mode.js +7 -4
- package/dist/mutex/acquire-modes/skip-if-busy-mode.js.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/acquire-modes/wait-mode.js +2 -2
- package/dist/mutex/acquire-modes/wait-mode.js.map +1 -1
- package/dist/mutex/key.d.ts +3 -2
- package/dist/mutex/key.d.ts.map +1 -1
- package/dist/mutex/key.js +3 -3
- package/dist/mutex/key.js.map +1 -1
- package/dist/mutex/mutex.d.ts +3 -3
- package/dist/mutex/mutex.d.ts.map +1 -1
- package/dist/mutex/mutex.js +28 -5
- package/dist/mutex/mutex.js.map +1 -1
- package/dist/shared/fs/atomic-write.d.ts.map +1 -1
- package/dist/shared/fs/atomic-write.js +3 -27
- package/dist/shared/fs/atomic-write.js.map +1 -1
- package/dist/shared/fs/create-exclusive.d.ts.map +1 -1
- package/dist/shared/fs/create-exclusive.js +7 -2
- package/dist/shared/fs/create-exclusive.js.map +1 -1
- package/dist/shared/fs/durable-write.d.ts +7 -0
- package/dist/shared/fs/durable-write.d.ts.map +1 -0
- package/dist/shared/fs/durable-write.js +46 -0
- package/dist/shared/fs/durable-write.js.map +1 -0
- package/dist/shared/fs/patiently.d.ts +3 -0
- package/dist/shared/fs/patiently.d.ts.map +1 -0
- package/dist/shared/fs/patiently.js +30 -0
- package/dist/shared/fs/patiently.js.map +1 -0
- package/dist/shared/fs/replace-file.d.ts +8 -0
- package/dist/shared/fs/replace-file.d.ts.map +1 -0
- package/dist/shared/fs/replace-file.js +12 -0
- package/dist/shared/fs/replace-file.js.map +1 -0
- package/dist/shared/latch.d.ts +10 -0
- package/dist/shared/latch.d.ts.map +1 -0
- package/dist/shared/latch.js +14 -0
- package/dist/shared/latch.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
1
|
import { FencingToken } from "../../fencing/fencing-token.js";
|
|
2
|
+
import { Latch } from "../../shared/latch.js";
|
|
3
3
|
import { untilAborted } from "../../shared/until-aborted.js";
|
|
4
4
|
import { MemoryStore } from "../memory/memory-store.js";
|
|
5
5
|
/**
|
|
@@ -7,29 +7,29 @@ import { MemoryStore } from "../memory/memory-store.js";
|
|
|
7
7
|
* connections. A peer that disconnects releases everything it held or waited for.
|
|
8
8
|
*/
|
|
9
9
|
export class LockCoordinator {
|
|
10
|
-
#
|
|
11
|
-
#graceOver;
|
|
12
|
-
#reassertions = new Map();
|
|
13
|
-
#inGrace;
|
|
10
|
+
#phase;
|
|
14
11
|
constructor({ tokens, graceWindow = 0 }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
const store = new MemoryStore({ tokens });
|
|
13
|
+
const granting = new Granting(store);
|
|
14
|
+
if (graceWindow > 0) {
|
|
15
|
+
const over = new Latch();
|
|
16
|
+
this.#phase = new GraceWindow(store, over, granting);
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
// Granting is current before the latch lets the waiters resume.
|
|
19
|
+
this.#phase = granting;
|
|
20
|
+
over.open();
|
|
21
|
+
}, graceWindow).unref();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.#phase = granting;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async acquire(key, options = {}) {
|
|
28
|
+
return this.#phase.acquire(key, options);
|
|
27
29
|
}
|
|
28
30
|
/** Nothing is granted during the grace window, so a key counts as busy then. */
|
|
29
31
|
async tryAcquire(key) {
|
|
30
|
-
|
|
31
|
-
return undefined;
|
|
32
|
-
return this.#store.tryAcquire(key);
|
|
32
|
+
return this.#phase.tryAcquire(key);
|
|
33
33
|
}
|
|
34
34
|
serve(connection) {
|
|
35
35
|
new Session(this, connection);
|
|
@@ -37,39 +37,70 @@ export class LockCoordinator {
|
|
|
37
37
|
/**
|
|
38
38
|
* Re-registers a holder from before a failover. Accepted only during the
|
|
39
39
|
* grace window; for two claims on one key the newer token wins and the older
|
|
40
|
-
* claim's `lose` is called.
|
|
40
|
+
* claim's `lose` is called. Returns `undefined` when the claim is refused.
|
|
41
41
|
*/
|
|
42
42
|
reassert(key, token, lose) {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
return this.#phase.reassert(key, token, lose);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** The only phase that grants. A holder that reasserts now is too late. */
|
|
47
|
+
class Granting {
|
|
48
|
+
#store;
|
|
49
|
+
constructor(store) {
|
|
50
|
+
this.#store = store;
|
|
51
|
+
}
|
|
52
|
+
acquire(key, { signal }) {
|
|
53
|
+
return this.#store.acquire(key, { signal });
|
|
54
|
+
}
|
|
55
|
+
tryAcquire(key) {
|
|
56
|
+
return this.#store.tryAcquire(key);
|
|
57
|
+
}
|
|
58
|
+
reassert() {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Grants nothing, so holders from a previous coordinator can reassert their keys first. */
|
|
63
|
+
class GraceWindow {
|
|
64
|
+
#store;
|
|
65
|
+
#over;
|
|
66
|
+
#next;
|
|
67
|
+
#reassertions = new Map();
|
|
68
|
+
constructor(store, over, next) {
|
|
69
|
+
this.#store = store;
|
|
70
|
+
this.#over = over;
|
|
71
|
+
this.#next = next;
|
|
72
|
+
}
|
|
73
|
+
async acquire(key, { signal }) {
|
|
74
|
+
await untilAborted(this.#over.wait(), signal);
|
|
75
|
+
return this.#next.acquire(key, { signal });
|
|
76
|
+
}
|
|
77
|
+
async tryAcquire() {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
reassert(key, token, lose) {
|
|
45
81
|
const existing = this.#reassertions.get(key);
|
|
46
82
|
if (existing && !token.isNewerThan(existing.token))
|
|
47
83
|
return undefined;
|
|
84
|
+
// Waiters join the line only after grace, so this claim is next once the
|
|
85
|
+
// claim it outranks gives the key back, even if that claim already let it go.
|
|
86
|
+
const lease = this.#store.acquire(key);
|
|
48
87
|
existing?.lose();
|
|
49
|
-
|
|
50
|
-
// and a newer claim takes over that same lease.
|
|
51
|
-
const lease = existing?.lease ?? this.#store.acquire(key);
|
|
52
|
-
this.#reassertions.set(key, { token, lease, lose });
|
|
88
|
+
this.#reassertions.set(key, { token, lose });
|
|
53
89
|
return lease;
|
|
54
90
|
}
|
|
55
91
|
}
|
|
56
92
|
class Session {
|
|
57
93
|
#coordinator;
|
|
58
|
-
#connection;
|
|
59
|
-
#leases = new Map();
|
|
60
94
|
#requested = new Set();
|
|
61
|
-
#
|
|
62
|
-
#closed = false;
|
|
95
|
+
#phase;
|
|
63
96
|
constructor(coordinator, connection) {
|
|
64
97
|
this.#coordinator = coordinator;
|
|
65
|
-
this.#
|
|
66
|
-
connection.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
void this.#close();
|
|
72
|
-
},
|
|
98
|
+
this.#phase = new Serving(connection);
|
|
99
|
+
connection.on('message', (request) => {
|
|
100
|
+
this.#handle(request).catch(() => connection.close());
|
|
101
|
+
});
|
|
102
|
+
connection.once('close', () => {
|
|
103
|
+
void this.#end();
|
|
73
104
|
});
|
|
74
105
|
}
|
|
75
106
|
async #handle(request) {
|
|
@@ -80,7 +111,7 @@ class Session {
|
|
|
80
111
|
return;
|
|
81
112
|
this.#requested.add(request.id);
|
|
82
113
|
const lease = await this.#coordinator.acquire(request.key);
|
|
83
|
-
return this.#grant(request.id
|
|
114
|
+
return this.#grant(lease, request.id);
|
|
84
115
|
}
|
|
85
116
|
case 'try': {
|
|
86
117
|
if (this.#requested.has(request.id))
|
|
@@ -89,62 +120,102 @@ class Session {
|
|
|
89
120
|
const lease = await this.#coordinator.tryAcquire(request.key);
|
|
90
121
|
if (!lease) {
|
|
91
122
|
this.#requested.delete(request.id);
|
|
92
|
-
return this.#reply({ op: 'busy', id: request.id });
|
|
123
|
+
return this.#phase.reply({ op: 'busy', id: request.id });
|
|
93
124
|
}
|
|
94
|
-
return this.#grant(request.id
|
|
125
|
+
return this.#grant(lease, request.id);
|
|
95
126
|
}
|
|
96
127
|
case 'cancel': {
|
|
97
128
|
// A request still in line is released by #grant when its turn comes.
|
|
98
129
|
this.#requested.delete(request.id);
|
|
99
|
-
return this.#
|
|
130
|
+
return this.#phase.release(request.id);
|
|
100
131
|
}
|
|
101
132
|
case 'release': {
|
|
102
133
|
this.#requested.delete(request.id);
|
|
103
|
-
return this.#
|
|
134
|
+
return this.#phase.release(request.id);
|
|
104
135
|
}
|
|
105
136
|
case 'reassert': {
|
|
137
|
+
// Registered like any request, so a release that arrives before the claim resolves gives the key back.
|
|
138
|
+
if (this.#requested.has(request.id))
|
|
139
|
+
return;
|
|
140
|
+
this.#requested.add(request.id);
|
|
106
141
|
const claim = this.#coordinator.reassert(request.key, new FencingToken(BigInt(request.token)), () => this.#lose(request.id));
|
|
107
|
-
if (!claim)
|
|
108
|
-
|
|
142
|
+
if (!claim) {
|
|
143
|
+
this.#requested.delete(request.id);
|
|
144
|
+
return this.#phase.reply({ op: 'rejected', id: request.id });
|
|
145
|
+
}
|
|
109
146
|
const lease = await claim;
|
|
110
|
-
if (this.#
|
|
111
|
-
return;
|
|
112
|
-
if (this.#closed)
|
|
147
|
+
if (!this.#requested.has(request.id)) {
|
|
113
148
|
return lease[Symbol.asyncDispose]();
|
|
114
|
-
|
|
115
|
-
return;
|
|
149
|
+
}
|
|
150
|
+
return this.#phase.keep(lease, request.id);
|
|
116
151
|
}
|
|
117
152
|
}
|
|
118
153
|
}
|
|
119
|
-
/** A request cancelled
|
|
120
|
-
#grant(
|
|
121
|
-
if (
|
|
154
|
+
/** A request cancelled while the grant was on its way releases the key at once. */
|
|
155
|
+
#grant(lease, id) {
|
|
156
|
+
if (!this.#requested.has(id))
|
|
122
157
|
return lease[Symbol.asyncDispose]();
|
|
123
|
-
|
|
124
|
-
this.#leases.set(id, lease);
|
|
125
|
-
return this.#reply({ op: 'granted', id, token: lease.token.toString() });
|
|
158
|
+
return this.#phase.grant(lease, id);
|
|
126
159
|
}
|
|
127
|
-
|
|
128
|
-
const lease = this.#leases.get(id);
|
|
129
|
-
this.#leases.delete(id);
|
|
130
|
-
return lease?.[Symbol.asyncDispose]();
|
|
131
|
-
}
|
|
132
|
-
/** A newer claim took this session's reasserted lease over, so it must not release it. */
|
|
160
|
+
/** A newer claim outranked this session's reassertion, so the key goes to that claim next. */
|
|
133
161
|
#lose(id) {
|
|
134
|
-
|
|
135
|
-
this.#
|
|
136
|
-
|
|
162
|
+
// A holder that already released has nothing to give back and nothing to learn.
|
|
163
|
+
if (!this.#requested.delete(id))
|
|
164
|
+
return;
|
|
165
|
+
this.#phase.lose(id);
|
|
137
166
|
}
|
|
138
|
-
|
|
167
|
+
/** Ends Serving before its leases are released, so a grant that arrives meanwhile is given back too. */
|
|
168
|
+
async #end() {
|
|
169
|
+
const serving = this.#phase;
|
|
170
|
+
this.#phase = new Ended();
|
|
171
|
+
await serving.end();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** The peer is connected: the session keeps its leases and answers it. */
|
|
175
|
+
class Serving {
|
|
176
|
+
#connection;
|
|
177
|
+
#leases = new Map();
|
|
178
|
+
constructor(connection) {
|
|
179
|
+
this.#connection = connection;
|
|
180
|
+
}
|
|
181
|
+
async reply(response) {
|
|
139
182
|
// A failed send means the peer is gone; `close` then releases its leases.
|
|
140
183
|
await this.#connection.send(response).catch(() => { });
|
|
141
184
|
}
|
|
142
|
-
async
|
|
143
|
-
this.#
|
|
185
|
+
async grant(lease, id) {
|
|
186
|
+
this.#leases.set(id, lease);
|
|
187
|
+
await this.reply({ op: 'granted', id, token: lease.token.toString() });
|
|
188
|
+
}
|
|
189
|
+
async keep(lease, id) {
|
|
190
|
+
this.#leases.set(id, lease);
|
|
191
|
+
}
|
|
192
|
+
async release(id) {
|
|
193
|
+
const lease = this.#leases.get(id);
|
|
194
|
+
this.#leases.delete(id);
|
|
195
|
+
await lease?.[Symbol.asyncDispose]();
|
|
196
|
+
}
|
|
197
|
+
lose(id) {
|
|
198
|
+
void this.release(id);
|
|
199
|
+
void this.reply({ op: 'rejected', id });
|
|
200
|
+
}
|
|
201
|
+
async end() {
|
|
144
202
|
const leases = [...this.#leases.values()];
|
|
145
203
|
this.#leases.clear();
|
|
146
204
|
for (const lease of leases)
|
|
147
205
|
await lease[Symbol.asyncDispose]();
|
|
148
206
|
}
|
|
149
207
|
}
|
|
208
|
+
/** The peer is gone: a lease that still arrives is given back at once, and nothing is answered. */
|
|
209
|
+
class Ended {
|
|
210
|
+
async reply() { }
|
|
211
|
+
async grant(lease) {
|
|
212
|
+
await lease[Symbol.asyncDispose]();
|
|
213
|
+
}
|
|
214
|
+
async keep(lease) {
|
|
215
|
+
await lease[Symbol.asyncDispose]();
|
|
216
|
+
}
|
|
217
|
+
async release() { }
|
|
218
|
+
lose() { }
|
|
219
|
+
async end() { }
|
|
220
|
+
}
|
|
150
221
|
//# sourceMappingURL=lock-coordinator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-coordinator.js","sourceRoot":"","sources":["../../../src/lock-stores/remote/lock-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"lock-coordinator.js","sourceRoot":"","sources":["../../../src/lock-stores/remote/lock-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { Lease } from '../../mutex/lease.ts';
|
|
2
2
|
import type { AcquireOptions, LockStore } from '../../mutex/lock-store.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ConnectionSupervisor } from './connection-supervisor.ts';
|
|
4
|
+
import type { LockRequest, LockResponse } from './protocol.ts';
|
|
4
5
|
/**
|
|
5
|
-
* Asks a coordinator for keys over a connection. When the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Asks a coordinator for keys over a supervised connection. When the
|
|
7
|
+
* connection is replaced, it reasserts the keys it holds and asks again for the
|
|
8
|
+
* ones it waits for. A held key whose reassertion is refused is lost.
|
|
8
9
|
*/
|
|
9
10
|
export declare class RemoteLockClient implements LockStore {
|
|
10
11
|
#private;
|
|
11
|
-
constructor(
|
|
12
|
+
constructor(link: ConnectionSupervisor<LockRequest, LockResponse>);
|
|
12
13
|
acquire(key: string, { signal }?: AcquireOptions): Promise<Lease>;
|
|
13
14
|
tryAcquire(key: string): Promise<Lease | undefined>;
|
|
14
|
-
/** Disconnects for good
|
|
15
|
-
close(): void
|
|
15
|
+
/** Disconnects for good. Over a socket, the coordinator then releases whatever this client still holds. */
|
|
16
|
+
close(): Promise<void>;
|
|
16
17
|
}
|
|
17
18
|
//# sourceMappingURL=remote-lock-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-lock-client.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/remote/remote-lock-client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"remote-lock-client.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/remote/remote-lock-client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAmB/D;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,SAAS;;gBAMpC,IAAI,EAAE,oBAAoB,CAAC,WAAW,EAAE,YAAY,CAAC;IAY3D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAmBrE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAMzD,2GAA2G;IACrG,KAAK;CA0HZ"}
|
|
@@ -4,26 +4,30 @@ import { LockLostError } from "../../mutex/lock-lost-error.js";
|
|
|
4
4
|
import { untilAborted } from "../../shared/until-aborted.js";
|
|
5
5
|
import { CoordinatorUnavailableError } from "./coordinator-unavailable-error.js";
|
|
6
6
|
/**
|
|
7
|
-
* Asks a coordinator for keys over a connection. When the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Asks a coordinator for keys over a supervised connection. When the
|
|
8
|
+
* connection is replaced, it reasserts the keys it holds and asks again for the
|
|
9
|
+
* ones it waits for. A held key whose reassertion is refused is lost.
|
|
10
10
|
*/
|
|
11
11
|
export class RemoteLockClient {
|
|
12
|
-
#
|
|
12
|
+
#link;
|
|
13
13
|
#pending = new Map();
|
|
14
14
|
#held = new Map();
|
|
15
15
|
#lost = new Set();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.#
|
|
16
|
+
constructor(link) {
|
|
17
|
+
this.#link = link;
|
|
18
|
+
link.on('connected', () => this.#resume());
|
|
19
|
+
link.on('message', (response) => this.#receive(response));
|
|
20
|
+
link.on('disconnected', () => this.#interrupt());
|
|
21
|
+
// Held keys stay exclusive: no coordinator is left to grant them to anyone else.
|
|
22
|
+
link.on('unavailable', () => this.#rejectAll((key) => new CoordinatorUnavailableError(key)));
|
|
23
|
+
link.on('failed', (error) => this.#fail(error));
|
|
21
24
|
}
|
|
22
25
|
async acquire(key, { signal } = {}) {
|
|
23
26
|
signal?.throwIfAborted();
|
|
24
27
|
const id = randomUUID();
|
|
28
|
+
const { answer } = this.#request(id, key, 'acquire');
|
|
25
29
|
try {
|
|
26
|
-
const token = await untilAborted(
|
|
30
|
+
const token = await untilAborted(answer.promise, signal);
|
|
27
31
|
// The coordinator answers 'busy' only to a try; an acquire waits for its grant.
|
|
28
32
|
if (!token) {
|
|
29
33
|
throw new Error(`The lock coordinator answered 'busy' to an acquire of key "${key}".`);
|
|
@@ -31,39 +35,52 @@ export class RemoteLockClient {
|
|
|
31
35
|
return this.#hold(id, key, token);
|
|
32
36
|
}
|
|
33
37
|
catch (error) {
|
|
34
|
-
if (signal?.aborted
|
|
35
|
-
|
|
36
|
-
}
|
|
38
|
+
if (signal?.aborted)
|
|
39
|
+
this.#withdraw(id);
|
|
37
40
|
throw error;
|
|
38
41
|
}
|
|
39
|
-
finally {
|
|
40
|
-
this.#pending.delete(id);
|
|
41
|
-
this.#updateRef();
|
|
42
|
-
}
|
|
43
42
|
}
|
|
44
43
|
async tryAcquire(key) {
|
|
45
44
|
const id = randomUUID();
|
|
46
|
-
try
|
|
47
|
-
|
|
48
|
-
return token && this.#hold(id, key, token);
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
this.#pending.delete(id);
|
|
52
|
-
this.#updateRef();
|
|
53
|
-
}
|
|
45
|
+
const token = await this.#request(id, key, 'try').answer.promise;
|
|
46
|
+
return token && this.#hold(id, key, token);
|
|
54
47
|
}
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
/** Disconnects for good. Over a socket, the coordinator then releases whatever this client still holds. */
|
|
49
|
+
async close() {
|
|
50
|
+
const closing = this.#link.close();
|
|
51
|
+
this.#rejectAll(() => new Error('This lock client is closed.'));
|
|
52
|
+
await closing;
|
|
53
|
+
}
|
|
54
|
+
#request(id, key, attempt) {
|
|
55
|
+
if (this.#link.status === 'closed') {
|
|
57
56
|
throw new Error('This lock client is closed.');
|
|
58
|
-
|
|
59
|
-
this.#
|
|
60
|
-
this.#updateRef();
|
|
61
|
-
this.#connection ??= this.#open();
|
|
62
|
-
const connection = await this.#connection;
|
|
63
|
-
if (!connection)
|
|
57
|
+
}
|
|
58
|
+
if (this.#link.status === 'unavailable') {
|
|
64
59
|
throw new CoordinatorUnavailableError(key);
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
}
|
|
61
|
+
const pending = {
|
|
62
|
+
key,
|
|
63
|
+
attempt,
|
|
64
|
+
delivery: 'queued',
|
|
65
|
+
answer: Promise.withResolvers(),
|
|
66
|
+
};
|
|
67
|
+
this.#pending.set(id, pending);
|
|
68
|
+
this.#updateRef();
|
|
69
|
+
this.#link.open();
|
|
70
|
+
this.#dispatch(id, pending);
|
|
71
|
+
return pending;
|
|
72
|
+
}
|
|
73
|
+
#dispatch(id, pending) {
|
|
74
|
+
const { key, attempt } = pending;
|
|
75
|
+
if (this.#link.send({ op: attempt, id, key }))
|
|
76
|
+
pending.delivery = 'sent';
|
|
77
|
+
}
|
|
78
|
+
/** The caller gave up. A grant already on its way is given back by the coordinator on `cancel`. */
|
|
79
|
+
#withdraw(id) {
|
|
80
|
+
const pending = this.#take(id);
|
|
81
|
+
if (!pending || pending.delivery === 'sent') {
|
|
82
|
+
this.#link.send({ op: 'cancel', id });
|
|
83
|
+
}
|
|
67
84
|
}
|
|
68
85
|
#hold(id, key, token) {
|
|
69
86
|
this.#held.set(id, { key, token });
|
|
@@ -74,40 +91,27 @@ export class RemoteLockClient {
|
|
|
74
91
|
const wasHeld = this.#held.delete(id);
|
|
75
92
|
if (this.#lost.delete(id))
|
|
76
93
|
throw new LockLostError(key);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
// Without an open connection there is nothing to tell: the coordinator that granted the key is gone.
|
|
95
|
+
if (wasHeld)
|
|
96
|
+
this.#link.send({ op: 'release', id });
|
|
80
97
|
},
|
|
81
98
|
};
|
|
82
99
|
}
|
|
83
|
-
|
|
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);
|
|
100
|
+
#receive(response) {
|
|
99
101
|
switch (response.op) {
|
|
100
|
-
case 'granted':
|
|
102
|
+
case 'granted': {
|
|
103
|
+
const pending = this.#take(response.id);
|
|
101
104
|
if (pending) {
|
|
102
105
|
pending.answer.resolve(new FencingToken(BigInt(response.token)));
|
|
103
106
|
}
|
|
104
107
|
else {
|
|
105
108
|
// Nobody waits for this grant any more (the request was cancelled), so give the key back.
|
|
106
|
-
|
|
109
|
+
this.#link.send({ op: 'release', id: response.id });
|
|
107
110
|
}
|
|
108
111
|
return;
|
|
112
|
+
}
|
|
109
113
|
case 'busy':
|
|
110
|
-
|
|
114
|
+
this.#take(response.id)?.answer.resolve(undefined);
|
|
111
115
|
return;
|
|
112
116
|
case 'rejected':
|
|
113
117
|
if (this.#held.delete(response.id))
|
|
@@ -115,58 +119,54 @@ export class RemoteLockClient {
|
|
|
115
119
|
return;
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
/** A new connection: reassert held keys first, then send what waits. */
|
|
123
|
+
#resume() {
|
|
124
|
+
// A new connection starts with its adapter's default, which may not match whether this client waits.
|
|
125
|
+
this.#updateRef();
|
|
126
|
+
for (const [id, { key, token }] of this.#held) {
|
|
127
|
+
this.#link.send({ op: 'reassert', id, key, token: token.toString() });
|
|
121
128
|
}
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
for (const [id, pending] of this.#pending) {
|
|
130
|
+
// A try is one attempt; the coordinator that would have answered it is gone.
|
|
131
|
+
if (pending.delivery === 'stranded')
|
|
132
|
+
this.#take(id)?.answer.resolve(undefined);
|
|
133
|
+
else if (pending.delivery === 'queued')
|
|
134
|
+
this.#dispatch(id, pending);
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
#interrupt() {
|
|
138
|
+
for (const pending of this.#pending.values()) {
|
|
139
|
+
if (pending.delivery !== 'sent')
|
|
140
|
+
continue;
|
|
141
|
+
pending.delivery = pending.attempt === 'try' ? 'stranded' : 'queued';
|
|
142
|
+
}
|
|
132
143
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.#
|
|
139
|
-
void this.#connection.then((connection) => this.#resume(connection));
|
|
144
|
+
/** No coordinator heard the reassertions, so another holder may be granted the held keys. */
|
|
145
|
+
#fail(error) {
|
|
146
|
+
for (const id of this.#held.keys())
|
|
147
|
+
this.#lost.add(id);
|
|
148
|
+
this.#held.clear();
|
|
149
|
+
this.#rejectAll(() => error);
|
|
140
150
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 });
|
|
151
|
+
#rejectAll(reason) {
|
|
152
|
+
for (const [id, { key }] of this.#pending) {
|
|
153
|
+
this.#take(id)?.answer.reject(reason(key));
|
|
163
154
|
}
|
|
164
155
|
}
|
|
156
|
+
/** Removes a request that is about to be answered, so a late grant for it is given back. */
|
|
157
|
+
#take(id) {
|
|
158
|
+
const pending = this.#pending.get(id);
|
|
159
|
+
if (!pending)
|
|
160
|
+
return undefined;
|
|
161
|
+
this.#pending.delete(id);
|
|
162
|
+
this.#updateRef();
|
|
163
|
+
return pending;
|
|
164
|
+
}
|
|
165
165
|
#updateRef() {
|
|
166
166
|
if (this.#pending.size > 0)
|
|
167
|
-
this.#
|
|
167
|
+
this.#link.ref();
|
|
168
168
|
else
|
|
169
|
-
this.#
|
|
169
|
+
this.#link.unref();
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
//# sourceMappingURL=remote-lock-client.js.map
|