@interopio/bridge 0.0.1-alpha
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 +9 -0
- package/gen/instance/GeneratedBuildInfo.ts +4 -0
- package/license.md +5 -0
- package/package.json +40 -0
- package/readme.md +10 -0
- package/src/cluster/Address.ts +57 -0
- package/src/cluster/Cluster.ts +13 -0
- package/src/cluster/Endpoint.ts +5 -0
- package/src/cluster/Member.ts +9 -0
- package/src/cluster/MembershipListener.ts +6 -0
- package/src/config/Config.ts +100 -0
- package/src/config/DiscoveryConfig.ts +21 -0
- package/src/config/Duration.ts +168 -0
- package/src/config/KubernetesConfig.ts +7 -0
- package/src/config/NamedDiscoveryConfig.ts +17 -0
- package/src/config/Properties.ts +49 -0
- package/src/config/index.ts +1 -0
- package/src/discovery/SimpleDiscoveryNode.ts +14 -0
- package/src/discovery/index.ts +207 -0
- package/src/discovery/multicast/MulticastDiscoveryStrategy.ts +141 -0
- package/src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts +30 -0
- package/src/discovery/multicast/MulticastProperties.ts +4 -0
- package/src/discovery/settings.ts +37 -0
- package/src/error/RequestFailure.ts +48 -0
- package/src/gossip/ApplicationState.ts +48 -0
- package/src/gossip/EndpointState.ts +141 -0
- package/src/gossip/FailureDetector.ts +235 -0
- package/src/gossip/Gossiper.ts +1133 -0
- package/src/gossip/HeartbeatState.ts +66 -0
- package/src/gossip/Messenger.ts +130 -0
- package/src/gossip/VersionedValue.ts +59 -0
- package/src/index.ts +3 -0
- package/src/instance/AddressPicker.ts +245 -0
- package/src/instance/BridgeNode.ts +141 -0
- package/src/instance/ClusterTopologyIntentTracker.ts +4 -0
- package/src/io/VersionedSerializer.ts +230 -0
- package/src/io/util.ts +117 -0
- package/src/kubernetes/DnsEndpointResolver.ts +70 -0
- package/src/kubernetes/KubernetesApiEndpointResolver.ts +111 -0
- package/src/kubernetes/KubernetesApiProvider.ts +75 -0
- package/src/kubernetes/KubernetesClient.ts +264 -0
- package/src/kubernetes/KubernetesConfig.ts +130 -0
- package/src/kubernetes/KubernetesDiscoveryStrategy.ts +30 -0
- package/src/kubernetes/KubernetesDiscoveryStrategyFactory.ts +71 -0
- package/src/kubernetes/KubernetesEndpointResolver.ts +43 -0
- package/src/kubernetes/KubernetesProperties.ts +22 -0
- package/src/license/BridgeLicenseValidator.ts +19 -0
- package/src/license/LicenseValidator.ts +114 -0
- package/src/license/types.ts +40 -0
- package/src/logging.ts +22 -0
- package/src/main.mts +53 -0
- package/src/net/Action.ts +143 -0
- package/src/net/AddressSerializer.ts +44 -0
- package/src/net/ByteBufferAllocator.ts +27 -0
- package/src/net/FrameDecoder.ts +314 -0
- package/src/net/FrameEncoder.ts +138 -0
- package/src/net/HandshakeProtocol.ts +143 -0
- package/src/net/InboundConnection.ts +108 -0
- package/src/net/InboundConnectionInitiator.ts +150 -0
- package/src/net/InboundMessageHandler.ts +377 -0
- package/src/net/InboundSink.ts +38 -0
- package/src/net/Message.ts +428 -0
- package/src/net/OutboundConnection.ts +1141 -0
- package/src/net/OutboundConnectionInitiator.ts +76 -0
- package/src/net/RequestCallbacks.ts +148 -0
- package/src/net/ResponseHandler.ts +30 -0
- package/src/net/ShareableBytes.ts +125 -0
- package/src/net/internal/AsyncResourceExecutor.ts +464 -0
- package/src/net/internal/AsyncSocketPromise.ts +37 -0
- package/src/net/internal/channel/ChannelHandlerAdapter.ts +99 -0
- package/src/net/internal/channel/types.ts +188 -0
- package/src/utils/bigint.ts +23 -0
- package/src/utils/buffer.ts +434 -0
- package/src/utils/clock.ts +148 -0
- package/src/utils/collections.ts +283 -0
- package/src/utils/crc.ts +39 -0
- package/src/utils/internal/IpAddressUtil.ts +161 -0
- package/src/utils/memory/BufferPools.ts +40 -0
- package/src/utils/network.ts +130 -0
- package/src/utils/promise.ts +38 -0
- package/src/utils/uuid.ts +5 -0
- package/src/utils/vint.ts +238 -0
- package/src/version/MemberVersion.ts +42 -0
- package/src/version/Version.ts +12 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {type Address, addressAsString} from '../cluster/Address.ts';
|
|
2
|
+
import {createServer, type Server, type Socket} from 'node:net';
|
|
3
|
+
import {deferred, type DeferredPromise} from '../utils/promise.ts';
|
|
4
|
+
import {type InboundConnectionSettings} from './InboundConnection.ts';
|
|
5
|
+
import getLogger from '../logging.ts';
|
|
6
|
+
import {adopt, type ByteBuffer} from '../utils/buffer.ts';
|
|
7
|
+
import {Accept, Initiate} from './HandshakeProtocol.ts';
|
|
8
|
+
import {writeAndFlush} from './internal/AsyncSocketPromise.ts';
|
|
9
|
+
import {createCrc, createUnprotected, type FrameDecoder} from './FrameDecoder.ts';
|
|
10
|
+
import {globalByteBufferAllocator} from './ByteBufferAllocator.ts';
|
|
11
|
+
|
|
12
|
+
const logger = getLogger('net.inbound.initiator');
|
|
13
|
+
|
|
14
|
+
function addressId(address: Address, realAddress: {host: string, type: 4 | 6, port: number}) {
|
|
15
|
+
let result = addressAsString(address);
|
|
16
|
+
if ((address.address.address !== realAddress.host) || (address.port !== realAddress.port)) {
|
|
17
|
+
result += ` (${addressAsString(realAddress)})`;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class Handler {
|
|
23
|
+
|
|
24
|
+
private readonly settings: InboundConnectionSettings;
|
|
25
|
+
private socket: Socket;
|
|
26
|
+
private initiate?: Initiate;
|
|
27
|
+
private handshakeTimeoutId: ReturnType<typeof setTimeout>;
|
|
28
|
+
|
|
29
|
+
constructor(settings: InboundConnectionSettings) {
|
|
30
|
+
this.settings = settings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private onData: (data: Buffer) => void;
|
|
34
|
+
private onError: (error: Error) => void;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
activate(socket: Socket) {
|
|
38
|
+
this.socket = socket;
|
|
39
|
+
const remoteAddress: Pick<Address, 'host' | 'port' | 'type'> = {
|
|
40
|
+
host: socket.remoteAddress,
|
|
41
|
+
port: socket.remotePort,
|
|
42
|
+
type: socket.remoteFamily === 'IPv6' ? 6 : 4
|
|
43
|
+
};
|
|
44
|
+
this.handshakeTimeoutId = setTimeout(() => {
|
|
45
|
+
logger.error(`timeout handshake with ${addressId(this.initiate?.from, remoteAddress)}`);
|
|
46
|
+
this.failHandshake();
|
|
47
|
+
}, this.settings.handshakeTimeout ?? 30000);
|
|
48
|
+
this.onData = (data: Buffer) => {
|
|
49
|
+
if (this.initiate) {
|
|
50
|
+
throw new Error(`should not receive data after handshake: ${this.initiate}`);
|
|
51
|
+
}
|
|
52
|
+
this.acceptInitiate(adopt(data));
|
|
53
|
+
}
|
|
54
|
+
this.onError = (error: Error) => {
|
|
55
|
+
logger.error(`failed to properly handshake with peer ${socket.remoteAddress}`, error);
|
|
56
|
+
try {
|
|
57
|
+
this.failHandshake();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
logger.error(`failed to fail handshake: ${e}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
socket.on('data', this.onData);
|
|
63
|
+
socket.on('error', this.onError);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private failHandshake() {
|
|
67
|
+
clearTimeout(this.handshakeTimeoutId);
|
|
68
|
+
this.socket.off('data', this.onData);
|
|
69
|
+
this.socket.off('error', this.onError);
|
|
70
|
+
this.socket.destroy();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private acceptInitiate(input: ByteBuffer) {
|
|
74
|
+
this.initiate = Initiate.maybeDecode(input);
|
|
75
|
+
if (!this.initiate) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (logger.enabledFor('debug')) {
|
|
80
|
+
logger.debug(`connection version ${this.initiate.acceptVersions.max} (min ${this.initiate.acceptVersions.min}) from ${addressAsString(this.initiate.from)}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const acceptVersions = this.settings.acceptVersions ?? {min: 13, max: 13};
|
|
84
|
+
const useVersion = Math.max(acceptVersions.min, Math.min(acceptVersions.max, this.initiate.acceptVersions.max));
|
|
85
|
+
|
|
86
|
+
const buffer = new Accept(useVersion, acceptVersions.max).encode();
|
|
87
|
+
|
|
88
|
+
const socketPromise = writeAndFlush(this.socket, {selfContained: true, buffer});
|
|
89
|
+
socketPromise.promise?.catch((err) => {
|
|
90
|
+
this.onError(err);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (this.initiate.acceptVersions.min > acceptVersions.max) {
|
|
94
|
+
logger.info(`peer ${this.socket.remoteAddress} only supports messaging versions higher (${this.initiate.acceptVersions.min}) than this node supports (${acceptVersions.min})`);
|
|
95
|
+
this.failHandshake();
|
|
96
|
+
}
|
|
97
|
+
else if (this.initiate.acceptVersions.max < acceptVersions.min) {
|
|
98
|
+
logger.info(`peer ${this.socket.remoteAddress} only supports messaging versions lower (${this.initiate.acceptVersions.max}) than this node supports (${acceptVersions.min})`);
|
|
99
|
+
this.failHandshake();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
else {
|
|
103
|
+
clearTimeout(this.handshakeTimeoutId);
|
|
104
|
+
|
|
105
|
+
const allocator = globalByteBufferAllocator;
|
|
106
|
+
let frameDecoder: FrameDecoder;
|
|
107
|
+
switch (this.initiate.framing) {
|
|
108
|
+
case 'UNPROTECTED': {
|
|
109
|
+
frameDecoder = createUnprotected(allocator);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'CRC': {
|
|
113
|
+
frameDecoder = createCrc(allocator);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
default: {
|
|
117
|
+
throw new Error(`unknown framing ${this.initiate.framing}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.settings.handlers(this.initiate.from).handle(frameDecoder, this.socket, useVersion);
|
|
121
|
+
this.socket.off('data', this.onData);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
function inboundSocketInitiator(settings: InboundConnectionSettings, connections: Socket[]): DeferredPromise<Server> {
|
|
129
|
+
const {host, port} = settings.bindAddress;
|
|
130
|
+
logger.info('listening on %s:%d', host, port);
|
|
131
|
+
const backlog = 1 << 9;
|
|
132
|
+
|
|
133
|
+
const s = createServer({keepAlive: true, noDelay: true}, (socket: Socket) => {
|
|
134
|
+
const handler = new Handler(settings);
|
|
135
|
+
connections.push(socket);
|
|
136
|
+
handler.activate(socket);
|
|
137
|
+
});
|
|
138
|
+
const d = deferred<Server>((cb: (err?: Error) => void) => {
|
|
139
|
+
s.close(cb);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
s.listen(port, host, backlog, () => {
|
|
143
|
+
d.success(s);
|
|
144
|
+
});
|
|
145
|
+
return d;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function bind(settings: InboundConnectionSettings, connections: Socket[]) {
|
|
149
|
+
return inboundSocketInitiator(settings, connections);
|
|
150
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {Socket} from 'node:net';
|
|
2
|
+
import {type Address, addressAsString} from '../cluster/Address.ts';
|
|
3
|
+
import {adopt} from '../utils/buffer.ts';
|
|
4
|
+
import type {Unit} from '../config/Duration.ts';
|
|
5
|
+
import {type Message, type MessageHeader, serializer} from './Message.ts';
|
|
6
|
+
import type {ShareableBytes} from './ShareableBytes.ts';
|
|
7
|
+
import type {CorruptedFrame, Frame, FrameDecoder, ValidFrame} from './FrameDecoder.ts';
|
|
8
|
+
import {approximateClock} from '../utils/clock.ts';
|
|
9
|
+
import {ByteBufferInput} from '../io/VersionedSerializer.ts';
|
|
10
|
+
import getLogger from '../logging.ts';
|
|
11
|
+
|
|
12
|
+
const logger = getLogger('net.inbound.handler');
|
|
13
|
+
|
|
14
|
+
interface InboundMessageCallbacks {
|
|
15
|
+
|
|
16
|
+
onHeaderArrived(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void;
|
|
17
|
+
|
|
18
|
+
onArrived(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void;
|
|
19
|
+
|
|
20
|
+
onArrivedExpired(messageSize: number, header: MessageHeader, wasCorrupt: boolean, timeElapsed: bigint, timeUnit: Unit): void;
|
|
21
|
+
|
|
22
|
+
onArrivedCorrupt(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void;
|
|
23
|
+
|
|
24
|
+
onClosedBeforeArrival(messageSize: number, header: MessageHeader, bytesReceived: number, wasCorrupt: boolean, wasExpired: boolean): void;
|
|
25
|
+
|
|
26
|
+
onFailedDeserialize(messageSize: number, header: MessageHeader, error: Error): void;
|
|
27
|
+
|
|
28
|
+
onDispatched(messageSize: number, header: MessageHeader): void;
|
|
29
|
+
|
|
30
|
+
onExecuting(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void;
|
|
31
|
+
|
|
32
|
+
onProcessed(messageSize: number, header: MessageHeader): void;
|
|
33
|
+
|
|
34
|
+
onExpired(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void;
|
|
35
|
+
|
|
36
|
+
onExecuted(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit): void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
abstract class ProcessMessage {
|
|
40
|
+
private readonly handler: InboundMessageHandler;
|
|
41
|
+
|
|
42
|
+
constructor(handler: InboundMessageHandler) {
|
|
43
|
+
this.handler = handler;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
run(): void {
|
|
47
|
+
const header = this.header;
|
|
48
|
+
const approxStartTimeNs = approximateClock().now();
|
|
49
|
+
const expired = !approximateClock().isBefore(header.expiresAtNanos, approxStartTimeNs);
|
|
50
|
+
|
|
51
|
+
let processed = false;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
this.handler.callbacks.onExecuting(this.size, header, approxStartTimeNs - header.createdAtNanos, 'nanoseconds');
|
|
55
|
+
|
|
56
|
+
if (expired) {
|
|
57
|
+
this.handler.callbacks.onExpired(this.size, header, approxStartTimeNs - header.createdAtNanos, 'nanoseconds');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const message = this.provideMessage();
|
|
61
|
+
if (message !== undefined) {
|
|
62
|
+
this.handler.consumer(message);
|
|
63
|
+
processed = true;
|
|
64
|
+
this.handler.callbacks.onProcessed(this.size, header);
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
if (processed) {
|
|
68
|
+
|
|
69
|
+
} else {
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
this.releaseResources();
|
|
73
|
+
|
|
74
|
+
this.handler.callbacks.onExecuted(this.size, header, approximateClock().now() - approxStartTimeNs, 'nanoseconds');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
abstract get size(): number;
|
|
79
|
+
|
|
80
|
+
abstract get header(): MessageHeader;
|
|
81
|
+
|
|
82
|
+
abstract provideMessage(): Message<unknown> | undefined;
|
|
83
|
+
|
|
84
|
+
releaseResources(): void {
|
|
85
|
+
// release resources if needed
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ProcessSmallMessage extends ProcessMessage {
|
|
92
|
+
private readonly message: Message<unknown>;
|
|
93
|
+
readonly size: number;
|
|
94
|
+
|
|
95
|
+
constructor(handler: InboundMessageHandler, message: Message<unknown>, size: number) {
|
|
96
|
+
super(handler);
|
|
97
|
+
this.message = message;
|
|
98
|
+
this.size = size;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get header(): MessageHeader {
|
|
102
|
+
return this.message.header;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
provideMessage(): Message<unknown> | undefined {
|
|
106
|
+
return this.message;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class InboundMessageHandler {
|
|
111
|
+
private readonly socket: Socket;
|
|
112
|
+
private readonly decoder: FrameDecoder;
|
|
113
|
+
private readonly self: Address;
|
|
114
|
+
private readonly peer: Address;
|
|
115
|
+
private readonly version: number;
|
|
116
|
+
|
|
117
|
+
readonly callbacks: InboundMessageCallbacks;
|
|
118
|
+
readonly consumer: (msg: Message<unknown>) => void;
|
|
119
|
+
|
|
120
|
+
constructor(decoder: FrameDecoder,
|
|
121
|
+
socket: Socket,
|
|
122
|
+
self: Address,
|
|
123
|
+
peer: Address,
|
|
124
|
+
version: number,
|
|
125
|
+
onClosed: (handler: InboundMessageHandler) => void,
|
|
126
|
+
callbacks: InboundMessageCallbacks,
|
|
127
|
+
consumer: (msg: Message<unknown>) => void) {
|
|
128
|
+
this.decoder = decoder;
|
|
129
|
+
this.socket = socket;
|
|
130
|
+
this.self = self;
|
|
131
|
+
this.peer = peer;
|
|
132
|
+
this.version = version;
|
|
133
|
+
|
|
134
|
+
this.callbacks = callbacks;
|
|
135
|
+
this.consumer = consumer;
|
|
136
|
+
|
|
137
|
+
this.decoder.activate((frame: Frame) => this.processFrame(frame));
|
|
138
|
+
|
|
139
|
+
socket.on('data', (data) => {
|
|
140
|
+
const buffer = adopt(data);
|
|
141
|
+
try {
|
|
142
|
+
this.decoder.receive(buffer);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
this.exceptionCaught(e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
socket.on('error', (error) => {
|
|
148
|
+
this.exceptionCaught(error);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected exceptionCaught(e: Error): void {
|
|
153
|
+
this.decoder.discard();
|
|
154
|
+
logger.error(`${this.id()} exception caught while processing inbound messages; terminating connection`, e);
|
|
155
|
+
this.socket.destroy();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private processFrame(frame: Frame): boolean {
|
|
159
|
+
if (frame.corrupted === true) {
|
|
160
|
+
this.processCorruptedFrame(frame);
|
|
161
|
+
return true;
|
|
162
|
+
} else {
|
|
163
|
+
return this.processValidFrame(frame);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private readonly received: { count: number, bytes: number } = {count: 0, bytes: 0};
|
|
168
|
+
|
|
169
|
+
private processOneContainedMessage(bytes: ShareableBytes): boolean {
|
|
170
|
+
const buffer = bytes.get();
|
|
171
|
+
|
|
172
|
+
const clock = approximateClock();
|
|
173
|
+
|
|
174
|
+
const nowNs = clock.now();
|
|
175
|
+
const header = serializer.extractHeader(buffer, this.peer, nowNs, this.version);
|
|
176
|
+
const timeElapsed = nowNs - header.createdAtNanos;
|
|
177
|
+
const size = serializer.inferMessageSize(buffer, buffer.position, buffer.limit, this.version);
|
|
178
|
+
|
|
179
|
+
if (!clock.isBefore(header.expiresAtNanos, nowNs)) {
|
|
180
|
+
this.callbacks.onHeaderArrived(size, header, timeElapsed, 'nanoseconds');
|
|
181
|
+
this.callbacks.onArrivedExpired(size, header, false, timeElapsed, 'nanoseconds');
|
|
182
|
+
this.received.count += 1;
|
|
183
|
+
this.received.bytes += size;
|
|
184
|
+
bytes.skipBytes(size);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.callbacks.onHeaderArrived(size, header, timeElapsed, 'nanoseconds');
|
|
189
|
+
this.callbacks.onArrived(size, header, timeElapsed, 'nanoseconds');
|
|
190
|
+
this.received.count += 1;
|
|
191
|
+
this.received.bytes += size;
|
|
192
|
+
|
|
193
|
+
this.processSmallMessage(bytes, size, header);
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private processSmallMessage(contents: ShareableBytes, size: number, header: MessageHeader): void {
|
|
199
|
+
const buffer = contents.get();
|
|
200
|
+
const begin = buffer.position;
|
|
201
|
+
const end = buffer.limit;
|
|
202
|
+
|
|
203
|
+
buffer.limit = begin + size;
|
|
204
|
+
|
|
205
|
+
let message: Message<unknown>;
|
|
206
|
+
const input = new ByteBufferInput(buffer, false);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const m = serializer.deserialize(input, header, this.version);
|
|
210
|
+
if (input.available() > 0) {
|
|
211
|
+
throw new Error(`not all bytes consumed: expected ${size} actual: at least ${size - input.available()}`);
|
|
212
|
+
}
|
|
213
|
+
message = m;
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
this.callbacks.onFailedDeserialize(size, header, e);
|
|
217
|
+
logger.error(`${this.id()} unexpected exception caught while deserializing the message`, e);
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
if (message === undefined) {
|
|
221
|
+
// this.releaseCapacity(size);
|
|
222
|
+
}
|
|
223
|
+
buffer.position = begin + size;
|
|
224
|
+
buffer.limit = end;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (message) {
|
|
228
|
+
this.dispatch(new ProcessSmallMessage(this, message, size));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private dispatch(task: ProcessMessage): void {
|
|
233
|
+
const header = task.header;
|
|
234
|
+
this.callbacks.onDispatched(task.size, header);
|
|
235
|
+
setImmediate(() => {
|
|
236
|
+
try {
|
|
237
|
+
task.run();
|
|
238
|
+
} catch (e) {
|
|
239
|
+
logger.error(`${this.id()} unexpected exception caught while processing the message`, e);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private id(): string {
|
|
245
|
+
return `${addressAsString(this.peer)} -> ${addressAsString(this.self)}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
private processFrameOfContainedMessage(bytes: ShareableBytes): boolean {
|
|
250
|
+
while (bytes.hasRemaining) {
|
|
251
|
+
if (!this.processOneContainedMessage(bytes)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private processValidFrame(frame: ValidFrame): boolean {
|
|
259
|
+
if (frame.selfContained) {
|
|
260
|
+
return this.processFrameOfContainedMessage(frame.contents);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private processCorruptedFrame(frame: CorruptedFrame): void {
|
|
265
|
+
if (!frame.recoverable) {
|
|
266
|
+
throw new Error(`invalid crc`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface MessageConsumer {
|
|
273
|
+
next(message: Message<unknown>): void
|
|
274
|
+
|
|
275
|
+
fail(header: MessageHeader, error: Error): void
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
type HandlerProvider = (decoder: FrameDecoder,
|
|
279
|
+
socket: Socket,
|
|
280
|
+
self: Address,
|
|
281
|
+
peer: Address,
|
|
282
|
+
version: number,
|
|
283
|
+
onClosed: (handler: InboundMessageHandler) => void,
|
|
284
|
+
callbacks: InboundMessageCallbacks,
|
|
285
|
+
consumer: MessageConsumer) => InboundMessageHandler;
|
|
286
|
+
|
|
287
|
+
class InboundMessageHandlers {
|
|
288
|
+
private readonly self: Address;
|
|
289
|
+
private readonly peer: Address;
|
|
290
|
+
private readonly callbacks: InboundMessageCallbacks;
|
|
291
|
+
private readonly consumer: MessageConsumer;
|
|
292
|
+
private readonly provider: HandlerProvider;
|
|
293
|
+
private handlers = new Array<InboundMessageHandler>();
|
|
294
|
+
|
|
295
|
+
constructor(self: Address,
|
|
296
|
+
peer: Address,
|
|
297
|
+
consumer: MessageConsumer,
|
|
298
|
+
provider?: (decoder: FrameDecoder,
|
|
299
|
+
socket: Socket,
|
|
300
|
+
self: Address,
|
|
301
|
+
peer: Address,
|
|
302
|
+
version: number,
|
|
303
|
+
onClosed: (handler: InboundMessageHandler) => void,
|
|
304
|
+
callbacks: InboundMessageCallbacks,
|
|
305
|
+
consumer: MessageConsumer) => InboundMessageHandler) {
|
|
306
|
+
this.self = self;
|
|
307
|
+
this.peer = peer;
|
|
308
|
+
this.consumer = consumer;
|
|
309
|
+
this.provider = provider ?? ((decoder: FrameDecoder, socket: Socket, self, peer, version: number, onClosed, callbacks, consumer) => new InboundMessageHandler(decoder, socket, self, peer, version, onClosed, callbacks, (msg) => consumer.next(msg)));
|
|
310
|
+
this.callbacks = InboundMessageHandlers.makeCallbacks(peer, consumer);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
handle(decoder: FrameDecoder, socket: Socket, version: number): InboundMessageHandler {
|
|
314
|
+
const handler = this.provider(decoder, socket, this.self, this.peer, version, (handler) => {
|
|
315
|
+
this.onHandleClosed(handler);
|
|
316
|
+
}, this.callbacks, this.consumer);
|
|
317
|
+
|
|
318
|
+
this.handlers.push(handler);
|
|
319
|
+
return handler;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private onHandleClosed(handler: InboundMessageHandler): void {
|
|
323
|
+
this.handlers = this.handlers.filter(h => h !== handler);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
static makeCallbacks(peer: Address, consumer: MessageConsumer): InboundMessageCallbacks {
|
|
327
|
+
return {
|
|
328
|
+
onHeaderArrived(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit) {
|
|
329
|
+
// no-op
|
|
330
|
+
},
|
|
331
|
+
onArrived(messageSize: number, header: MessageHeader, timeElapsed: bigint, timeUnit: Unit) {
|
|
332
|
+
// no-op
|
|
333
|
+
},
|
|
334
|
+
onArrivedExpired(messageSize: number, header: MessageHeader, wasCorrupt: boolean, timeElapsed: bigint, timeUnit: Unit) {
|
|
335
|
+
// no-op
|
|
336
|
+
},
|
|
337
|
+
onArrivedCorrupt: (messageSize, header, timeElapsed, timeUnit) => {
|
|
338
|
+
consumer.fail(header, new Error(`invalid crc`));
|
|
339
|
+
},
|
|
340
|
+
onClosedBeforeArrival: (messageSize, header, bytesReceived, wasCorrupt, wasExpired) => {
|
|
341
|
+
consumer.fail(header, new Error(`invalid crc`));
|
|
342
|
+
},
|
|
343
|
+
onExpired: (messageSize, header, timeElapsed, timeUnit) => {
|
|
344
|
+
// no-op
|
|
345
|
+
},
|
|
346
|
+
onFailedDeserialize: (messageSize, header, error: Error) => {
|
|
347
|
+
consumer.fail(header, error);
|
|
348
|
+
},
|
|
349
|
+
onDispatched: (messageSize, header) => {
|
|
350
|
+
// no-op
|
|
351
|
+
},
|
|
352
|
+
onExecuting: (messageSize, header, timeElapsed, timeUnit) => {
|
|
353
|
+
// no-op
|
|
354
|
+
},
|
|
355
|
+
onExecuted: (messageSize, header, timeElapsed, timeUnit) => {
|
|
356
|
+
// no-op
|
|
357
|
+
},
|
|
358
|
+
onProcessed: (messageSize, header) => {
|
|
359
|
+
// no-op
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function getInbound(from: Address, handlers: Map<Address, InboundMessageHandlers>, consumer: MessageConsumer, settings: {
|
|
366
|
+
bindAddress: Address
|
|
367
|
+
}) {
|
|
368
|
+
let handler = handlers.get(from);
|
|
369
|
+
if (handler) {
|
|
370
|
+
return handler;
|
|
371
|
+
}
|
|
372
|
+
handler = new InboundMessageHandlers(settings.bindAddress, from, consumer);
|
|
373
|
+
handlers.set(from, handler);
|
|
374
|
+
return handler;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export {InboundMessageHandlers};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {MessageConsumer} from './InboundMessageHandler.ts';
|
|
2
|
+
import {isFlagOn, type Message, type MessageHeader} from './Message.ts';
|
|
3
|
+
import {forError} from '../error/RequestFailure.ts';
|
|
4
|
+
import {fromId, type VerbHandlerContext} from './Action.ts';
|
|
5
|
+
|
|
6
|
+
export class InboundSink implements MessageConsumer {
|
|
7
|
+
private readonly context: Pick<VerbHandlerContext, 'messaging' | 'messageFactory'>;
|
|
8
|
+
private readonly sink: (message: Message<unknown>) => void;
|
|
9
|
+
|
|
10
|
+
constructor(context: Pick<VerbHandlerContext, 'messaging' | 'messageFactory'>) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.sink = (message: Message<unknown>) => {
|
|
13
|
+
const handler = message.header.verb().handler(this.context as VerbHandlerContext);
|
|
14
|
+
if (!handler) {
|
|
15
|
+
throw new Error(`No handler for verb ${fromId(message.header.verb().id)}`);
|
|
16
|
+
}
|
|
17
|
+
handler.doVerb(message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fail(header: MessageHeader, error: Error): void {
|
|
22
|
+
if (isFlagOn(header, 'CALL_BACK_ON_FAILURE')) {
|
|
23
|
+
const to = header.from;
|
|
24
|
+
const response = this.context.messageFactory().failureResponse(header.seq, header.expiresAtNanos, forError(error));
|
|
25
|
+
this.context.messaging().send(response, to);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
next(message: Message<unknown>) {
|
|
30
|
+
try {
|
|
31
|
+
this.sink(message);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
this.fail(message.header, e as Error);
|
|
34
|
+
// todo maybe just log warning for some errors
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|