@zukhruf/single-flight 0.3.20 → 0.3.21
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 +26 -17
- package/dist/client/flight-client.d.ts +9 -1
- package/dist/client/flight-client.d.ts.map +1 -1
- package/dist/client/flight-client.js +86 -16
- package/dist/client/flight-client.js.map +1 -1
- package/dist/coordinator/flight-coordinator.d.ts +24 -12
- package/dist/coordinator/flight-coordinator.d.ts.map +1 -1
- package/dist/coordinator/flight-coordinator.js +92 -143
- package/dist/coordinator/flight-coordinator.js.map +1 -1
- package/dist/coordinator/flight-server.d.ts +7 -7
- package/dist/coordinator/flight-server.d.ts.map +1 -1
- package/dist/coordinator/flight-server.js +7 -13
- package/dist/coordinator/flight-server.js.map +1 -1
- package/dist/coordinator/flight.d.ts +13 -2
- package/dist/coordinator/flight.d.ts.map +1 -1
- package/dist/coordinator/flight.js +20 -4
- package/dist/coordinator/flight.js.map +1 -1
- package/dist/coordinator/parked-runs.d.ts +26 -0
- package/dist/coordinator/parked-runs.d.ts.map +1 -0
- package/dist/coordinator/parked-runs.js +65 -0
- package/dist/coordinator/parked-runs.js.map +1 -0
- package/dist/coordinator/party.d.ts +1 -1
- package/dist/coordinator/party.d.ts.map +1 -1
- package/dist/coordinator/session.d.ts.map +1 -1
- package/dist/coordinator/session.js +6 -3
- package/dist/coordinator/session.js.map +1 -1
- package/dist/key-lock/key-hold.d.ts +16 -0
- package/dist/key-lock/key-hold.d.ts.map +1 -0
- package/dist/key-lock/key-hold.js +24 -0
- package/dist/key-lock/key-hold.js.map +1 -0
- package/dist/key-lock/key-locks.d.ts +35 -0
- package/dist/key-lock/key-locks.d.ts.map +1 -0
- package/dist/key-lock/key-locks.js +67 -0
- package/dist/key-lock/key-locks.js.map +1 -0
- package/dist/protocol/flight-protocol.d.ts +2 -2
- package/dist/protocol/flight-protocol.d.ts.map +1 -1
- package/dist/single-flight.d.ts +4 -12
- package/dist/single-flight.d.ts.map +1 -1
- package/dist/single-flight.js +36 -29
- package/dist/single-flight.js.map +1 -1
- package/package.json +7 -8
|
@@ -1,186 +1,135 @@
|
|
|
1
|
-
import { Latch } from '@zukhruf/async';
|
|
2
1
|
import { Flight } from "./flight.js";
|
|
2
|
+
import { ParkedRuns } from "./parked-runs.js";
|
|
3
3
|
import { Session } from "./session.js";
|
|
4
4
|
/**
|
|
5
5
|
* Coordinates the flights of one term. A run leads a new flight of its key, or
|
|
6
6
|
* joins the flight of that key in progress, in one step, so two runs never
|
|
7
7
|
* both lead. The outcome is pushed to every joiner as soon as it lands.
|
|
8
|
+
*
|
|
9
|
+
* A key whose lock another process holds, while no flight of it is known
|
|
10
|
+
* here, runs the work of a leader that has not reasserted its flight yet, so
|
|
11
|
+
* its runs and rejoins wait for that flight or for the lock to free. A leader
|
|
12
|
+
* keeps its flight while its process lives.
|
|
8
13
|
*/
|
|
9
14
|
export class FlightCoordinator {
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}, graceWindow).unref();
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
this.#phase = coordinating;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Whether the next coordinator would find nothing to recover: no flight is
|
|
29
|
-
* in progress, and the grace window is over or there was none.
|
|
30
|
-
*/
|
|
31
|
-
get leavesNothingToRecover() {
|
|
32
|
-
return this.#phase.leavesNothingToRecover;
|
|
15
|
+
#flights = new Map();
|
|
16
|
+
/** The latest ending of a reasserted flight, per key. */
|
|
17
|
+
#endings = new Map();
|
|
18
|
+
#tokens;
|
|
19
|
+
#locks;
|
|
20
|
+
#parked;
|
|
21
|
+
#closing = new AbortController();
|
|
22
|
+
constructor({ tokens, locks }) {
|
|
23
|
+
this.#tokens = tokens;
|
|
24
|
+
this.#locks = locks;
|
|
25
|
+
this.#parked = new ParkedRuns(locks, this.#closing.signal);
|
|
33
26
|
}
|
|
34
27
|
serve(connection) {
|
|
35
28
|
new Session(this, connection);
|
|
36
29
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/** A joiner that lost its connection, back for the flight it joined: it never leads, so its work never runs twice. */
|
|
41
|
-
rejoin(party, key, flight) {
|
|
42
|
-
return this.#phase.rejoin(party, key, flight);
|
|
43
|
-
}
|
|
44
|
-
reassert(party, key, token) {
|
|
45
|
-
this.#phase.reassert(party, key, token);
|
|
46
|
-
}
|
|
47
|
-
land(flight, outcome) {
|
|
48
|
-
this.#phase.land(flight, outcome);
|
|
49
|
-
}
|
|
50
|
-
/** The leader withdrew or its connection is gone. */
|
|
51
|
-
interrupt(flight) {
|
|
52
|
-
this.#phase.interrupt(flight);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/** The only phase that starts flights. A leader that reasserts now is too late. */
|
|
56
|
-
class Coordinating {
|
|
57
|
-
#flights;
|
|
58
|
-
#tokens;
|
|
59
|
-
constructor(flights, tokens) {
|
|
60
|
-
this.#flights = flights;
|
|
61
|
-
this.#tokens = tokens;
|
|
62
|
-
}
|
|
63
|
-
get leavesNothingToRecover() {
|
|
64
|
-
return this.#flights.size === 0;
|
|
30
|
+
/** Stops watching the locks of the keys whose runs wait. */
|
|
31
|
+
close() {
|
|
32
|
+
this.#closing.abort();
|
|
65
33
|
}
|
|
34
|
+
/** Rejects only when no token can be minted. */
|
|
66
35
|
async run(party, key) {
|
|
67
36
|
// A token that goes unused is harmless: tokens only need to grow.
|
|
68
|
-
|
|
69
|
-
// The client may have withdrawn the run while the token was minted.
|
|
70
|
-
if (!party.waiting)
|
|
71
|
-
return;
|
|
72
|
-
const inProgress = this.#flights.get(key);
|
|
73
|
-
if (inProgress)
|
|
74
|
-
return inProgress.add(party);
|
|
75
|
-
const flight = new Flight(key, token, party);
|
|
76
|
-
this.#flights.set(key, flight);
|
|
77
|
-
party.lead(flight);
|
|
37
|
+
this.#decide(party, key, await this.#tokens.next(key));
|
|
78
38
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
39
|
+
/**
|
|
40
|
+
* A joiner that lost its connection, back for the flight it joined: it never
|
|
41
|
+
* leads, so its work never runs twice. A rejoin joins only its own flight;
|
|
42
|
+
* any other answer could be a second run of the work.
|
|
43
|
+
*/
|
|
44
|
+
rejoin(party, key, flight) {
|
|
81
45
|
if (!party.waiting)
|
|
82
46
|
return;
|
|
83
47
|
const inProgress = this.#flights.get(key);
|
|
84
48
|
if (inProgress?.is(flight))
|
|
85
49
|
return inProgress.add(party);
|
|
50
|
+
const ending = this.#endings.get(key);
|
|
51
|
+
if (ending?.flight.is(flight))
|
|
52
|
+
return party.end(ending.answer);
|
|
53
|
+
// Its leader may still run the work, and not have reasserted it yet.
|
|
54
|
+
if (!inProgress && this.#held(key)) {
|
|
55
|
+
return this.#parked.park(key, party, () => this.rejoin(party, key, flight));
|
|
56
|
+
}
|
|
86
57
|
party.end({ op: 'interrupted' });
|
|
87
58
|
}
|
|
88
|
-
|
|
89
|
-
|
|
59
|
+
/**
|
|
60
|
+
* A leader claims its flight again after its connection was lost. The same
|
|
61
|
+
* flight, through a new connection, stays one flight with its joiners. For
|
|
62
|
+
* two flights of one key, the newer token wins, and the older flight is lost.
|
|
63
|
+
*/
|
|
64
|
+
reassert(party, key, token) {
|
|
65
|
+
const claimed = this.#flights.get(key);
|
|
66
|
+
if (claimed?.token.value === token.value)
|
|
67
|
+
return claimed.handOver(party);
|
|
68
|
+
if (claimed && !token.isNewerThan(claimed.token)) {
|
|
69
|
+
return party.end({ op: 'rejected' });
|
|
70
|
+
}
|
|
71
|
+
if (claimed) {
|
|
72
|
+
claimed.leader.end({ op: 'rejected' });
|
|
73
|
+
this.interrupt(claimed);
|
|
74
|
+
}
|
|
75
|
+
this.#start(new Flight(key, token, party, { reasserted: true }), (flight) => party.resume(flight));
|
|
90
76
|
}
|
|
91
77
|
land(flight, outcome) {
|
|
92
|
-
this.#
|
|
78
|
+
this.#end(flight, { op: 'landed', outcome });
|
|
93
79
|
flight.land(outcome);
|
|
94
80
|
}
|
|
81
|
+
/** The leader withdrew or its connection is gone. */
|
|
95
82
|
interrupt(flight) {
|
|
96
|
-
this.#
|
|
83
|
+
this.#end(flight, { op: 'interrupted' });
|
|
97
84
|
flight.interrupt();
|
|
98
85
|
}
|
|
99
|
-
|
|
86
|
+
/** Every new flight takes the runs parked on its key, in the step that starts it. */
|
|
87
|
+
#start(flight, tell) {
|
|
88
|
+
this.#flights.set(flight.key, flight);
|
|
89
|
+
tell(flight);
|
|
90
|
+
this.#parked.wake(flight.key);
|
|
91
|
+
}
|
|
92
|
+
#end(flight, answer) {
|
|
100
93
|
if (this.#flights.get(flight.key) === flight) {
|
|
101
94
|
this.#flights.delete(flight.key);
|
|
102
95
|
}
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
/** Starts no flight, so leaders from a previous coordinator can reassert theirs first. */
|
|
106
|
-
class GraceWindow {
|
|
107
|
-
#flights;
|
|
108
|
-
#over;
|
|
109
|
-
#next;
|
|
110
|
-
#endings = new Map();
|
|
111
|
-
/** Wakes the rejoins that wait for a reassertion of their key. */
|
|
112
|
-
#wakers = new Map();
|
|
113
|
-
constructor(flights, over, next) {
|
|
114
|
-
this.#flights = flights;
|
|
115
|
-
this.#over = over;
|
|
116
|
-
this.#next = next;
|
|
117
|
-
}
|
|
118
|
-
/** A leader of the previous term may not have reasserted its flight yet, so this coordinator cannot know. */
|
|
119
|
-
get leavesNothingToRecover() {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
/** A fresh run leads or joins only once every leader had its chance to reassert. */
|
|
123
|
-
async run(party, key) {
|
|
124
|
-
await this.#over.wait();
|
|
125
|
-
return this.#next.run(party, key);
|
|
96
|
+
if (flight.reasserted)
|
|
97
|
+
this.#endings.set(flight.key, { flight, answer });
|
|
126
98
|
}
|
|
127
99
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* the
|
|
100
|
+
* Joins the flight of `key` in progress, waits while another process holds
|
|
101
|
+
* the key, or leads. A flight of the key that starts while the run waits
|
|
102
|
+
* takes it in the step that starts it, so a landing read right behind a
|
|
103
|
+
* reassertion reaches it.
|
|
131
104
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (inProgress?.is(flight))
|
|
139
|
-
return inProgress.add(party);
|
|
140
|
-
const ending = this.#endings.get(key);
|
|
141
|
-
if (ending?.flight.is(flight)) {
|
|
142
|
-
return party.end('landed' in ending
|
|
143
|
-
? { op: 'landed', outcome: ending.landed }
|
|
144
|
-
: { op: 'interrupted' });
|
|
145
|
-
}
|
|
146
|
-
const woken = await Promise.race([over, this.#reassertionOf(key)]);
|
|
147
|
-
if (woken === 'over')
|
|
148
|
-
return this.#next.rejoin(party, key, flight);
|
|
105
|
+
#decide(party, key, token) {
|
|
106
|
+
// The client may have withdrawn the run while the token was minted or while it waited.
|
|
107
|
+
if (!party.waiting || this.#join(party, key))
|
|
108
|
+
return;
|
|
109
|
+
if (this.#held(key)) {
|
|
110
|
+
return this.#parked.park(key, party, () => this.#decide(party, key, token));
|
|
149
111
|
}
|
|
112
|
+
// No flight of the key started while the run waited, so its token is still newer than every flight of the key.
|
|
113
|
+
this.#start(new Flight(key, token, party), (flight) => party.lead(flight));
|
|
150
114
|
}
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
115
|
+
/** Joins `party` to the flight of `key` in progress, if there is one. */
|
|
116
|
+
#join(party, key) {
|
|
117
|
+
const inProgress = this.#flights.get(key);
|
|
118
|
+
inProgress?.add(party);
|
|
119
|
+
return inProgress !== undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The lock check is advice only (F5). A fault answers "not held", so the
|
|
123
|
+
* run leads and its leader's own lock decides; a fault never rejects a run
|
|
124
|
+
* or a rejoin, which would close its connection.
|
|
125
|
+
*/
|
|
126
|
+
#held(key) {
|
|
127
|
+
try {
|
|
128
|
+
return this.#locks.isHeld(key);
|
|
156
129
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.interrupt(claimed);
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
160
132
|
}
|
|
161
|
-
const flight = new Flight(key, token, party);
|
|
162
|
-
this.#flights.set(key, flight);
|
|
163
|
-
party.resume(flight);
|
|
164
|
-
const wakers = this.#wakers.get(key);
|
|
165
|
-
this.#wakers.delete(key);
|
|
166
|
-
for (const wake of wakers ?? [])
|
|
167
|
-
wake();
|
|
168
|
-
}
|
|
169
|
-
land(flight, outcome) {
|
|
170
|
-
this.#endings.set(flight.key, { flight, landed: outcome });
|
|
171
|
-
this.#next.land(flight, outcome);
|
|
172
|
-
}
|
|
173
|
-
interrupt(flight) {
|
|
174
|
-
this.#endings.set(flight.key, { flight, interrupted: true });
|
|
175
|
-
this.#next.interrupt(flight);
|
|
176
|
-
}
|
|
177
|
-
#reassertionOf(key) {
|
|
178
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
179
|
-
let wakers = this.#wakers.get(key);
|
|
180
|
-
if (!wakers)
|
|
181
|
-
this.#wakers.set(key, (wakers = new Set()));
|
|
182
|
-
wakers.add(() => resolve('reasserted'));
|
|
183
|
-
return promise;
|
|
184
133
|
}
|
|
185
134
|
}
|
|
186
135
|
//# sourceMappingURL=flight-coordinator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-coordinator.js","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flight-coordinator.js","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Term } from '@zukhruf/election';
|
|
2
|
+
import type { KeyLocks } from '../key-lock/key-locks.ts';
|
|
2
3
|
export interface FlightServerOptions {
|
|
3
|
-
/**
|
|
4
|
-
|
|
4
|
+
/** The kernel locks on the keys, which the coordinator checks before a run leads. */
|
|
5
|
+
locks: KeyLocks;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* Serves a `FlightCoordinator` on a Unix socket for one term. Lease tokens
|
|
@@ -12,18 +13,17 @@ export interface FlightServerOptions {
|
|
|
12
13
|
export declare class FlightServer {
|
|
13
14
|
#private;
|
|
14
15
|
private constructor();
|
|
15
|
-
static start(socketPath: string, term: Term, {
|
|
16
|
+
static start(socketPath: string, term: Term, { locks }: FlightServerOptions): Promise<FlightServer>;
|
|
16
17
|
/**
|
|
17
18
|
* Hands the term over. Ending every connection tells the other processes to
|
|
18
|
-
* elect a successor and reassert their flights
|
|
19
|
-
*
|
|
19
|
+
* elect a successor and reassert their flights to it; `close` alone would
|
|
20
|
+
* wait for clients that never disconnect on their own.
|
|
20
21
|
* Each connection ends only after what was written to it, such as an
|
|
21
22
|
* outcome for a joiner, is sent. The term ends only after the socket is
|
|
22
23
|
* gone: in the other order, this close could remove a successor's socket file.
|
|
23
|
-
* The term resigns clean when the coordinator leaves nothing to recover, so
|
|
24
|
-
* the successor skips its grace window; a lost term records nothing.
|
|
25
24
|
* The first call removes the file at once, so a second caller finds it gone,
|
|
26
25
|
* and it too resolves only once the term ended as the first call ended it.
|
|
26
|
+
* The coordinator stops watching the locks of the keys whose runs wait.
|
|
27
27
|
*/
|
|
28
28
|
close(): Promise<void>;
|
|
29
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-server.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight-server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"flight-server.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight-server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAK9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAIzD,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;GAKG;AACH,qBAAa,YAAY;;IAOvB,OAAO;WAaM,KAAK,CAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,IAAI,EACV,EAAE,KAAK,EAAE,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC;IAkCxB;;;;;;;;;;OAUG;IACG,KAAK;CAQZ"}
|
|
@@ -24,7 +24,7 @@ export class FlightServer {
|
|
|
24
24
|
this.#term = term;
|
|
25
25
|
this.#closeOnLoss = addAbortListener(term.signal, () => void this.close());
|
|
26
26
|
}
|
|
27
|
-
static async start(socketPath, term, {
|
|
27
|
+
static async start(socketPath, term, { locks }) {
|
|
28
28
|
// Only the coordinator gets here, so removing a dead coordinator's socket file cannot race another server.
|
|
29
29
|
// Windows removes a named pipe when its process stops, so there is no file to remove.
|
|
30
30
|
if (process.platform !== 'win32') {
|
|
@@ -32,7 +32,7 @@ export class FlightServer {
|
|
|
32
32
|
}
|
|
33
33
|
const coordinator = new FlightCoordinator({
|
|
34
34
|
tokens: new EpochTokenSource(term.epoch),
|
|
35
|
-
|
|
35
|
+
locks,
|
|
36
36
|
});
|
|
37
37
|
const connections = new Set();
|
|
38
38
|
// Serving others never keeps the coordinator alive: when its own work ends, it exits and another process takes over.
|
|
@@ -60,29 +60,23 @@ export class FlightServer {
|
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Hands the term over. Ending every connection tells the other processes to
|
|
63
|
-
* elect a successor and reassert their flights
|
|
64
|
-
*
|
|
63
|
+
* elect a successor and reassert their flights to it; `close` alone would
|
|
64
|
+
* wait for clients that never disconnect on their own.
|
|
65
65
|
* Each connection ends only after what was written to it, such as an
|
|
66
66
|
* outcome for a joiner, is sent. The term ends only after the socket is
|
|
67
67
|
* gone: in the other order, this close could remove a successor's socket file.
|
|
68
|
-
* The term resigns clean when the coordinator leaves nothing to recover, so
|
|
69
|
-
* the successor skips its grace window; a lost term records nothing.
|
|
70
68
|
* The first call removes the file at once, so a second caller finds it gone,
|
|
71
69
|
* and it too resolves only once the term ended as the first call ended it.
|
|
70
|
+
* The coordinator stops watching the locks of the keys whose runs wait.
|
|
72
71
|
*/
|
|
73
72
|
async close() {
|
|
74
73
|
this.#closeOnLoss[Symbol.dispose]();
|
|
74
|
+
this.#coordinator.close();
|
|
75
75
|
const closed = this.#server[Symbol.asyncDispose]();
|
|
76
|
-
// Read in the step that ends the connections: each session that ends
|
|
77
|
-
// withdraws its flights, so later there is never a flight in progress.
|
|
78
|
-
// An answer written after this step, such as the `lead` of a run whose
|
|
79
|
-
// token is being minted, never reaches its client, which sends the run
|
|
80
|
-
// again to the successor. So no client knows of a flight this read missed.
|
|
81
|
-
const clean = this.#coordinator.leavesNothingToRecover;
|
|
82
76
|
for (const socket of this.#connections)
|
|
83
77
|
socket.destroySoon();
|
|
84
78
|
await closed;
|
|
85
|
-
await this.#term.resign(
|
|
79
|
+
await this.#term.resign();
|
|
86
80
|
}
|
|
87
81
|
}
|
|
88
82
|
//# sourceMappingURL=flight-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-server.js","sourceRoot":"","sources":["../../src/coordinator/flight-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAA4B,YAAY,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"flight-server.js","sourceRoot":"","sources":["../../src/coordinator/flight-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import type { FencingToken } from '@zukhruf/fencing';
|
|
2
2
|
import type { Outcome } from '../protocol/flight-protocol.ts';
|
|
3
3
|
import type { Party } from './party.ts';
|
|
4
|
+
export interface FlightOptions {
|
|
5
|
+
/** A leader claimed this flight again after its connection was lost, so a rejoin may come after its end. */
|
|
6
|
+
reasserted?: boolean;
|
|
7
|
+
}
|
|
4
8
|
/** A flight in progress: the key, the leader that runs the work, its lease token, and the parties that joined. */
|
|
5
9
|
export declare class Flight {
|
|
6
10
|
#private;
|
|
7
11
|
readonly key: string;
|
|
8
12
|
readonly token: FencingToken;
|
|
9
|
-
readonly
|
|
10
|
-
constructor(key: string, token: FencingToken, leader: Party);
|
|
13
|
+
readonly reasserted: boolean;
|
|
14
|
+
constructor(key: string, token: FencingToken, leader: Party, { reasserted }?: FlightOptions);
|
|
15
|
+
get leader(): Party;
|
|
11
16
|
/** The flight's name on the wire: its token as decimal text, which a joined answer and a rejoin carry. */
|
|
12
17
|
get id(): string;
|
|
13
18
|
/** Whether this is the flight with the id `flight`, which a rejoin names. */
|
|
14
19
|
is(flight: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The leader claimed this flight again through a new connection. The new
|
|
22
|
+
* party leads it, and the old one ends without an answer, so the close of
|
|
23
|
+
* the old connection interrupts nothing.
|
|
24
|
+
*/
|
|
25
|
+
handOver(leader: Party): void;
|
|
15
26
|
add(joiner: Party): void;
|
|
16
27
|
remove(joiner: Party): void;
|
|
17
28
|
/** Pushes the outcome to every joiner, then tells the leader that it was handed over. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,kHAAkH;AAClH,qBAAa,MAAM;;IACjB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"flight.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,4GAA4G;IAC5G,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,kHAAkH;AAClH,qBAAa,MAAM;;IACjB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;gBAK3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,KAAK,EACb,EAAE,UAAkB,EAAE,GAAE,aAAkB;IAQ5C,IAAI,MAAM,IAAI,KAAK,CAElB;IAED,0GAA0G;IAC1G,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,6EAA6E;IAC7E,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI3B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,KAAK;IAOtB,GAAG,CAAC,MAAM,EAAE,KAAK;IAKjB,MAAM,CAAC,MAAM,EAAE,KAAK;IAIpB,yFAAyF;IACzF,IAAI,CAAC,OAAO,EAAE,OAAO;IAQrB,0GAA0G;IAC1G,SAAS;CAMV"}
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
export class Flight {
|
|
3
3
|
key;
|
|
4
4
|
token;
|
|
5
|
-
|
|
5
|
+
reasserted;
|
|
6
6
|
#joiners = new Set();
|
|
7
|
-
|
|
7
|
+
#leader;
|
|
8
|
+
constructor(key, token, leader, { reasserted = false } = {}) {
|
|
8
9
|
this.key = key;
|
|
9
10
|
this.token = token;
|
|
10
|
-
this.
|
|
11
|
+
this.reasserted = reasserted;
|
|
12
|
+
this.#leader = leader;
|
|
13
|
+
}
|
|
14
|
+
get leader() {
|
|
15
|
+
return this.#leader;
|
|
11
16
|
}
|
|
12
17
|
/** The flight's name on the wire: its token as decimal text, which a joined answer and a rejoin carry. */
|
|
13
18
|
get id() {
|
|
@@ -17,6 +22,17 @@ export class Flight {
|
|
|
17
22
|
is(flight) {
|
|
18
23
|
return flight === this.id;
|
|
19
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The leader claimed this flight again through a new connection. The new
|
|
27
|
+
* party leads it, and the old one ends without an answer, so the close of
|
|
28
|
+
* the old connection interrupts nothing.
|
|
29
|
+
*/
|
|
30
|
+
handOver(leader) {
|
|
31
|
+
const previous = this.#leader;
|
|
32
|
+
this.#leader = leader;
|
|
33
|
+
previous.end();
|
|
34
|
+
leader.resume(this);
|
|
35
|
+
}
|
|
20
36
|
add(joiner) {
|
|
21
37
|
this.#joiners.add(joiner);
|
|
22
38
|
joiner.join(this);
|
|
@@ -30,7 +46,7 @@ export class Flight {
|
|
|
30
46
|
joiner.end({ op: 'landed', outcome });
|
|
31
47
|
}
|
|
32
48
|
this.#joiners.clear();
|
|
33
|
-
this
|
|
49
|
+
this.#leader.end({ op: 'ack' });
|
|
34
50
|
}
|
|
35
51
|
/** Ends without an outcome: every joiner learns that its flight is lost, and none runs the work again. */
|
|
36
52
|
interrupt() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight.js","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flight.js","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"AAUA,kHAAkH;AAClH,MAAM,OAAO,MAAM;IACR,GAAG,CAAS;IACZ,KAAK,CAAe;IACpB,UAAU,CAAU;IACpB,QAAQ,GAAG,IAAI,GAAG,EAAS,CAAC;IACrC,OAAO,CAAQ;IAEf,YACE,GAAW,EACX,KAAmB,EACnB,MAAa,EACb,EAAE,UAAU,GAAG,KAAK,KAAoB,EAAE;QAE1C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,0GAA0G;IAC1G,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC;IAED,6EAA6E;IAC7E,EAAE,CAAC,MAAc;QACf,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,MAAa;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,QAAQ,CAAC,GAAG,EAAE,CAAC;QACf,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,GAAG,CAAC,MAAa;QACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,MAAa;QAClB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC,OAAgB;QACnB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,0GAA0G;IAC1G,SAAS;QACP,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { KeyLocks } from '../key-lock/key-locks.ts';
|
|
2
|
+
import type { Party } from './party.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The runs and rejoins of keys that another process holds while this
|
|
5
|
+
* coordinator knows no flight of them: a leader of an earlier term that has
|
|
6
|
+
* not reasserted its flight yet, or a leader whose flight ended and that
|
|
7
|
+
* still lets its key go. They wait until a flight of the key starts or the
|
|
8
|
+
* key's lock frees.
|
|
9
|
+
*
|
|
10
|
+
* Each key with parked runs has one watch of its lock. It stops when the lock
|
|
11
|
+
* frees, when the key's runs were woken, when none of them waits any more, or
|
|
12
|
+
* when the coordinator closes. It never keeps the process alive.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ParkedRuns {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(locks: KeyLocks, closed: AbortSignal);
|
|
17
|
+
/** Parks `party` on `key`; `retry` runs once the key's runs are woken. */
|
|
18
|
+
park(key: string, party: Party, retry: () => void): void;
|
|
19
|
+
/**
|
|
20
|
+
* Decides every parked run of `key` again, now, in this step. A flight of
|
|
21
|
+
* the key that just started takes them before anything else is read, such
|
|
22
|
+
* as a landing that arrived right behind its reassertion.
|
|
23
|
+
*/
|
|
24
|
+
wake(key: string): void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=parked-runs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parked-runs.d.ts","sourceRoot":"","sources":["../../src/coordinator/parked-runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAQxC;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;;gBAKT,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW;IAKhD,0EAA0E;IAC1E,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,IAAI;IAWxD;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CA0BxB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runs and rejoins of keys that another process holds while this
|
|
3
|
+
* coordinator knows no flight of them: a leader of an earlier term that has
|
|
4
|
+
* not reasserted its flight yet, or a leader whose flight ended and that
|
|
5
|
+
* still lets its key go. They wait until a flight of the key starts or the
|
|
6
|
+
* key's lock frees.
|
|
7
|
+
*
|
|
8
|
+
* Each key with parked runs has one watch of its lock. It stops when the lock
|
|
9
|
+
* frees, when the key's runs were woken, when none of them waits any more, or
|
|
10
|
+
* when the coordinator closes. It never keeps the process alive.
|
|
11
|
+
*/
|
|
12
|
+
export class ParkedRuns {
|
|
13
|
+
#locks;
|
|
14
|
+
#closed;
|
|
15
|
+
#parked = new Map();
|
|
16
|
+
constructor(locks, closed) {
|
|
17
|
+
this.#locks = locks;
|
|
18
|
+
this.#closed = closed;
|
|
19
|
+
}
|
|
20
|
+
/** Parks `party` on `key`; `retry` runs once the key's runs are woken. */
|
|
21
|
+
park(key, party, retry) {
|
|
22
|
+
const parked = this.#parked.get(key);
|
|
23
|
+
if (parked) {
|
|
24
|
+
parked.add({ party, retry });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const first = new Set([{ party, retry }]);
|
|
28
|
+
this.#parked.set(key, first);
|
|
29
|
+
void this.#watch(key, first);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Decides every parked run of `key` again, now, in this step. A flight of
|
|
33
|
+
* the key that just started takes them before anything else is read, such
|
|
34
|
+
* as a landing that arrived right behind its reassertion.
|
|
35
|
+
*/
|
|
36
|
+
wake(key) {
|
|
37
|
+
const parked = this.#parked.get(key);
|
|
38
|
+
if (!parked)
|
|
39
|
+
return;
|
|
40
|
+
this.#parked.delete(key);
|
|
41
|
+
for (const { retry } of parked)
|
|
42
|
+
retry();
|
|
43
|
+
}
|
|
44
|
+
async #watch(key, parked) {
|
|
45
|
+
const current = () => this.#parked.get(key) === parked;
|
|
46
|
+
let free;
|
|
47
|
+
try {
|
|
48
|
+
free = await this.#locks.whenFree(key, () => current() && [...parked].some(({ party }) => party.waiting), this.#closed);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
if (this.#closed.aborted)
|
|
52
|
+
return;
|
|
53
|
+
// The check is advice only (F5), so a fault counts as free: each run is
|
|
54
|
+
// decided again, and a leader's own lock is the truth.
|
|
55
|
+
free = true;
|
|
56
|
+
}
|
|
57
|
+
if (!current())
|
|
58
|
+
return;
|
|
59
|
+
if (free)
|
|
60
|
+
this.wake(key);
|
|
61
|
+
else
|
|
62
|
+
this.#parked.delete(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=parked-runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parked-runs.js","sourceRoot":"","sources":["../../src/coordinator/parked-runs.ts"],"names":[],"mappings":"AASA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,UAAU;IACZ,MAAM,CAAW;IACjB,OAAO,CAAc;IACrB,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAErD,YAAY,KAAe,EAAE,MAAmB;QAC9C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,0EAA0E;IAC1E,IAAI,CAAC,GAAW,EAAE,KAAY,EAAE,KAAiB;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,GAAW;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,MAAM;YAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW,EAAE,MAAsB;QAC9C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC;QACvD,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAC/B,GAAG,EACH,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EACjE,IAAI,CAAC,OAAO,CACb,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO;gBAAE,OAAO;YACjC,wEAAwE;YACxE,uDAAuD;YACvD,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,OAAO,EAAE;YAAE,OAAO;QACvB,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -14,7 +14,7 @@ export interface Inbox {
|
|
|
14
14
|
}
|
|
15
15
|
/** What a party is to the coordinator now. */
|
|
16
16
|
export type Role =
|
|
17
|
-
/** A run that waits for
|
|
17
|
+
/** A run that waits for a token, or for its key's lock: for a flight of the key or for the lock to free. */
|
|
18
18
|
{
|
|
19
19
|
kind: 'waiting';
|
|
20
20
|
} | {
|