@zukhruf/single-flight 0.3.17 → 0.3.19
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 +13 -11
- package/dist/client/flight-client.d.ts +2 -2
- package/dist/client/flight-client.d.ts.map +1 -1
- package/dist/client/flight-client.js +6 -1
- package/dist/client/flight-client.js.map +1 -1
- package/dist/connection/connection-supervisor.d.ts +4 -4
- package/dist/connection/connection-supervisor.d.ts.map +1 -1
- package/dist/connection/connection-supervisor.js.map +1 -1
- package/dist/connection/connection.d.ts +4 -3
- package/dist/connection/connection.d.ts.map +1 -1
- package/dist/connection/connector.d.ts +5 -5
- package/dist/connection/connector.d.ts.map +1 -1
- package/dist/connection/electing-connector.d.ts +4 -5
- package/dist/connection/electing-connector.d.ts.map +1 -1
- package/dist/connection/electing-connector.js +38 -42
- package/dist/connection/electing-connector.js.map +1 -1
- package/dist/connection/flight-election.d.ts +11 -0
- package/dist/connection/flight-election.d.ts.map +1 -0
- package/dist/connection/flight-election.js +18 -0
- package/dist/connection/flight-election.js.map +1 -0
- package/dist/connection/handshake.d.ts.map +1 -1
- package/dist/connection/handshake.js +6 -5
- package/dist/connection/handshake.js.map +1 -1
- package/dist/connection/json-line.d.ts +6 -0
- package/dist/connection/json-line.d.ts.map +1 -0
- package/dist/connection/json-line.js +8 -0
- package/dist/connection/json-line.js.map +1 -0
- package/dist/connection/leave-errors-to-close.d.ts +9 -0
- package/dist/connection/leave-errors-to-close.d.ts.map +1 -0
- package/dist/connection/leave-errors-to-close.js +10 -0
- package/dist/connection/leave-errors-to-close.js.map +1 -0
- package/dist/connection/socket-connection.d.ts +5 -5
- package/dist/connection/socket-connection.d.ts.map +1 -1
- package/dist/connection/socket-connection.js +21 -23
- package/dist/connection/socket-connection.js.map +1 -1
- package/dist/coordinator/flight-coordinator.d.ts +9 -4
- package/dist/coordinator/flight-coordinator.d.ts.map +1 -1
- package/dist/coordinator/flight-coordinator.js +26 -18
- package/dist/coordinator/flight-coordinator.js.map +1 -1
- package/dist/coordinator/flight-server.d.ts +9 -3
- package/dist/coordinator/flight-server.d.ts.map +1 -1
- package/dist/coordinator/flight-server.js +38 -24
- package/dist/coordinator/flight-server.js.map +1 -1
- package/dist/coordinator/flight.d.ts +3 -1
- package/dist/coordinator/flight.d.ts.map +1 -1
- package/dist/coordinator/flight.js +9 -5
- package/dist/coordinator/flight.js.map +1 -1
- package/dist/coordinator/party.d.ts +11 -2
- package/dist/coordinator/party.d.ts.map +1 -1
- package/dist/coordinator/party.js +15 -15
- package/dist/coordinator/party.js.map +1 -1
- package/dist/coordinator/session.d.ts +2 -2
- package/dist/coordinator/session.d.ts.map +1 -1
- package/dist/coordinator/session.js +7 -2
- package/dist/coordinator/session.js.map +1 -1
- package/dist/protocol/flight-protocol.d.ts +6 -2
- package/dist/protocol/flight-protocol.d.ts.map +1 -1
- package/dist/protocol/flight-protocol.js +15 -9
- package/dist/protocol/flight-protocol.js.map +1 -1
- package/dist/single-flight.d.ts +4 -2
- package/dist/single-flight.d.ts.map +1 -1
- package/dist/single-flight.js +10 -8
- package/dist/single-flight.js.map +1 -1
- package/package.json +2 -1
- package/dist/election/leader-election.d.ts +0 -24
- package/dist/election/leader-election.d.ts.map +0 -1
- package/dist/election/leader-election.js +0 -78
- package/dist/election/leader-election.js.map +0 -1
- package/dist/election/leadership.d.ts +0 -14
- package/dist/election/leadership.d.ts.map +0 -1
- package/dist/election/leadership.js +0 -23
- package/dist/election/leadership.js.map +0 -1
- package/dist/shared/sqlite/is-busy.d.ts +0 -3
- package/dist/shared/sqlite/is-busy.d.ts.map +0 -1
- package/dist/shared/sqlite/is-busy.js +0 -10
- package/dist/shared/sqlite/is-busy.js.map +0 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps the errors of a socket, or of a stream reading it, from stopping the
|
|
3
|
+
* process: an `error` with no listener does that. Every such error is followed
|
|
4
|
+
* by the socket's `close`, which is where the lost connection is reported, so
|
|
5
|
+
* this listener does nothing.
|
|
6
|
+
*/
|
|
7
|
+
export function leaveErrorsToClose(emitter) {
|
|
8
|
+
emitter.on('error', () => { });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=leave-errors-to-close.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leave-errors-to-close.js","sourceRoot":"","sources":["../../src/connection/leave-errors-to-close.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACtD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -3,13 +3,13 @@ import type { Socket } from 'node:net';
|
|
|
3
3
|
import type { Connection, ConnectionEvents } from './connection.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Newline-delimited JSON over a stream socket. A stream has no message
|
|
6
|
-
* boundaries (writes arrive merged and split),
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* boundaries (writes arrive merged and split), so each message is one line
|
|
7
|
+
* (see `jsonLine`). A line that is not JSON breaks the framing, so it closes
|
|
8
|
+
* the connection; any JSON value goes to the listener, which checks it.
|
|
9
9
|
*/
|
|
10
|
-
export declare class SocketConnection<Outgoing
|
|
10
|
+
export declare class SocketConnection<Outgoing> extends EventEmitter<ConnectionEvents> implements Connection<Outgoing> {
|
|
11
11
|
#private;
|
|
12
|
-
constructor(socket: Socket
|
|
12
|
+
constructor(socket: Socket);
|
|
13
13
|
send(message: Outgoing): Promise<void>;
|
|
14
14
|
ref(): void;
|
|
15
15
|
unref(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-connection.d.ts","sourceRoot":"","sources":["../../src/connection/socket-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"socket-connection.d.ts","sourceRoot":"","sources":["../../src/connection/socket-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIvC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIpE;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,CACpC,SAAQ,YAAY,CAAC,gBAAgB,CACrC,YAAW,UAAU,CAAC,QAAQ,CAAC;;gBAMnB,MAAM,EAAE,MAAM;IAqB1B,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,GAAG;IAIH,KAAK;IAIL,KAAK;CAGN"}
|
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { jsonLine } from "./json-line.js";
|
|
5
|
+
import { leaveErrorsToClose } from "./leave-errors-to-close.js";
|
|
3
6
|
/**
|
|
4
7
|
* Newline-delimited JSON over a stream socket. A stream has no message
|
|
5
|
-
* boundaries (writes arrive merged and split),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* boundaries (writes arrive merged and split), so each message is one line
|
|
9
|
+
* (see `jsonLine`). A line that is not JSON breaks the framing, so it closes
|
|
10
|
+
* the connection; any JSON value goes to the listener, which checks it.
|
|
8
11
|
*/
|
|
9
12
|
export class SocketConnection extends EventEmitter {
|
|
10
13
|
#socket;
|
|
11
|
-
|
|
14
|
+
/** A write after the socket closed reports the error through its callback. */
|
|
15
|
+
#write;
|
|
16
|
+
constructor(socket) {
|
|
12
17
|
super();
|
|
13
18
|
this.#socket = socket;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
this.#write = promisify(socket.write).bind(socket);
|
|
20
|
+
// readline removes its own 'error' listener from the socket when the
|
|
21
|
+
// interface closes on the peer's FIN, so an error after that (e.g. a reset
|
|
22
|
+
// after the half-close) needs this listener of the socket's own.
|
|
23
|
+
leaveErrorsToClose(socket);
|
|
24
|
+
const lines = createInterface({ input: socket, crlfDelay: Infinity });
|
|
25
|
+
lines.on('line', (line) => {
|
|
18
26
|
try {
|
|
19
|
-
|
|
20
|
-
if (isIncoming(parsed))
|
|
21
|
-
this.emit('message', parsed);
|
|
22
|
-
else
|
|
23
|
-
this.close();
|
|
27
|
+
this.emit('message', JSON.parse(line));
|
|
24
28
|
}
|
|
25
29
|
catch {
|
|
26
30
|
this.close();
|
|
27
31
|
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
});
|
|
33
|
+
// readline re-emits the socket's errors (e.g. EPIPE when the peer died).
|
|
34
|
+
leaveErrorsToClose(lines);
|
|
31
35
|
socket.once('close', () => this.emit('close'));
|
|
32
36
|
}
|
|
33
37
|
send(message) {
|
|
34
|
-
return
|
|
35
|
-
if (!this.#socket.writable) {
|
|
36
|
-
reject(new Error('The socket is closed.'));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
this.#socket.write(`${JSON.stringify(message)}\n`, (error) => error ? reject(error) : resolve());
|
|
40
|
-
});
|
|
38
|
+
return this.#write(jsonLine(message));
|
|
41
39
|
}
|
|
42
40
|
ref() {
|
|
43
41
|
this.#socket.ref();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-connection.js","sourceRoot":"","sources":["../../src/connection/socket-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"socket-connection.js","sourceRoot":"","sources":["../../src/connection/socket-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,OAAO,gBACX,SAAQ,YAA8B;IAG7B,OAAO,CAAS;IACzB,8EAA8E;IACrE,MAAM,CAAkC;IAEjD,YAAY,MAAc;QACxB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAe,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjE,qEAAqE;QACrE,2EAA2E;QAC3E,iEAAiE;QACjE,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC;gBACH,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;QACH,yEAAyE;QACzE,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,CAAC,OAAiB;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACxC,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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FencingToken, TokenSource } from '@zukhruf/fencing';
|
|
2
2
|
import type { Connection } from '../connection/connection.ts';
|
|
3
|
-
import type { FlightResponse, Outcome
|
|
3
|
+
import type { FlightResponse, Outcome } from '../protocol/flight-protocol.ts';
|
|
4
4
|
import { Flight } from './flight.ts';
|
|
5
5
|
import type { Party } from './party.ts';
|
|
6
6
|
export interface FlightCoordinatorOptions {
|
|
@@ -8,8 +8,8 @@ export interface FlightCoordinatorOptions {
|
|
|
8
8
|
tokens: TokenSource;
|
|
9
9
|
/**
|
|
10
10
|
* Milliseconds after start during which no flight starts, so leaders from a
|
|
11
|
-
* previous coordinator can reassert their flights first. Zero
|
|
12
|
-
* coordinator
|
|
11
|
+
* previous coordinator can reassert their flights first. Zero when no
|
|
12
|
+
* earlier coordinator can have left a flight in progress.
|
|
13
13
|
*/
|
|
14
14
|
graceWindow: number;
|
|
15
15
|
}
|
|
@@ -21,7 +21,12 @@ export interface FlightCoordinatorOptions {
|
|
|
21
21
|
export declare class FlightCoordinator {
|
|
22
22
|
#private;
|
|
23
23
|
constructor({ tokens, graceWindow }: FlightCoordinatorOptions);
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Whether the next coordinator would find nothing to recover: no flight is
|
|
26
|
+
* in progress, and the grace window is over or there was none.
|
|
27
|
+
*/
|
|
28
|
+
get leavesNothingToRecover(): boolean;
|
|
29
|
+
serve(connection: Connection<FlightResponse>): void;
|
|
25
30
|
run(party: Party, key: string): Promise<void>;
|
|
26
31
|
/** A joiner that lost its connection, back for the flight it joined: it never leads, so its work never runs twice. */
|
|
27
32
|
rejoin(party: Party, key: string, flight: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-coordinator.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"flight-coordinator.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD;;;;GAIG;AACH,qBAAa,iBAAiB;;gBAGhB,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,wBAAwB;IAgB7D;;;OAGG;IACH,IAAI,sBAAsB,IAAI,OAAO,CAEpC;IAED,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,cAAc,CAAC,GAAG,IAAI;IAInD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,sHAAsH;IACtH,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAI9D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAI5C,qDAAqD;IACrD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAGhC"}
|
|
@@ -24,6 +24,13 @@ export class FlightCoordinator {
|
|
|
24
24
|
this.#phase = coordinating;
|
|
25
25
|
}
|
|
26
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;
|
|
33
|
+
}
|
|
27
34
|
serve(connection) {
|
|
28
35
|
new Session(this, connection);
|
|
29
36
|
}
|
|
@@ -53,11 +60,14 @@ class Coordinating {
|
|
|
53
60
|
this.#flights = flights;
|
|
54
61
|
this.#tokens = tokens;
|
|
55
62
|
}
|
|
63
|
+
get leavesNothingToRecover() {
|
|
64
|
+
return this.#flights.size === 0;
|
|
65
|
+
}
|
|
56
66
|
async run(party, key) {
|
|
57
67
|
// A token that goes unused is harmless: tokens only need to grow.
|
|
58
68
|
const token = await this.#tokens.next(key);
|
|
59
69
|
// The client may have withdrawn the run while the token was minted.
|
|
60
|
-
if (party.
|
|
70
|
+
if (!party.waiting)
|
|
61
71
|
return;
|
|
62
72
|
const inProgress = this.#flights.get(key);
|
|
63
73
|
if (inProgress)
|
|
@@ -68,15 +78,15 @@ class Coordinating {
|
|
|
68
78
|
}
|
|
69
79
|
/** A rejoin joins only its own flight; any other answer could be a second run of the work. */
|
|
70
80
|
async rejoin(party, key, flight) {
|
|
71
|
-
if (party.
|
|
81
|
+
if (!party.waiting)
|
|
72
82
|
return;
|
|
73
83
|
const inProgress = this.#flights.get(key);
|
|
74
84
|
if (inProgress?.is(flight))
|
|
75
85
|
return inProgress.add(party);
|
|
76
|
-
party.end({ op: 'interrupted'
|
|
86
|
+
party.end({ op: 'interrupted' });
|
|
77
87
|
}
|
|
78
88
|
reassert(party) {
|
|
79
|
-
party.end({ op: 'rejected'
|
|
89
|
+
party.end({ op: 'rejected' });
|
|
80
90
|
}
|
|
81
91
|
land(flight, outcome) {
|
|
82
92
|
this.#forget(flight);
|
|
@@ -105,6 +115,10 @@ class GraceWindow {
|
|
|
105
115
|
this.#over = over;
|
|
106
116
|
this.#next = next;
|
|
107
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
|
+
}
|
|
108
122
|
/** A fresh run leads or joins only once every leader had its chance to reassert. */
|
|
109
123
|
async run(party, key) {
|
|
110
124
|
await this.#over.wait();
|
|
@@ -118,16 +132,16 @@ class GraceWindow {
|
|
|
118
132
|
async rejoin(party, key, flight) {
|
|
119
133
|
const over = this.#over.wait().then(() => 'over');
|
|
120
134
|
for (;;) {
|
|
121
|
-
if (party.
|
|
135
|
+
if (!party.waiting)
|
|
122
136
|
return;
|
|
123
137
|
const inProgress = this.#flights.get(key);
|
|
124
138
|
if (inProgress?.is(flight))
|
|
125
139
|
return inProgress.add(party);
|
|
126
140
|
const ending = this.#endings.get(key);
|
|
127
|
-
if (ending?.
|
|
141
|
+
if (ending?.flight.is(flight)) {
|
|
128
142
|
return party.end('landed' in ending
|
|
129
|
-
? { op: 'landed',
|
|
130
|
-
: { op: 'interrupted'
|
|
143
|
+
? { op: 'landed', outcome: ending.landed }
|
|
144
|
+
: { op: 'interrupted' });
|
|
131
145
|
}
|
|
132
146
|
const woken = await Promise.race([over, this.#reassertionOf(key)]);
|
|
133
147
|
if (woken === 'over')
|
|
@@ -138,10 +152,10 @@ class GraceWindow {
|
|
|
138
152
|
reassert(party, key, token) {
|
|
139
153
|
const claimed = this.#flights.get(key);
|
|
140
154
|
if (claimed && !token.isNewerThan(claimed.token)) {
|
|
141
|
-
return party.end({ op: 'rejected'
|
|
155
|
+
return party.end({ op: 'rejected' });
|
|
142
156
|
}
|
|
143
157
|
if (claimed) {
|
|
144
|
-
claimed.leader.end({ op: 'rejected'
|
|
158
|
+
claimed.leader.end({ op: 'rejected' });
|
|
145
159
|
this.interrupt(claimed);
|
|
146
160
|
}
|
|
147
161
|
const flight = new Flight(key, token, party);
|
|
@@ -153,17 +167,11 @@ class GraceWindow {
|
|
|
153
167
|
wake();
|
|
154
168
|
}
|
|
155
169
|
land(flight, outcome) {
|
|
156
|
-
this.#endings.set(flight.key, {
|
|
157
|
-
token: flight.token.toString(),
|
|
158
|
-
landed: outcome,
|
|
159
|
-
});
|
|
170
|
+
this.#endings.set(flight.key, { flight, landed: outcome });
|
|
160
171
|
this.#next.land(flight, outcome);
|
|
161
172
|
}
|
|
162
173
|
interrupt(flight) {
|
|
163
|
-
this.#endings.set(flight.key, {
|
|
164
|
-
token: flight.token.toString(),
|
|
165
|
-
interrupted: true,
|
|
166
|
-
});
|
|
174
|
+
this.#endings.set(flight.key, { flight, interrupted: true });
|
|
167
175
|
this.#next.interrupt(flight);
|
|
168
176
|
}
|
|
169
177
|
#reassertionOf(key) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-coordinator.js","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"flight-coordinator.js","sourceRoot":"","sources":["../../src/coordinator/flight-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Term } from '@zukhruf/election';
|
|
2
2
|
export interface FlightServerOptions {
|
|
3
3
|
/** Milliseconds the new term starts no flight, so leaders from the last term can reassert theirs. */
|
|
4
4
|
graceWindow: number;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Serves a `FlightCoordinator` on a Unix socket for one term. Lease tokens
|
|
8
|
-
* carry the term's epoch, so they outrank every token of earlier terms.
|
|
8
|
+
* carry the term's epoch, so they outrank every token of earlier terms. A
|
|
9
|
+
* term that is lost may already be another process's, so the server closes
|
|
10
|
+
* itself when the term's signal aborts.
|
|
9
11
|
*/
|
|
10
12
|
export declare class FlightServer {
|
|
11
13
|
#private;
|
|
12
14
|
private constructor();
|
|
13
|
-
static start(socketPath: string,
|
|
15
|
+
static start(socketPath: string, term: Term, { graceWindow }: FlightServerOptions): Promise<FlightServer>;
|
|
14
16
|
/**
|
|
15
17
|
* Hands the term over. Ending every connection tells the other processes to
|
|
16
18
|
* elect a successor and reassert their flights during its grace window;
|
|
@@ -18,6 +20,10 @@ export declare class FlightServer {
|
|
|
18
20
|
* Each connection ends only after what was written to it, such as an
|
|
19
21
|
* outcome for a joiner, is sent. The term ends only after the socket is
|
|
20
22
|
* 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
|
+
* The first call removes the file at once, so a second caller finds it gone,
|
|
26
|
+
* and it too resolves only once the term ended as the first call ended it.
|
|
21
27
|
*/
|
|
22
28
|
close(): Promise<void>;
|
|
23
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-server.d.ts","sourceRoot":"","sources":["../../src/coordinator/flight-server.ts"],"names":[],"mappings":"
|
|
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;AAQ9C,MAAM,WAAW,mBAAmB;IAClC,qGAAqG;IACrG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,qBAAa,YAAY;;IAOvB,OAAO;WAaM,KAAK,CAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,IAAI,EACV,EAAE,WAAW,EAAE,EAAE,mBAAmB,GACnC,OAAO,CAAC,YAAY,CAAC;IAkCxB;;;;;;;;;;;OAWG;IACG,KAAK;CAaZ"}
|
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { addAbortListener, once } from 'node:events';
|
|
2
|
+
import { rm } from 'node:fs/promises';
|
|
2
3
|
import { createServer } from 'node:net';
|
|
3
4
|
import { EpochTokenSource } from '@zukhruf/fencing';
|
|
4
|
-
import { isErrno } from '@zukhruf/fs';
|
|
5
5
|
import { welcome } from "../connection/handshake.js";
|
|
6
6
|
import { SocketConnection } from "../connection/socket-connection.js";
|
|
7
|
-
import { isRequestEnvelope, } from "../protocol/flight-protocol.js";
|
|
8
7
|
import { FlightCoordinator } from "./flight-coordinator.js";
|
|
9
8
|
/**
|
|
10
9
|
* Serves a `FlightCoordinator` on a Unix socket for one term. Lease tokens
|
|
11
|
-
* carry the term's epoch, so they outrank every token of earlier terms.
|
|
10
|
+
* carry the term's epoch, so they outrank every token of earlier terms. A
|
|
11
|
+
* term that is lost may already be another process's, so the server closes
|
|
12
|
+
* itself when the term's signal aborts.
|
|
12
13
|
*/
|
|
13
14
|
export class FlightServer {
|
|
14
15
|
#server;
|
|
15
16
|
#connections;
|
|
16
|
-
#
|
|
17
|
-
|
|
17
|
+
#coordinator;
|
|
18
|
+
#term;
|
|
19
|
+
#closeOnLoss;
|
|
20
|
+
constructor(server, connections, coordinator, term) {
|
|
18
21
|
this.#server = server;
|
|
19
22
|
this.#connections = connections;
|
|
20
|
-
this.#
|
|
23
|
+
this.#coordinator = coordinator;
|
|
24
|
+
this.#term = term;
|
|
25
|
+
this.#closeOnLoss = addAbortListener(term.signal, () => void this.close());
|
|
21
26
|
}
|
|
22
|
-
static async start(socketPath,
|
|
27
|
+
static async start(socketPath, term, { graceWindow }) {
|
|
23
28
|
// Only the coordinator gets here, so removing a dead coordinator's socket file cannot race another server.
|
|
24
29
|
// Windows removes a named pipe when its process stops, so there is no file to remove.
|
|
25
30
|
if (process.platform !== 'win32') {
|
|
26
|
-
await
|
|
27
|
-
if (!isErrno(error, 'ENOENT'))
|
|
28
|
-
throw error;
|
|
29
|
-
});
|
|
31
|
+
await rm(socketPath, { force: true });
|
|
30
32
|
}
|
|
31
33
|
const coordinator = new FlightCoordinator({
|
|
32
|
-
tokens: new EpochTokenSource(
|
|
34
|
+
tokens: new EpochTokenSource(term.epoch),
|
|
33
35
|
graceWindow,
|
|
34
36
|
});
|
|
35
37
|
const connections = new Set();
|
|
@@ -42,18 +44,19 @@ export class FlightServer {
|
|
|
42
44
|
void welcome(socket).then((speaksOurs) => {
|
|
43
45
|
if (!speaksOurs)
|
|
44
46
|
return;
|
|
45
|
-
coordinator.serve(new SocketConnection(socket
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
await new Promise((resolve, reject) => {
|
|
49
|
-
server.once('error', reject);
|
|
50
|
-
server.listen(socketPath, () => {
|
|
51
|
-
server.off('error', reject);
|
|
52
|
-
resolve();
|
|
47
|
+
coordinator.serve(new SocketConnection(socket));
|
|
53
48
|
});
|
|
54
49
|
});
|
|
50
|
+
server.listen(socketPath);
|
|
51
|
+
await once(server, 'listening');
|
|
55
52
|
server.unref();
|
|
56
|
-
|
|
53
|
+
const started = new FlightServer(server, connections, coordinator, term);
|
|
54
|
+
// The term may have been lost while the server started.
|
|
55
|
+
if (term.signal.aborted) {
|
|
56
|
+
await started.close();
|
|
57
|
+
term.signal.throwIfAborted();
|
|
58
|
+
}
|
|
59
|
+
return started;
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
62
|
* Hands the term over. Ending every connection tells the other processes to
|
|
@@ -62,13 +65,24 @@ export class FlightServer {
|
|
|
62
65
|
* Each connection ends only after what was written to it, such as an
|
|
63
66
|
* outcome for a joiner, is sent. The term ends only after the socket is
|
|
64
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
|
+
* The first call removes the file at once, so a second caller finds it gone,
|
|
71
|
+
* and it too resolves only once the term ended as the first call ended it.
|
|
65
72
|
*/
|
|
66
73
|
async close() {
|
|
67
|
-
|
|
74
|
+
this.#closeOnLoss[Symbol.dispose]();
|
|
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;
|
|
68
82
|
for (const socket of this.#connections)
|
|
69
83
|
socket.destroySoon();
|
|
70
84
|
await closed;
|
|
71
|
-
await this.#
|
|
85
|
+
await this.#term.resign({ clean });
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
//# 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,
|
|
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;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAO5D;;;;;GAKG;AACH,MAAM,OAAO,YAAY;IACd,OAAO,CAAS;IAChB,YAAY,CAAc;IAC1B,YAAY,CAAoB;IAChC,KAAK,CAAO;IACZ,YAAY,CAAa;IAElC,YACE,MAAc,EACd,WAAwB,EACxB,WAA8B,EAC9B,IAAU;QAEV,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,KAAK,CAChB,UAAkB,EAClB,IAAU,EACV,EAAE,WAAW,EAAuB;QAEpC,2GAA2G;QAC3G,sFAAsF;QACtF,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,iBAAiB,CAAC;YACxC,MAAM,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;YACxC,WAAW;SACZ,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QACtC,qHAAqH;QACrH,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;YACrC,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACvD,kHAAkH;YAClH,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;gBACvC,IAAI,CAAC,UAAU;oBAAE,OAAO;gBACxB,WAAW,CAAC,KAAK,CAAC,IAAI,gBAAgB,CAAiB,MAAM,CAAC,CAAC,CAAC;YAClE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1B,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzE,wDAAwD;QACxD,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC/B,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QACnD,qEAAqE;QACrE,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,2EAA2E;QAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,sBAAsB,CAAC;QACvD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,YAAY;YAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAC7D,MAAM,MAAM,CAAC;QACb,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -8,7 +8,9 @@ export declare class Flight {
|
|
|
8
8
|
readonly token: FencingToken;
|
|
9
9
|
readonly leader: Party;
|
|
10
10
|
constructor(key: string, token: FencingToken, leader: Party);
|
|
11
|
-
/**
|
|
11
|
+
/** The flight's name on the wire: its token as decimal text, which a joined answer and a rejoin carry. */
|
|
12
|
+
get id(): string;
|
|
13
|
+
/** Whether this is the flight with the id `flight`, which a rejoin names. */
|
|
12
14
|
is(flight: string): boolean;
|
|
13
15
|
add(joiner: Party): void;
|
|
14
16
|
remove(joiner: Party): void;
|
|
@@ -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,MAAM,EAAE,KAAK,CAAC;gBAGX,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK;IAM3D,
|
|
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,MAAM,EAAE,KAAK,CAAC;gBAGX,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK;IAM3D,0GAA0G;IAC1G,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,6EAA6E;IAC7E,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI3B,GAAG,CAAC,MAAM,EAAE,KAAK;IAKjB,MAAM,CAAC,MAAM,EAAE,KAAK;IAIpB,yFAAyF;IACzF,IAAI,CAAC,OAAO,EAAE,OAAO;IAQrB,0GAA0G;IAC1G,SAAS;CAMV"}
|
|
@@ -9,9 +9,13 @@ export class Flight {
|
|
|
9
9
|
this.token = token;
|
|
10
10
|
this.leader = leader;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
12
|
+
/** The flight's name on the wire: its token as decimal text, which a joined answer and a rejoin carry. */
|
|
13
|
+
get id() {
|
|
14
|
+
return this.token.toString();
|
|
15
|
+
}
|
|
16
|
+
/** Whether this is the flight with the id `flight`, which a rejoin names. */
|
|
13
17
|
is(flight) {
|
|
14
|
-
return flight === this.
|
|
18
|
+
return flight === this.id;
|
|
15
19
|
}
|
|
16
20
|
add(joiner) {
|
|
17
21
|
this.#joiners.add(joiner);
|
|
@@ -23,15 +27,15 @@ export class Flight {
|
|
|
23
27
|
/** Pushes the outcome to every joiner, then tells the leader that it was handed over. */
|
|
24
28
|
land(outcome) {
|
|
25
29
|
for (const joiner of this.#joiners) {
|
|
26
|
-
joiner.end({ op: 'landed',
|
|
30
|
+
joiner.end({ op: 'landed', outcome });
|
|
27
31
|
}
|
|
28
32
|
this.#joiners.clear();
|
|
29
|
-
this.leader.end({ op: 'ack'
|
|
33
|
+
this.leader.end({ op: 'ack' });
|
|
30
34
|
}
|
|
31
35
|
/** Ends without an outcome: every joiner learns that its flight is lost, and none runs the work again. */
|
|
32
36
|
interrupt() {
|
|
33
37
|
for (const joiner of this.#joiners) {
|
|
34
|
-
joiner.end({ op: 'interrupted'
|
|
38
|
+
joiner.end({ op: 'interrupted' });
|
|
35
39
|
}
|
|
36
40
|
this.#joiners.clear();
|
|
37
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight.js","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"AAKA,kHAAkH;AAClH,MAAM,OAAO,MAAM;IACR,GAAG,CAAS;IACZ,KAAK,CAAe;IACpB,MAAM,CAAQ;IACd,QAAQ,GAAG,IAAI,GAAG,EAAS,CAAC;IAErC,YAAY,GAAW,EAAE,KAAmB,EAAE,MAAa;QACzD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"flight.js","sourceRoot":"","sources":["../../src/coordinator/flight.ts"],"names":[],"mappings":"AAKA,kHAAkH;AAClH,MAAM,OAAO,MAAM;IACR,GAAG,CAAS;IACZ,KAAK,CAAe;IACpB,MAAM,CAAQ;IACd,QAAQ,GAAG,IAAI,GAAG,EAAS,CAAC;IAErC,YAAY,GAAW,EAAE,KAAmB,EAAE,MAAa;QACzD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,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,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,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACjC,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"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { FlightResponse } from '../protocol/flight-protocol.ts';
|
|
2
2
|
import type { Flight } from './flight.ts';
|
|
3
|
+
/** A response to one party, without the id: the party fills in its own. */
|
|
4
|
+
type Answer = {
|
|
5
|
+
[Op in FlightResponse['op']]: Omit<Extract<FlightResponse, {
|
|
6
|
+
op: Op;
|
|
7
|
+
}>, 'id'>;
|
|
8
|
+
}[FlightResponse['op']];
|
|
3
9
|
/** Where a party's answers go: the session of the party's connection. */
|
|
4
10
|
export interface Inbox {
|
|
5
11
|
reply(response: FlightResponse): void;
|
|
@@ -28,11 +34,14 @@ export declare class Party {
|
|
|
28
34
|
readonly id: string;
|
|
29
35
|
constructor(id: string, inbox: Inbox);
|
|
30
36
|
get role(): Role;
|
|
37
|
+
/** Whether the party still waits: it has no answer yet, it was not withdrawn, and its connection is not gone. */
|
|
38
|
+
get waiting(): boolean;
|
|
31
39
|
lead(flight: Flight): void;
|
|
32
40
|
/** A leader from before a failover claimed its flight again; it already knows that it leads. */
|
|
33
41
|
resume(flight: Flight): void;
|
|
34
42
|
join(flight: Flight): void;
|
|
35
|
-
/** Answers for good with `
|
|
36
|
-
end(
|
|
43
|
+
/** Answers for good with `answer`, or ends without an answer when it was withdrawn or its connection is gone. */
|
|
44
|
+
end(answer?: Answer): void;
|
|
37
45
|
}
|
|
46
|
+
export {};
|
|
38
47
|
//# sourceMappingURL=party.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"party.d.ts","sourceRoot":"","sources":["../../src/coordinator/party.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,gFAAgF;IAChF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,8CAA8C;AAC9C,MAAM,MAAM,IAAI;AACd,wFAAwF;AACtF;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrC,+DAA+D;GAC7D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,sHAAsH;AACtH,qBAAa,KAAK;;IAChB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAIR,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;IAKpC,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,IAAI,CAAC,MAAM,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"party.d.ts","sourceRoot":"","sources":["../../src/coordinator/party.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,2EAA2E;AAC3E,KAAK,MAAM,GAAG;KACX,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE;QAAE,EAAE,EAAE,EAAE,CAAA;KAAE,CAAC,EAAE,IAAI,CAAC;CAC9E,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;AAExB,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,gFAAgF;IAChF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,8CAA8C;AAC9C,MAAM,MAAM,IAAI;AACd,wFAAwF;AACtF;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrC,+DAA+D;GAC7D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,sHAAsH;AACtH,qBAAa,KAAK;;IAChB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAIR,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;IAKpC,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,iHAAiH;IACjH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,CAAC,MAAM,EAAE,MAAM;IAKnB,gGAAgG;IAChG,MAAM,CAAC,MAAM,EAAE,MAAM;IAIrB,IAAI,CAAC,MAAM,EAAE,MAAM;IAKnB,iHAAiH;IACjH,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM;CAWpB"}
|
|
@@ -10,13 +10,13 @@ export class Party {
|
|
|
10
10
|
get role() {
|
|
11
11
|
return this.#role;
|
|
12
12
|
}
|
|
13
|
+
/** Whether the party still waits: it has no answer yet, it was not withdrawn, and its connection is not gone. */
|
|
14
|
+
get waiting() {
|
|
15
|
+
return this.#role.kind === 'waiting';
|
|
16
|
+
}
|
|
13
17
|
lead(flight) {
|
|
14
18
|
this.#role = { kind: 'leading', flight };
|
|
15
|
-
this.#
|
|
16
|
-
op: 'lead',
|
|
17
|
-
id: this.id,
|
|
18
|
-
token: flight.token.toString(),
|
|
19
|
-
});
|
|
19
|
+
this.#reply({ op: 'lead', token: flight.token.toString() });
|
|
20
20
|
}
|
|
21
21
|
/** A leader from before a failover claimed its flight again; it already knows that it leads. */
|
|
22
22
|
resume(flight) {
|
|
@@ -24,20 +24,20 @@ export class Party {
|
|
|
24
24
|
}
|
|
25
25
|
join(flight) {
|
|
26
26
|
this.#role = { kind: 'joining', flight };
|
|
27
|
-
this.#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
/** Answers for good with `response`, or ends without an answer when it was withdrawn or its connection is gone. */
|
|
34
|
-
end(response) {
|
|
27
|
+
this.#reply({ op: 'joined', flight: flight.id });
|
|
28
|
+
}
|
|
29
|
+
/** Answers for good with `answer`, or ends without an answer when it was withdrawn or its connection is gone. */
|
|
30
|
+
end(answer) {
|
|
35
31
|
if (this.#role.kind === 'gone')
|
|
36
32
|
return;
|
|
37
33
|
this.#role = { kind: 'gone' };
|
|
38
34
|
this.#inbox.forget(this.id);
|
|
39
|
-
if (
|
|
40
|
-
this.#
|
|
35
|
+
if (answer)
|
|
36
|
+
this.#reply(answer);
|
|
37
|
+
}
|
|
38
|
+
#reply(answer) {
|
|
39
|
+
// The id goes right after the op, so each line keeps its bytes: op, id, then the fields of the op.
|
|
40
|
+
this.#inbox.reply(Object.assign({ op: answer.op, id: this.id }, answer));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
//# sourceMappingURL=party.js.map
|