@interopio/bridge 0.0.1-alpha → 0.0.3-beta
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/bin/bridge.js +1 -1
- package/dist/main.js +2139 -0
- package/dist/main.js.map +7 -0
- package/package.json +9 -6
- package/gen/instance/GeneratedBuildInfo.ts +0 -4
- package/src/cluster/Address.ts +0 -57
- package/src/cluster/Cluster.ts +0 -13
- package/src/cluster/Endpoint.ts +0 -5
- package/src/cluster/Member.ts +0 -9
- package/src/cluster/MembershipListener.ts +0 -6
- package/src/config/Config.ts +0 -100
- package/src/config/DiscoveryConfig.ts +0 -21
- package/src/config/Duration.ts +0 -168
- package/src/config/KubernetesConfig.ts +0 -7
- package/src/config/NamedDiscoveryConfig.ts +0 -17
- package/src/config/Properties.ts +0 -49
- package/src/config/index.ts +0 -1
- package/src/discovery/SimpleDiscoveryNode.ts +0 -14
- package/src/discovery/index.ts +0 -207
- package/src/discovery/multicast/MulticastDiscoveryStrategy.ts +0 -141
- package/src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts +0 -30
- package/src/discovery/multicast/MulticastProperties.ts +0 -4
- package/src/discovery/settings.ts +0 -37
- package/src/error/RequestFailure.ts +0 -48
- package/src/gossip/ApplicationState.ts +0 -48
- package/src/gossip/EndpointState.ts +0 -141
- package/src/gossip/FailureDetector.ts +0 -235
- package/src/gossip/Gossiper.ts +0 -1133
- package/src/gossip/HeartbeatState.ts +0 -66
- package/src/gossip/Messenger.ts +0 -130
- package/src/gossip/VersionedValue.ts +0 -59
- package/src/index.ts +0 -3
- package/src/instance/AddressPicker.ts +0 -245
- package/src/instance/BridgeNode.ts +0 -141
- package/src/instance/ClusterTopologyIntentTracker.ts +0 -4
- package/src/io/VersionedSerializer.ts +0 -230
- package/src/io/util.ts +0 -117
- package/src/kubernetes/DnsEndpointResolver.ts +0 -70
- package/src/kubernetes/KubernetesApiEndpointResolver.ts +0 -111
- package/src/kubernetes/KubernetesApiProvider.ts +0 -75
- package/src/kubernetes/KubernetesClient.ts +0 -264
- package/src/kubernetes/KubernetesConfig.ts +0 -130
- package/src/kubernetes/KubernetesDiscoveryStrategy.ts +0 -30
- package/src/kubernetes/KubernetesDiscoveryStrategyFactory.ts +0 -71
- package/src/kubernetes/KubernetesEndpointResolver.ts +0 -43
- package/src/kubernetes/KubernetesProperties.ts +0 -22
- package/src/license/BridgeLicenseValidator.ts +0 -19
- package/src/license/LicenseValidator.ts +0 -114
- package/src/license/types.ts +0 -40
- package/src/logging.ts +0 -22
- package/src/main.mts +0 -53
- package/src/net/Action.ts +0 -143
- package/src/net/AddressSerializer.ts +0 -44
- package/src/net/ByteBufferAllocator.ts +0 -27
- package/src/net/FrameDecoder.ts +0 -314
- package/src/net/FrameEncoder.ts +0 -138
- package/src/net/HandshakeProtocol.ts +0 -143
- package/src/net/InboundConnection.ts +0 -108
- package/src/net/InboundConnectionInitiator.ts +0 -150
- package/src/net/InboundMessageHandler.ts +0 -377
- package/src/net/InboundSink.ts +0 -38
- package/src/net/Message.ts +0 -428
- package/src/net/OutboundConnection.ts +0 -1141
- package/src/net/OutboundConnectionInitiator.ts +0 -76
- package/src/net/RequestCallbacks.ts +0 -148
- package/src/net/ResponseHandler.ts +0 -30
- package/src/net/ShareableBytes.ts +0 -125
- package/src/net/internal/AsyncResourceExecutor.ts +0 -464
- package/src/net/internal/AsyncSocketPromise.ts +0 -37
- package/src/net/internal/channel/ChannelHandlerAdapter.ts +0 -99
- package/src/net/internal/channel/types.ts +0 -188
- package/src/utils/bigint.ts +0 -23
- package/src/utils/buffer.ts +0 -434
- package/src/utils/clock.ts +0 -148
- package/src/utils/collections.ts +0 -283
- package/src/utils/crc.ts +0 -39
- package/src/utils/internal/IpAddressUtil.ts +0 -161
- package/src/utils/memory/BufferPools.ts +0 -40
- package/src/utils/network.ts +0 -130
- package/src/utils/promise.ts +0 -38
- package/src/utils/uuid.ts +0 -5
- package/src/utils/vint.ts +0 -238
- package/src/version/MemberVersion.ts +0 -42
- package/src/version/Version.ts +0 -12
|
@@ -1,1141 +0,0 @@
|
|
|
1
|
-
import {Socket} from 'node:net';
|
|
2
|
-
import {type Address, addressAsString} from '../cluster/Address.ts';
|
|
3
|
-
import {approximateClock, type Clock} from '../utils/clock.ts';
|
|
4
|
-
import {deferred, type DeferredPromise} from '../utils/promise.ts';
|
|
5
|
-
import {ByteBufferOutput, type DataOutput} from '../io/VersionedSerializer.ts';
|
|
6
|
-
import {type Message, serializer} from './Message.ts';
|
|
7
|
-
import getLogger from '../logging.ts';
|
|
8
|
-
import {
|
|
9
|
-
initiateMessaging,
|
|
10
|
-
type MessagingSuccess,
|
|
11
|
-
type OutboundSocketInitiatorResult
|
|
12
|
-
} from './OutboundConnectionInitiator.ts';
|
|
13
|
-
import {writeAndFlush} from './internal/AsyncSocketPromise.ts';
|
|
14
|
-
import {type Payload, type PayloadAllocator} from './FrameEncoder.ts';
|
|
15
|
-
import {MessagingService} from '../gossip/Messenger.ts';
|
|
16
|
-
import {minn} from '../utils/bigint.ts';
|
|
17
|
-
import {fromId} from './Action.ts';
|
|
18
|
-
import {convert, type Unit} from '../config/Duration.ts';
|
|
19
|
-
|
|
20
|
-
const logger = getLogger('net.outbound');
|
|
21
|
-
|
|
22
|
-
class State {
|
|
23
|
-
readonly kind: 'established' | 'connecting' | 'paused' | 'closed';
|
|
24
|
-
|
|
25
|
-
constructor(kind: 'established' | 'connecting' | 'paused' | 'closed') {
|
|
26
|
-
this.kind = kind;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
static readonly CLOSED = new State('closed');
|
|
30
|
-
|
|
31
|
-
isEstablished(): this is Established {
|
|
32
|
-
return this.kind === 'established';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
isConnecting(): this is Connecting {
|
|
36
|
-
return this.kind === 'connecting';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
isDisconnected(): this is Disconnected | Connecting {
|
|
40
|
-
return this.kind === 'connecting' || this.kind === 'paused';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
isClosed(): boolean {
|
|
44
|
-
return this.kind === 'closed';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
class Established extends State {
|
|
49
|
-
readonly socket: Socket;
|
|
50
|
-
readonly version: number;
|
|
51
|
-
readonly allocator: PayloadAllocator;
|
|
52
|
-
readonly settings: OutboundConnectionSettings;
|
|
53
|
-
constructor(version: number, socket: Socket, allocator: PayloadAllocator, settings: OutboundConnectionSettings) {
|
|
54
|
-
super('established');
|
|
55
|
-
this.version = version;
|
|
56
|
-
this.socket = socket;
|
|
57
|
-
this.allocator = allocator;
|
|
58
|
-
this.settings = settings;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
isConnected(): boolean {
|
|
62
|
-
return !this.socket.closed;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
class Disconnected extends State {
|
|
67
|
-
readonly maintenance: ReturnType<typeof setInterval>;
|
|
68
|
-
|
|
69
|
-
constructor(kind: 'connecting' | 'paused', maintenance: ReturnType<typeof setInterval>) {
|
|
70
|
-
super(kind);
|
|
71
|
-
this.maintenance = maintenance;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
static paused(maintenance: ReturnType<typeof setInterval>) {
|
|
75
|
-
return new Disconnected('paused', maintenance);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
class Connecting extends Disconnected {
|
|
80
|
-
readonly failingToConnect: boolean;
|
|
81
|
-
readonly attempt: DeferredPromise<OutboundSocketInitiatorResult<MessagingSuccess>>;
|
|
82
|
-
|
|
83
|
-
readonly scheduled?: ReturnType<typeof setTimeout>;
|
|
84
|
-
|
|
85
|
-
constructor(previous: Disconnected, attempt: DeferredPromise<OutboundSocketInitiatorResult<MessagingSuccess>>, scheduled?: ReturnType<typeof setTimeout>) {
|
|
86
|
-
super('connecting', previous.maintenance);
|
|
87
|
-
this.scheduled = scheduled;
|
|
88
|
-
this.attempt = attempt;
|
|
89
|
-
this.failingToConnect = (scheduled !== undefined) || (previous.isConnecting() && previous.failingToConnect);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
cancel() {
|
|
93
|
-
if (this.scheduled) {
|
|
94
|
-
clearTimeout(this.scheduled);
|
|
95
|
-
}
|
|
96
|
-
this.attempt.cancel();
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
type Removed = { message?: Message<unknown>, next?: Removed };
|
|
102
|
-
|
|
103
|
-
class RemoveRunner {
|
|
104
|
-
removed = new Set<Message<unknown>>();
|
|
105
|
-
readonly ready: Promise<void>;
|
|
106
|
-
private done: () => void;
|
|
107
|
-
private value?: Removed = {};
|
|
108
|
-
private readonly queue: OutboundQueue;
|
|
109
|
-
|
|
110
|
-
constructor(queue: OutboundQueue) {
|
|
111
|
-
this.queue = queue;
|
|
112
|
-
this.ready = new Promise<void>((resolve) => {
|
|
113
|
-
this.done = resolve;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
undo(message: Message<unknown>): boolean {
|
|
118
|
-
const prev = this.value;
|
|
119
|
-
if (prev !== undefined) {
|
|
120
|
-
this.value = {message, next: prev};
|
|
121
|
-
}
|
|
122
|
-
return this.value !== undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
run() {
|
|
126
|
-
const remove = new Set<Message<unknown>>();
|
|
127
|
-
delete this.queue.removeRunner;
|
|
128
|
-
let undo = this.value;
|
|
129
|
-
delete this.value;
|
|
130
|
-
while (undo.message !== undefined) {
|
|
131
|
-
remove.add(undo.message);
|
|
132
|
-
undo = undo.next;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.queue.drainExternalIntoInternal();
|
|
137
|
-
let earliest: bigint = undefined;
|
|
138
|
-
const size = this.queue.internal.length;
|
|
139
|
-
for (let i = 0; i < size; i++) {
|
|
140
|
-
const message = this.queue.internal[i];
|
|
141
|
-
if (!remove.has(message)) {
|
|
142
|
-
if (earliest === undefined || message.header.expiresAtNanos < earliest) {
|
|
143
|
-
earliest = message.header.expiresAtNanos;
|
|
144
|
-
}
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
this.queue.internal.splice(i, 1);
|
|
148
|
-
this.removed.add(message);
|
|
149
|
-
}
|
|
150
|
-
const now = this.queue.clock.now();
|
|
151
|
-
this.queue.maybeUpdateNextExpireTime(now, this.queue.maybeUpdateEarliestExpire(now, earliest));
|
|
152
|
-
|
|
153
|
-
this.done();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
class Locked {
|
|
158
|
-
readonly _run: () => void;
|
|
159
|
-
readonly next?: Locked;
|
|
160
|
-
constructor(run: () => void, next?: Locked) {
|
|
161
|
-
this._run = run;
|
|
162
|
-
this.next = next;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
andThen(next: () => void): Locked {
|
|
166
|
-
return new Locked(next, this);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
run() {
|
|
170
|
-
let current: Locked = this;
|
|
171
|
-
while (current !== undefined) {
|
|
172
|
-
try {
|
|
173
|
-
current._run();
|
|
174
|
-
}
|
|
175
|
-
catch (e) {
|
|
176
|
-
logger.error(`error while executing deferred functions`, e);
|
|
177
|
-
}
|
|
178
|
-
current = current.next;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const LOCKED = new Locked(() => {});
|
|
184
|
-
|
|
185
|
-
class OutboundQueue {
|
|
186
|
-
readonly external: Message<unknown>[] = [];
|
|
187
|
-
readonly internal: Message<unknown>[] = [];
|
|
188
|
-
private earliestExpire?: bigint;
|
|
189
|
-
nextExpireTime?: bigint;
|
|
190
|
-
readonly onExpired: (message: Message<unknown>) => void;
|
|
191
|
-
private locked?: Locked;
|
|
192
|
-
|
|
193
|
-
readonly clock?: Clock;
|
|
194
|
-
|
|
195
|
-
constructor(clock: Clock, onExpired:(message: Message<unknown>) => void) {
|
|
196
|
-
this.clock = clock;
|
|
197
|
-
this.onExpired = onExpired;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
add<T>(message: Message<T>): void {
|
|
201
|
-
this.pruneExpired();
|
|
202
|
-
this.external.push(message);
|
|
203
|
-
this.maybeUpdateEarliestExpire(this.clock.now(), message.header.expiresAtNanos);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private tryLock() {
|
|
207
|
-
if (this.locked === undefined) {
|
|
208
|
-
this.locked = LOCKED;
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
lockOrCallback(nowNs: bigint, callbackIfDeferred: () => void): WithLock | undefined {
|
|
215
|
-
if (!this.doLockOrCallback(callbackIfDeferred)) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return new WithLock(this, nowNs);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
runEventually(runEventually:(withLock: WithLock) => void) {
|
|
223
|
-
const withLock = this.lockOrCallback(this.clock.now(), () => {
|
|
224
|
-
this.runEventually(runEventually);
|
|
225
|
-
});
|
|
226
|
-
try {
|
|
227
|
-
if (withLock) {
|
|
228
|
-
runEventually(withLock);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
finally {
|
|
232
|
-
if (withLock) {
|
|
233
|
-
withLock.close();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
unlock() {
|
|
239
|
-
const l = this.locked;
|
|
240
|
-
delete this.locked;
|
|
241
|
-
if (l) {
|
|
242
|
-
l.run();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
private doLockOrCallback(callbackIfDeferred: () => void): boolean {
|
|
247
|
-
if (callbackIfDeferred === undefined) {
|
|
248
|
-
return this.tryLock();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
while (true)
|
|
252
|
-
{
|
|
253
|
-
const current = this.locked;
|
|
254
|
-
if (current === undefined) {
|
|
255
|
-
this.locked = LOCKED;
|
|
256
|
-
try {
|
|
257
|
-
throw new Error();
|
|
258
|
-
}
|
|
259
|
-
catch (e) {
|
|
260
|
-
LOCKED['owner'] = e;
|
|
261
|
-
}
|
|
262
|
-
return true;
|
|
263
|
-
} else if (this.locked === current) {
|
|
264
|
-
this.locked = current.andThen(callbackIfDeferred);
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private tryRun(runIfAvailable: () => void) {
|
|
271
|
-
if (!this.tryLock()) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
try {
|
|
275
|
-
runIfAvailable();
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
this.unlock();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
pruneExpired(now?: bigint) {
|
|
284
|
-
now = now ?? this.clock.now();
|
|
285
|
-
if (this.nextExpireTime !== undefined && !this.clock.isBefore(this.nextExpireTime, now)) {
|
|
286
|
-
return this.tryRun(() => this.pruneWithLock(now));
|
|
287
|
-
}
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
drainExternalIntoInternal() {
|
|
292
|
-
while (true) {
|
|
293
|
-
const message = this.external.shift();
|
|
294
|
-
if (!message) {
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
this.internal.push(message);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
static shouldSend(message: Message<unknown>, clock: Clock, now: bigint): boolean {
|
|
302
|
-
return clock.isBefore(message.header.expiresAtNanos, now);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
pruneInternalWithLock(now: bigint) {
|
|
306
|
-
let earliest: bigint = undefined;
|
|
307
|
-
const size = this.internal.length;
|
|
308
|
-
for (let i = 0; i < size; i++) {
|
|
309
|
-
const message = this.internal[i];
|
|
310
|
-
if (OutboundQueue.shouldSend(message, this.clock, now)) {
|
|
311
|
-
if (earliest === undefined || message.header.expiresAtNanos < earliest) {
|
|
312
|
-
earliest = message.header.expiresAtNanos;
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
this.internal.splice(i, 1);
|
|
316
|
-
this.onExpired(message);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
this.maybeUpdateNextExpireTime(now, this.maybeUpdateEarliestExpire(now, earliest));
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private pruneWithLock(now: bigint) {
|
|
324
|
-
this.drainExternalIntoInternal();
|
|
325
|
-
this.pruneInternalWithLock(now);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
maybeUpdateNextExpireTime(now: bigint, deadline: bigint): bigint {
|
|
329
|
-
if (this.nextExpireTime === undefined || !this.clock.isBefore(this.nextExpireTime, now)) {
|
|
330
|
-
this.nextExpireTime = deadline;
|
|
331
|
-
} else {
|
|
332
|
-
this.nextExpireTime = minn(this.nextExpireTime, deadline);
|
|
333
|
-
}
|
|
334
|
-
return this.nextExpireTime;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
maybeUpdateEarliestExpire(now: bigint, time: bigint): bigint {
|
|
338
|
-
if (this.earliestExpire === undefined || !this.clock.isBefore(this.earliestExpire, now)) {
|
|
339
|
-
this.earliestExpire = time;
|
|
340
|
-
} else {
|
|
341
|
-
this.earliestExpire = minn(this.earliestExpire, time);
|
|
342
|
-
}
|
|
343
|
-
return this.earliestExpire;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
removeRunner?: RemoveRunner;
|
|
347
|
-
|
|
348
|
-
async remove(remove: Message<unknown>): Promise<boolean> {
|
|
349
|
-
if (!remove) {
|
|
350
|
-
throw new Error('no message');
|
|
351
|
-
}
|
|
352
|
-
let runner: RemoveRunner;
|
|
353
|
-
while (true) {
|
|
354
|
-
runner = this.removeRunner;
|
|
355
|
-
if (runner && runner.undo(remove)) {
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
if (runner === undefined) {
|
|
359
|
-
this.removeRunner = runner = new RemoveRunner(this);
|
|
360
|
-
runner.undo(remove);
|
|
361
|
-
runner.run();
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
await runner.ready;
|
|
366
|
-
return runner.removed.has(remove)
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
class WithLock {
|
|
370
|
-
private readonly queue: OutboundQueue;
|
|
371
|
-
private readonly nowNs: bigint;
|
|
372
|
-
constructor(queue: OutboundQueue, nowNs?: bigint) {
|
|
373
|
-
this.queue = queue;
|
|
374
|
-
this.nowNs = nowNs;
|
|
375
|
-
this.queue.drainExternalIntoInternal();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
removeHead(expected: Message<unknown>): void {
|
|
379
|
-
if (expected !== this.queue.internal[0]) {
|
|
380
|
-
throw new Error('unexpected head');
|
|
381
|
-
}
|
|
382
|
-
this.queue.internal.shift();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
peek(): Message<unknown> {
|
|
386
|
-
let m: Message<unknown>;
|
|
387
|
-
while (undefined !== (m = this.queue.internal[0])) {
|
|
388
|
-
if (OutboundQueue.shouldSend(m, this.queue.clock, this.nowNs)) {
|
|
389
|
-
break;
|
|
390
|
-
}
|
|
391
|
-
this.queue.internal.shift();
|
|
392
|
-
this.queue.onExpired(m);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return m;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
poll(): Message<unknown> {
|
|
399
|
-
let m: Message<unknown>;
|
|
400
|
-
while (undefined !== (m = this.queue.internal.shift())) {
|
|
401
|
-
if (OutboundQueue.shouldSend(m, this.queue.clock, this.nowNs)) {
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
this.queue.onExpired(m);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return m;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
consume(consumer: (msg: Message<unknown>) => void): void {
|
|
411
|
-
let m: Message<unknown>;
|
|
412
|
-
while (undefined !== (m = this.poll())) {
|
|
413
|
-
consumer(m);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
close() {
|
|
418
|
-
try {
|
|
419
|
-
if (!this.queue.clock.isBefore(this.queue.nextExpireTime, this.nowNs)) {
|
|
420
|
-
this.queue.pruneInternalWithLock(this.nowNs);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
this.queue.unlock();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function andThen(a: Runnable | undefined, b: Runnable): Runnable {
|
|
430
|
-
if (a === undefined) {
|
|
431
|
-
return b;
|
|
432
|
-
}
|
|
433
|
-
return () => {a(); b(); return;};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function valueOf(value: number): keyof typeof DeliveryState {
|
|
437
|
-
switch (value) {
|
|
438
|
-
case DeliveryState.STOPPED:
|
|
439
|
-
return 'STOPPED';
|
|
440
|
-
case DeliveryState.EXECUTING:
|
|
441
|
-
return 'EXECUTING';
|
|
442
|
-
case DeliveryState.EXECUTE_AGAIN:
|
|
443
|
-
return 'EXECUTE_AGAIN';
|
|
444
|
-
case DeliveryState.EXECUTING_AGAIN:
|
|
445
|
-
return 'EXECUTING_AGAIN';
|
|
446
|
-
case DeliveryState.WAITING_TO_EXECUTE:
|
|
447
|
-
return 'WAITING_TO_EXECUTE';
|
|
448
|
-
case DeliveryState.EXECUTING_AND_WAITING_TO_EXECUTE:
|
|
449
|
-
return 'EXECUTING_AND_WAITING_TO_EXECUTE';
|
|
450
|
-
default:
|
|
451
|
-
throw new Error('Invalid value');
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const DeliveryState = {
|
|
456
|
-
/*0x0000*/STOPPED: 0,
|
|
457
|
-
/*0x0001*/EXECUTING: 1,
|
|
458
|
-
/*0x0010*/EXECUTE_AGAIN: 2,
|
|
459
|
-
/*0x0011*/EXECUTING_AGAIN: 1 /*EXECUTING*/ | 2 /*EXECUTE_AGAIN*/,
|
|
460
|
-
/*0x0100*/WAITING_TO_EXECUTE: 4,
|
|
461
|
-
/*0x0101*/EXECUTING_AND_WAITING_TO_EXECUTE: 1 /*EXECUTING*/ | 4 /*WAITING_TO_EXECUTE*/,
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
class Initiate {
|
|
465
|
-
private readonly connection: OutboundConnection;
|
|
466
|
-
private retryRateMs = 100;
|
|
467
|
-
private connectionAttempts = 0;
|
|
468
|
-
private successfullConnections = 0;
|
|
469
|
-
private settings: Required<OutboundConnectionSettings>;
|
|
470
|
-
|
|
471
|
-
constructor(connection: OutboundConnection) {
|
|
472
|
-
this.connection = connection;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
private onFailure(err?: Error) {
|
|
476
|
-
const state = this.connection.state as Disconnected;
|
|
477
|
-
if (state.isConnecting()) {
|
|
478
|
-
state.attempt.promise.catch(err => {
|
|
479
|
-
return;
|
|
480
|
-
});
|
|
481
|
-
// await state.attempt.catch((err) => {
|
|
482
|
-
// logger.debug(`handle connection failure`, err);
|
|
483
|
-
// });
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (this.connection.hasPending()) {
|
|
487
|
-
const result = deferred<OutboundSocketInitiatorResult<MessagingSuccess>>();
|
|
488
|
-
this.connection.state = new Connecting(state, result, setTimeout(async () => {
|
|
489
|
-
this.attempt(result);
|
|
490
|
-
}, Math.max(100, this.retryRateMs)));
|
|
491
|
-
this.retryRateMs = Math.min(1000, this.retryRateMs * 2);
|
|
492
|
-
} else {
|
|
493
|
-
this.connection.state = Disconnected.paused(state.maintenance);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private onSuccess(result: OutboundSocketInitiatorResult<MessagingSuccess>) {
|
|
498
|
-
switch (result.outcome) {
|
|
499
|
-
case "success": {
|
|
500
|
-
if (this.connection.state.isClosed()) {
|
|
501
|
-
throw new Error('close did not canceled this. this is a bug');
|
|
502
|
-
}
|
|
503
|
-
clearInterval((this.connection.state as Disconnected).maintenance);
|
|
504
|
-
const allocator = result.allocator;
|
|
505
|
-
const socket = result.socket;
|
|
506
|
-
const established = new Established(result.version, socket, allocator, this.settings);
|
|
507
|
-
this.connection.state = established;
|
|
508
|
-
socket.on('error', (err: Error) => {
|
|
509
|
-
try {
|
|
510
|
-
this.connection.invalidateSocket(established, err);
|
|
511
|
-
} catch (e) {
|
|
512
|
-
logger.error(`unhandled exception in outbound connection`, e);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
++this.successfullConnections;
|
|
517
|
-
logger.info(`successfully connected, version = ${result.version}`);
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
case 'retry': {
|
|
521
|
-
this.initiate();
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
case 'incompatible': {
|
|
525
|
-
this.onFailure(new Error('Incompatible connection'));
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
default: {
|
|
529
|
-
throw new Error();
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
private attempt(result: DeferredPromise<OutboundSocketInitiatorResult<MessagingSuccess>>): void {
|
|
535
|
-
this.connectionAttempts++;
|
|
536
|
-
this.settings = this.connection.template;
|
|
537
|
-
initiateMessaging(this.settings, result)
|
|
538
|
-
.promise
|
|
539
|
-
.then((v) => this.onSuccess(v))
|
|
540
|
-
.catch((err?) => this.onFailure(err));
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
initiate(): Promise<OutboundSocketInitiatorResult<MessagingSuccess>> {
|
|
544
|
-
const def = deferred<OutboundSocketInitiatorResult<MessagingSuccess>>();
|
|
545
|
-
this.connection.state = new Connecting(this.connection.state as Disconnected, def);
|
|
546
|
-
this.attempt(def);
|
|
547
|
-
return def.promise;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
type Runnable = () => void;
|
|
552
|
-
|
|
553
|
-
abstract class Delivery {
|
|
554
|
-
private terminated = false;
|
|
555
|
-
private inProgress = false;
|
|
556
|
-
private _value: keyof typeof DeliveryState = 'STOPPED';
|
|
557
|
-
|
|
558
|
-
private _stopAndRun?: Runnable;
|
|
559
|
-
protected readonly connection: OutboundConnection;
|
|
560
|
-
set value(v) {
|
|
561
|
-
// logger.trace(`set value ${this._value} => ${v}}`);
|
|
562
|
-
this._value = v;
|
|
563
|
-
}
|
|
564
|
-
get value() {
|
|
565
|
-
return this._value;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/*protected */constructor(connection: OutboundConnection) {
|
|
569
|
-
this.connection = connection;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
execute() {
|
|
573
|
-
const state = this.value;
|
|
574
|
-
if (DeliveryState[state] < DeliveryState.EXECUTE_AGAIN) {
|
|
575
|
-
this.value = (state === 'STOPPED') ? 'EXECUTING' : valueOf(DeliveryState[state] | DeliveryState.EXECUTE_AGAIN);
|
|
576
|
-
if (state === 'STOPPED') {
|
|
577
|
-
setImmediate(() => this.run());
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
private isExecuting(state: keyof typeof DeliveryState): boolean {
|
|
583
|
-
return (DeliveryState[state] & DeliveryState.EXECUTING) !== 0;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
executeAgain(): void {
|
|
587
|
-
const state = this.value;
|
|
588
|
-
this.value = !this.isExecuting(state) ? 'EXECUTING' : 'EXECUTE_AGAIN';
|
|
589
|
-
if (!this.isExecuting(state)) {
|
|
590
|
-
setImmediate(() => this.run());
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
promiseToExecute(): void {
|
|
595
|
-
this.value = 'EXECUTING_AND_WAITING_TO_EXECUTE';
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
private maybeExecuteAgain() {
|
|
599
|
-
const state = this.value;
|
|
600
|
-
this.value = state === 'EXECUTING_AGAIN' ? 'EXECUTING' : valueOf(DeliveryState[state] & ~DeliveryState.EXECUTING);
|
|
601
|
-
if (state === 'EXECUTING_AGAIN') {
|
|
602
|
-
setImmediate(() => this.run());
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
terminate() {
|
|
607
|
-
this.terminated = true;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
setInProgress(inProgress: boolean): void {
|
|
611
|
-
const wasInProgress = this.inProgress;
|
|
612
|
-
this.inProgress = inProgress;
|
|
613
|
-
if (wasInProgress && !inProgress) {
|
|
614
|
-
this.executeAgain();
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
run(): void {
|
|
619
|
-
try {
|
|
620
|
-
logger.error("START DELIVERY TASK");
|
|
621
|
-
while (true) {
|
|
622
|
-
if (this.terminated) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
if (this._stopAndRun) {
|
|
626
|
-
if (this.inProgress) {
|
|
627
|
-
this.promiseToExecute();
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
const stopAndRun = this._stopAndRun;
|
|
631
|
-
this._stopAndRun = undefined;
|
|
632
|
-
stopAndRun();
|
|
633
|
-
}
|
|
634
|
-
const state = this.connection.state;
|
|
635
|
-
if (!state.isEstablished() || !state.isConnected()) {
|
|
636
|
-
if (this.connection.hasPending() || this._stopAndRun !== undefined) {
|
|
637
|
-
this.promiseToExecute();
|
|
638
|
-
this.connection.requestConnect().finally(() => {
|
|
639
|
-
this.executeAgain();
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
if (!this.doRun(state)) {
|
|
645
|
-
break;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
} catch (e) {
|
|
649
|
-
logger.error(`error while executing outbound connection`, e);
|
|
650
|
-
} finally {
|
|
651
|
-
logger.error('END DELIVERY TASK');
|
|
652
|
-
this.maybeExecuteAgain();
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
protected abstract doRun(established: Established): boolean;
|
|
657
|
-
|
|
658
|
-
stopAndRun(stopAndRun: Runnable): void {
|
|
659
|
-
this._stopAndRun = andThen(this._stopAndRun, stopAndRun);
|
|
660
|
-
this.execute();
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
abstract stopAndRunOnEventLoop(stopAndRun: Runnable): void
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
class EventLoopDelivery extends Delivery {
|
|
668
|
-
private flushingBytes: number = 0;
|
|
669
|
-
private isWriteable: boolean = true;
|
|
670
|
-
|
|
671
|
-
constructor(connection: OutboundConnection) {
|
|
672
|
-
super(connection);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
doRun(established: Established): boolean {
|
|
676
|
-
if (!this.isWriteable) {
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
679
|
-
const maxSendBytes = Math.min(this.connection.pendingBytes - this.flushingBytes, 64*1024);
|
|
680
|
-
if (maxSendBytes == 0) {
|
|
681
|
-
return false;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const settings = established.settings;
|
|
685
|
-
const version: number = established.version;
|
|
686
|
-
|
|
687
|
-
let payload: Payload = undefined;
|
|
688
|
-
let canonicalSize = 0;
|
|
689
|
-
const sending = {count: 0, bytes: 0};
|
|
690
|
-
|
|
691
|
-
const lock = this.connection.queue.lockOrCallback(approximateClock().now(), () => this.execute());
|
|
692
|
-
|
|
693
|
-
try {
|
|
694
|
-
if (!lock) {
|
|
695
|
-
return false;
|
|
696
|
-
}
|
|
697
|
-
payload = established.allocator(maxSendBytes);
|
|
698
|
-
let out: DataOutput = new ByteBufferOutput(payload.buffer);
|
|
699
|
-
|
|
700
|
-
let next: Message<unknown>;
|
|
701
|
-
while (undefined !== (next = lock.peek())) {
|
|
702
|
-
try {
|
|
703
|
-
|
|
704
|
-
const messageSize = next.serializedSize(version);
|
|
705
|
-
// todo: check message size limit
|
|
706
|
-
if (messageSize > payload.remaining) {
|
|
707
|
-
if (sending.bytes > 0) {
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
|
-
payload.release();
|
|
711
|
-
payload = undefined;
|
|
712
|
-
payload = established.allocator(messageSize);
|
|
713
|
-
|
|
714
|
-
out = new ByteBufferOutput(payload.buffer);
|
|
715
|
-
}
|
|
716
|
-
serializer.serialize(next, out, version);
|
|
717
|
-
if (payload.length != sending.bytes + messageSize) {
|
|
718
|
-
throw new Error('Invalid payload length');
|
|
719
|
-
}
|
|
720
|
-
canonicalSize += this.connection.canonicalSize(next);
|
|
721
|
-
sending.count += 1;
|
|
722
|
-
sending.bytes += messageSize;
|
|
723
|
-
|
|
724
|
-
} catch (e) {
|
|
725
|
-
this.connection.onFailedSerialize(next, version, 0, e);
|
|
726
|
-
payload.trim(sending.bytes);
|
|
727
|
-
}
|
|
728
|
-
lock.removeHead(next);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (sending.bytes == 0) {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
payload.finish();
|
|
735
|
-
|
|
736
|
-
const flushResult = writeAndFlush(established.socket, payload);
|
|
737
|
-
payload = undefined;
|
|
738
|
-
if (flushResult.success) {
|
|
739
|
-
this.connection.sent.count += sending.count;
|
|
740
|
-
this.connection.sent.bytes += sending.bytes;
|
|
741
|
-
}
|
|
742
|
-
else {
|
|
743
|
-
this.flushingBytes += canonicalSize;
|
|
744
|
-
this.setInProgress(true);
|
|
745
|
-
const overflowed = this.flushingBytes >= settings.flushHighWaterMark
|
|
746
|
-
if (overflowed) {
|
|
747
|
-
this.isWriteable = false;
|
|
748
|
-
this.promiseToExecute();
|
|
749
|
-
}
|
|
750
|
-
const releaseBytes = canonicalSize;
|
|
751
|
-
const sendingBytes = sending.bytes;
|
|
752
|
-
const sendingCount = sending.count;
|
|
753
|
-
flushResult.promise
|
|
754
|
-
.then(() => {return {success: true}})
|
|
755
|
-
.catch((error?: Error) => {return {success: false, error}})
|
|
756
|
-
.then(future => {
|
|
757
|
-
this.connection.releaseCapacity(sendingCount, releaseBytes);
|
|
758
|
-
this.flushingBytes -= releaseBytes;
|
|
759
|
-
if (this.flushingBytes === 0) {
|
|
760
|
-
this.setInProgress(false);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (!this.isWriteable && this.flushingBytes <= settings.flushLowWaterMark) {
|
|
764
|
-
this.isWriteable = true;
|
|
765
|
-
this.executeAgain();
|
|
766
|
-
}
|
|
767
|
-
if (future.success) {
|
|
768
|
-
this.connection.sent.count += sendingCount;
|
|
769
|
-
this.connection.sent.bytes += sendingBytes;
|
|
770
|
-
} else {
|
|
771
|
-
this.connection.error.count += sendingCount;
|
|
772
|
-
this.connection.error.bytes += sendingBytes;
|
|
773
|
-
this.connection.invalidateSocket(established, future['error']);
|
|
774
|
-
}
|
|
775
|
-
});
|
|
776
|
-
canonicalSize = 0;
|
|
777
|
-
}
|
|
778
|
-
} catch (e) {
|
|
779
|
-
this.connection.error.count += sending.count;
|
|
780
|
-
this.connection.error.bytes += sending.bytes;
|
|
781
|
-
this.connection.invalidateSocket(established, e);
|
|
782
|
-
}
|
|
783
|
-
finally {
|
|
784
|
-
if (lock) {
|
|
785
|
-
lock.close();
|
|
786
|
-
}
|
|
787
|
-
if (canonicalSize > 0) {
|
|
788
|
-
this.connection.releaseCapacity(sending.count, canonicalSize);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
if (payload) {
|
|
792
|
-
payload.release();
|
|
793
|
-
}
|
|
794
|
-
if (this.connection.pendingBytes > this.flushingBytes && this.isWriteable) {
|
|
795
|
-
this.execute();
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
return false;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
stopAndRunOnEventLoop(stopAndRun: Runnable): void {
|
|
803
|
-
this.stopAndRun(stopAndRun);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
export interface OutboundMessageCallbacks {
|
|
809
|
-
|
|
810
|
-
onExpired(message: Message<unknown>, peer: Address): void;
|
|
811
|
-
onFailedSerialize(message: Message<unknown>, peer: Address, bytesWritten: number, error: Error): void;
|
|
812
|
-
onDiscardOnClose(message: Message<unknown>, peer: Address): void;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
export class OutboundConnectionSettings {
|
|
816
|
-
readonly acceptVersions = {min: MessagingService.CURRENT_VERSION, max: 13};
|
|
817
|
-
readonly from: Address;
|
|
818
|
-
readonly to: Address;
|
|
819
|
-
readonly connectTo?: Address;
|
|
820
|
-
readonly flushLowWaterMark: number;
|
|
821
|
-
readonly flushHighWaterMark: number;
|
|
822
|
-
readonly tcpNoDelay?: boolean;
|
|
823
|
-
readonly callbacks: OutboundMessageCallbacks;
|
|
824
|
-
|
|
825
|
-
constructor(from: Address, to: Address, callbacks: OutboundMessageCallbacks, flushLowWaterMark = 1<<15, flushHighWaterMark = 1 << 16, connectTo?: Address, tcpNoDelay?: boolean) {
|
|
826
|
-
this.from = from;
|
|
827
|
-
this.to = to;
|
|
828
|
-
this.connectTo = connectTo;
|
|
829
|
-
this.tcpNoDelay = tcpNoDelay;
|
|
830
|
-
this.flushLowWaterMark = flushLowWaterMark;
|
|
831
|
-
this.flushHighWaterMark = flushHighWaterMark;
|
|
832
|
-
this.callbacks = callbacks;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
get connectToId(): string {
|
|
836
|
-
const connectTo = this.connectTo;
|
|
837
|
-
if (connectTo === undefined) {
|
|
838
|
-
return addressAsString(this.to);
|
|
839
|
-
}
|
|
840
|
-
return `${addressAsString(this.to)} (${addressAsString(connectTo)})`;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
getConnectTo(): Address {
|
|
844
|
-
const connectTo = this.connectTo;
|
|
845
|
-
if (connectTo === undefined) {
|
|
846
|
-
return this.to;
|
|
847
|
-
}
|
|
848
|
-
return connectTo;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
getTcpNoDelay(): boolean {
|
|
852
|
-
const tcpNoDelay = this.tcpNoDelay;
|
|
853
|
-
if (tcpNoDelay === undefined) {
|
|
854
|
-
return true;
|
|
855
|
-
}
|
|
856
|
-
return tcpNoDelay;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
withDefaults(): Required<OutboundConnectionSettings> {
|
|
860
|
-
return new OutboundConnectionSettings(this.from, this.to, this.callbacks, this.flushLowWaterMark, this.flushHighWaterMark, this.getConnectTo(), this.getTcpNoDelay()) as Required<OutboundConnectionSettings>;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
toString() {
|
|
864
|
-
return `peer: (${addressAsString(this.to)}, ${addressAsString(this.connectTo)})`;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
export class OutboundConnection {
|
|
869
|
-
state: State;
|
|
870
|
-
|
|
871
|
-
queue: OutboundQueue;
|
|
872
|
-
private readonly delivery = new EventLoopDelivery(this);
|
|
873
|
-
private closing?: DeferredPromise<void>;
|
|
874
|
-
private scheduledClose?: DeferredPromise<void>;
|
|
875
|
-
private readonly maxMessageSize: number = 1024 * 1024;
|
|
876
|
-
template: Required<OutboundConnectionSettings>;
|
|
877
|
-
private readonly callbacks: OutboundMessageCallbacks;
|
|
878
|
-
|
|
879
|
-
constructor(settings: OutboundConnectionSettings) {
|
|
880
|
-
this.template = settings.withDefaults();
|
|
881
|
-
this.callbacks = this.template.callbacks;
|
|
882
|
-
this.queue = new OutboundQueue(approximateClock(), (msg)=> this.onExpired(msg));
|
|
883
|
-
this.setDisconnected();
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
private onExpired(message: Message<unknown>) {
|
|
887
|
-
if (logger.enabledFor('debug')) {
|
|
888
|
-
const rpcTimeout = 200;
|
|
889
|
-
logger.debug(`dropping ${fromId(message.header.verb().id)} with payload ${message.payload} whose timeout (${rpcTimeout}) expired before reaching network.`);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const expiredBytes = this.canonicalSize(message);
|
|
893
|
-
this.releaseCapacity(1, expiredBytes);
|
|
894
|
-
this.expired.count += 1;
|
|
895
|
-
this.expired.bytes += expiredBytes;
|
|
896
|
-
this.callbacks.onExpired(message, this.template.to);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
onFailedSerialize(message: Message<unknown>, version: number, bytesWritten:number, error: Error) {
|
|
900
|
-
const bytes = message.serializedSize(version);
|
|
901
|
-
this.releaseCapacity(1, bytes);
|
|
902
|
-
this.error.count += 1;
|
|
903
|
-
this.error.bytes += bytes;
|
|
904
|
-
this.callbacks.onFailedSerialize(message, this.template.to, bytesWritten, error);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
onClosed(message: Message<unknown>) {
|
|
908
|
-
this.releaseCapacity(1, this.canonicalSize(message));
|
|
909
|
-
this.callbacks.onDiscardOnClose(message, this.template.to);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
enqueue<T>(message: Message<T>): void {
|
|
913
|
-
if (this.isClosing()) {
|
|
914
|
-
throw new Error('socket closed');
|
|
915
|
-
}
|
|
916
|
-
const canonicalSize = this.canonicalSize(message);
|
|
917
|
-
this.acquireCapacity(1, canonicalSize);
|
|
918
|
-
this.queue.add(message);
|
|
919
|
-
this.delivery.execute();
|
|
920
|
-
|
|
921
|
-
// if (this.isClosing() && await this.queue.remove(message)) {
|
|
922
|
-
// this.releaseCapacity(1, canonicalSize);
|
|
923
|
-
// throw new Error('socket closing');
|
|
924
|
-
// }
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
private readonly pending = {count: 0, bytes: 0};
|
|
928
|
-
private readonly expired = {count: 0, bytes: 0};
|
|
929
|
-
readonly error = {count: 0, bytes: 0};
|
|
930
|
-
readonly sent = {count: 0, bytes: 0};
|
|
931
|
-
|
|
932
|
-
get pendingBytes(): number {
|
|
933
|
-
return this.pending.bytes;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
private acquireCapacity(count: number = 1, bytes: number): 'success' {
|
|
937
|
-
this.pending.count += count;
|
|
938
|
-
this.pending.bytes += bytes;
|
|
939
|
-
return 'success';
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
releaseCapacity(count: number, bytes: number): number {
|
|
943
|
-
this.pending.count -= count;
|
|
944
|
-
this.pending.bytes -= bytes;
|
|
945
|
-
return this.pending.count;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
canonicalSize(message: Message<unknown>): number {
|
|
949
|
-
return message.serializedSize(MessagingService.CURRENT_VERSION);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
invalidateSocket(established: Established, err?: Error): void {
|
|
953
|
-
if (this.state !== established) {
|
|
954
|
-
return; // do nothing; socket already closed
|
|
955
|
-
}
|
|
956
|
-
this.disconnectNow(established);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
get connected() {
|
|
960
|
-
return this.state.isEstablished() && this.state.isConnected();
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
private isClosing(): boolean {
|
|
964
|
-
return this.closing !== undefined;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
private setDisconnected() {
|
|
968
|
-
if (this.state !== undefined && !this.state.isEstablished()) {
|
|
969
|
-
throw new Error();
|
|
970
|
-
}
|
|
971
|
-
this.state = Disconnected.paused(setInterval(this.queue.pruneExpired.bind(this.queue), 100));
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
scheduleClose(time: number, unit: Unit, flushQueue: boolean): DeferredPromise<void> {
|
|
975
|
-
if (this.scheduledClose === undefined) {
|
|
976
|
-
let timeoutId: ReturnType<typeof setTimeout>;
|
|
977
|
-
|
|
978
|
-
this.scheduledClose = deferred<void>((cb) => {
|
|
979
|
-
clearTimeout(timeoutId);
|
|
980
|
-
return true;
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
timeoutId = setTimeout(() => {
|
|
984
|
-
this.close(flushQueue).then(() => {
|
|
985
|
-
this.scheduledClose.success();
|
|
986
|
-
}).catch(error => {
|
|
987
|
-
this.scheduledClose.failure(error);
|
|
988
|
-
});
|
|
989
|
-
}, Number(convert('milliseconds', time, unit)));
|
|
990
|
-
}
|
|
991
|
-
return this.scheduledClose;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
close(flushQueue: boolean): Promise<void> {
|
|
995
|
-
if (this.closing === undefined) {
|
|
996
|
-
this.closing = deferred<void>((cb) => {
|
|
997
|
-
return true;
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
const eventLoopCleanup = () => {
|
|
1001
|
-
const onceNotConnecting = () => {
|
|
1002
|
-
const state = this.state;
|
|
1003
|
-
this.state = State.CLOSED;
|
|
1004
|
-
|
|
1005
|
-
try {
|
|
1006
|
-
this.delivery.terminate();
|
|
1007
|
-
|
|
1008
|
-
if (state.isDisconnected()) {
|
|
1009
|
-
clearInterval(state.maintenance);
|
|
1010
|
-
this.closing.success();
|
|
1011
|
-
}
|
|
1012
|
-
else {
|
|
1013
|
-
if (!state.isEstablished()) {
|
|
1014
|
-
throw new Error(`Invalid state: ${state}`);
|
|
1015
|
-
}
|
|
1016
|
-
state.socket.once('close', (hadError) => {
|
|
1017
|
-
this.closing.success();
|
|
1018
|
-
});
|
|
1019
|
-
state.socket.destroy();
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
} catch (ex) {
|
|
1023
|
-
this.closing.success();
|
|
1024
|
-
|
|
1025
|
-
try {
|
|
1026
|
-
if (state.isEstablished()) {
|
|
1027
|
-
state.socket.destroy();
|
|
1028
|
-
}
|
|
1029
|
-
} catch (e) {
|
|
1030
|
-
logger.error('Failed to close the connection cleanly', e);
|
|
1031
|
-
logger.error('suppressed', ex);
|
|
1032
|
-
}
|
|
1033
|
-
throw ex;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
if (this.state.isConnecting()) {
|
|
1038
|
-
this.state.cancel();
|
|
1039
|
-
this.state.attempt.promise.catch(e => {}).finally(() => onceNotConnecting());
|
|
1040
|
-
}
|
|
1041
|
-
else {
|
|
1042
|
-
onceNotConnecting();
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
const clearQueue = async () => {
|
|
1048
|
-
await new Promise<void>(resolve => {
|
|
1049
|
-
this.queue.runEventually((withLock) => {
|
|
1050
|
-
withLock.consume((msg) => this.onClosed(msg));
|
|
1051
|
-
resolve();
|
|
1052
|
-
});
|
|
1053
|
-
});
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
if(flushQueue)
|
|
1057
|
-
{
|
|
1058
|
-
const finishDelivery = async () => {
|
|
1059
|
-
if (!this.hasPending()) {
|
|
1060
|
-
this.delivery.stopAndRunOnEventLoop(async () => await eventLoopCleanup());
|
|
1061
|
-
}
|
|
1062
|
-
else {
|
|
1063
|
-
this.delivery.stopAndRun(async () => {
|
|
1064
|
-
if (this.state.isConnecting() && this.state.failingToConnect) {
|
|
1065
|
-
await clearQueue();
|
|
1066
|
-
}
|
|
1067
|
-
await finishDelivery();
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
|
|
1072
|
-
this.delivery.stopAndRun(finishDelivery);
|
|
1073
|
-
}
|
|
1074
|
-
else {
|
|
1075
|
-
this.delivery.stopAndRunOnEventLoop(async () => {
|
|
1076
|
-
await clearQueue();
|
|
1077
|
-
eventLoopCleanup();
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
return this.closing.promise;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
initiate(): Promise<unknown> {
|
|
1085
|
-
return new Initiate(this).initiate();
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
requestConnect(): Promise<unknown> {
|
|
1089
|
-
if (this.state?.isConnecting()) {
|
|
1090
|
-
return this.state.attempt.promise
|
|
1091
|
-
}
|
|
1092
|
-
return new Promise<void>((resolve, reject) => {
|
|
1093
|
-
setImmediate(() => {
|
|
1094
|
-
if (this.state?.isClosed()) {
|
|
1095
|
-
reject(new Error('socket closed'));
|
|
1096
|
-
} else if (this.state.isEstablished() && this.state.isConnected()) {
|
|
1097
|
-
resolve();
|
|
1098
|
-
} else {
|
|
1099
|
-
if (this.state.isEstablished()) {
|
|
1100
|
-
this.setDisconnected();
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
if (!this.state.isConnecting()) {
|
|
1104
|
-
this.initiate().then(resolve).catch(reject);
|
|
1105
|
-
} else {
|
|
1106
|
-
this.state.attempt.promise.then(() => resolve()).catch(reject);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
});
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
private disconnectNow(closeIfIs: Established): Promise<void> {
|
|
1115
|
-
return new Promise<void>(resolve => {
|
|
1116
|
-
setImmediate(() => {
|
|
1117
|
-
if (this.state === closeIfIs) {
|
|
1118
|
-
this.setDisconnected();
|
|
1119
|
-
if (this.hasPending()) {
|
|
1120
|
-
this.delivery.execute();
|
|
1121
|
-
}
|
|
1122
|
-
closeIfIs.socket.once('close', (hadError: boolean) => {
|
|
1123
|
-
if (hadError) {
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1127
|
-
closeIfIs.socket.destroy();
|
|
1128
|
-
resolve();
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
hasPending() {
|
|
1135
|
-
return this.pending.count !== 0 && this.pending.bytes !== 0;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
/*testing*/ unsafeRunDelivery(run : () => void): void {
|
|
1139
|
-
this.delivery.stopAndRun(run);
|
|
1140
|
-
}
|
|
1141
|
-
}
|