@interopio/bridge 0.0.1-alpha → 0.0.4-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/bridge.js +1 -1
- package/changelog.md +24 -0
- package/dist/main.js +2201 -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,141 +0,0 @@
|
|
|
1
|
-
import {createSocket, type RemoteInfo, Socket} from 'node:dgram';
|
|
2
|
-
import {type DiscoveryNode, type DiscoveryStrategy} from '../index.ts';
|
|
3
|
-
import {SimpleDiscoveryNode} from '../SimpleDiscoveryNode.ts';
|
|
4
|
-
import {type Properties} from '../../config/Properties.ts';
|
|
5
|
-
import {GROUP, PORT} from './MulticastProperties.ts';
|
|
6
|
-
import {getOrDefault} from '../settings.ts';
|
|
7
|
-
import {fromHostname, toIpAddress} from '../../cluster/Address.ts';
|
|
8
|
-
import {isLoopback} from '../../utils/network.ts';
|
|
9
|
-
import {type Logger} from '../../logging.ts';
|
|
10
|
-
|
|
11
|
-
const DATA_OUTPUT_BUFFER_SIZE = 64 * 1024;
|
|
12
|
-
const DEFAULT_MULTICAST_PORT = 34567;
|
|
13
|
-
const DEFAULT_MULTICAST_GROUP = '239.1.2.3';
|
|
14
|
-
const SOCKET_TIME_TO_LIVE = 255; // unrestricted (other possible values would be 1 (local-network), 15 (site-local), 32(local-area), 64(regional), 128(global))
|
|
15
|
-
|
|
16
|
-
interface MulticastMemberInfo {
|
|
17
|
-
readonly host: string;
|
|
18
|
-
readonly port: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function multicastDiscoverySender(discoveryNode: DiscoveryNode | undefined, socket: Socket, group: string, port: number): () => void {
|
|
22
|
-
let multicastMemberInfo: MulticastMemberInfo;
|
|
23
|
-
if (discoveryNode) {
|
|
24
|
-
const address = discoveryNode.publicAddress;
|
|
25
|
-
multicastMemberInfo = {host: address.host, port: address.port};
|
|
26
|
-
}
|
|
27
|
-
const msg: Buffer = Buffer.from(JSON.stringify(multicastMemberInfo));
|
|
28
|
-
|
|
29
|
-
return () => {
|
|
30
|
-
socket.send(msg, port, group, (error) => {
|
|
31
|
-
console.error(error);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function multicastDiscoveryReceiver(socket: Socket): () => Promise<MulticastMemberInfo> {
|
|
37
|
-
return () => {
|
|
38
|
-
return new Promise<MulticastMemberInfo>((resolve, reject) => {
|
|
39
|
-
socket.on('message', (message: Buffer, remote: RemoteInfo) => {
|
|
40
|
-
resolve(JSON.parse(message as unknown as string));
|
|
41
|
-
});
|
|
42
|
-
socket.on('error', (err: Error) => {
|
|
43
|
-
reject(err);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class MulticastDiscoveryStrategy implements DiscoveryStrategy {
|
|
50
|
-
private readonly logger: Logger;
|
|
51
|
-
private readonly properties: Properties;
|
|
52
|
-
private readonly discoveryNode?: DiscoveryNode;
|
|
53
|
-
private isClient?: boolean;
|
|
54
|
-
private discoverySender?: () => void;
|
|
55
|
-
private discoveryReceiver?: () => Promise<MulticastMemberInfo>;
|
|
56
|
-
private sendIntervalId?: ReturnType<typeof setInterval>;
|
|
57
|
-
private socket?: Socket;
|
|
58
|
-
constructor(discoveryNode: DiscoveryNode | undefined,
|
|
59
|
-
logger: Logger,
|
|
60
|
-
properties: Properties) {
|
|
61
|
-
this.logger = logger;
|
|
62
|
-
this.properties = properties;
|
|
63
|
-
this.discoveryNode = discoveryNode;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private async initializeMulticastSocket() {
|
|
67
|
-
const port = getOrDefault(this.properties, '', PORT, DEFAULT_MULTICAST_PORT);
|
|
68
|
-
if (port < 0 || port > 65535) {
|
|
69
|
-
throw new Error('port number must be between 0 and 65535');
|
|
70
|
-
}
|
|
71
|
-
const group = getOrDefault(this.properties, '', GROUP, DEFAULT_MULTICAST_GROUP);
|
|
72
|
-
this.socket = createSocket({
|
|
73
|
-
type: 'udp4',
|
|
74
|
-
reuseAddr: true,
|
|
75
|
-
recvBufferSize: DATA_OUTPUT_BUFFER_SIZE,
|
|
76
|
-
sendBufferSize: DATA_OUTPUT_BUFFER_SIZE});
|
|
77
|
-
|
|
78
|
-
if (this.discoveryNode) {
|
|
79
|
-
const address = await toIpAddress(this.discoveryNode.privateAddress);
|
|
80
|
-
if (!isLoopback(address)) {
|
|
81
|
-
this.socket.setMulticastInterface(address.address);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.socket.bind(port, () => {
|
|
86
|
-
this.socket.setMulticastTTL(SOCKET_TIME_TO_LIVE);
|
|
87
|
-
this.socket.addMembership(group);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
this.discoverySender = multicastDiscoverySender(this.discoveryNode, this.socket, group, port);
|
|
91
|
-
this.discoveryReceiver = multicastDiscoveryReceiver(this.socket);
|
|
92
|
-
if (this.discoveryNode == null) {
|
|
93
|
-
this.isClient = true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async discover(): Promise<Iterable<DiscoveryNode> | undefined> {
|
|
98
|
-
const multicastMemberInfo = await this.discoveryReceiver();
|
|
99
|
-
if (multicastMemberInfo) {
|
|
100
|
-
const list = [];
|
|
101
|
-
try {
|
|
102
|
-
const discoveryNode = new SimpleDiscoveryNode(await fromHostname(multicastMemberInfo.host, multicastMemberInfo.port));
|
|
103
|
-
list.push(discoveryNode);
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
this.logger.trace(e.message);
|
|
107
|
-
}
|
|
108
|
-
return list;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async start() {
|
|
113
|
-
await this.initializeMulticastSocket();
|
|
114
|
-
if (!this.isClient) {
|
|
115
|
-
this.sendIntervalId = setInterval(() => {
|
|
116
|
-
this.discoverySender();
|
|
117
|
-
}, 2000);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async close() {
|
|
122
|
-
if (this.sendIntervalId) {
|
|
123
|
-
clearInterval(this.sendIntervalId);
|
|
124
|
-
delete this.sendIntervalId;
|
|
125
|
-
}
|
|
126
|
-
return new Promise<void>((resolve, reject) => {
|
|
127
|
-
if (this.socket)
|
|
128
|
-
{
|
|
129
|
-
this.socket.close(() => {
|
|
130
|
-
resolve()
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
else
|
|
134
|
-
{
|
|
135
|
-
resolve();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type DiscoveryNode,
|
|
3
|
-
type DiscoveryStrategy,
|
|
4
|
-
type DiscoveryStrategyFactory,
|
|
5
|
-
DiscoveryStrategyPriorities
|
|
6
|
-
} from '../index.ts';
|
|
7
|
-
import {MulticastDiscoveryStrategy} from './MulticastDiscoveryStrategy.ts';
|
|
8
|
-
import {GROUP, PORT} from './MulticastProperties.ts';
|
|
9
|
-
import {type Properties, type PropertyDefinition} from '../../config/Properties.ts';
|
|
10
|
-
import {type Logger} from '../../logging.ts';
|
|
11
|
-
|
|
12
|
-
const PROPERTY_DEFINITIONS: ReadonlyArray<PropertyDefinition> = [
|
|
13
|
-
GROUP,
|
|
14
|
-
PORT
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export class MulticastDiscoveryStrategyFactory implements DiscoveryStrategyFactory {
|
|
18
|
-
readonly priority = DiscoveryStrategyPriorities.UNKNOWN;
|
|
19
|
-
newDiscoveryStrategy(discoveryNode: DiscoveryNode, logger: Logger, prop: Properties): DiscoveryStrategy {
|
|
20
|
-
return new MulticastDiscoveryStrategy(discoveryNode, logger, prop);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async isAutoDetectionApplicable(): Promise<boolean> {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getConfigurationProperties(): ReadonlyArray<PropertyDefinition> {
|
|
28
|
-
return PROPERTY_DEFINITIONS;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {type Properties, type PropertyDefinition, type PropertyType} from '../config/Properties.ts';
|
|
2
|
-
|
|
3
|
-
function getProperty(prefix: string, property: PropertyDefinition) {
|
|
4
|
-
let s = prefix;
|
|
5
|
-
if (!s.endsWith('.')) {
|
|
6
|
-
s += '.';
|
|
7
|
-
}
|
|
8
|
-
return s + property.key;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function readProperty(prefix: string | undefined, property: PropertyDefinition): PropertyType | undefined {
|
|
12
|
-
if (prefix) {
|
|
13
|
-
const p = getProperty(prefix, property);
|
|
14
|
-
const v = process.env[p];
|
|
15
|
-
// todo convert
|
|
16
|
-
return v;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function getOrUndefined<T extends PropertyType>(properties: Properties, prefix: string, property?: PropertyDefinition): T | undefined {
|
|
21
|
-
return getOrDefault(properties, prefix, property);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function getOrDefault<T extends PropertyType>(properties: Properties, prefix: string, property?: PropertyDefinition, defaultValue?: T) {
|
|
25
|
-
if (property === undefined) {
|
|
26
|
-
return defaultValue;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let value = readProperty(prefix, property);
|
|
30
|
-
if (value === undefined) {
|
|
31
|
-
value = properties[property.key];
|
|
32
|
-
}
|
|
33
|
-
if (value === undefined) {
|
|
34
|
-
return defaultValue;
|
|
35
|
-
}
|
|
36
|
-
return value as T;
|
|
37
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type {DataInput, DataOutput, VersionedSerializer} from '../io/VersionedSerializer.ts';
|
|
2
|
-
|
|
3
|
-
export const RequestFailure = {
|
|
4
|
-
UNKNOWN: 0,
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type RequestFailureReason = keyof typeof RequestFailure
|
|
8
|
-
|
|
9
|
-
const codeToReason: RequestFailureReason[] = function initCodeToReason() {
|
|
10
|
-
const result: RequestFailureReason[] = [];
|
|
11
|
-
for (const [reason, code] of Object.entries(RequestFailure)) {
|
|
12
|
-
if (result[code] !== undefined) {
|
|
13
|
-
throw new Error(`Duplicate RequestFailure code: ${code}`);
|
|
14
|
-
}
|
|
15
|
-
result[code] = reason as RequestFailureReason;
|
|
16
|
-
}
|
|
17
|
-
if (result.length >= 1<<8) {
|
|
18
|
-
throw new Error('RequestFailure codes exceed 8 bits');
|
|
19
|
-
}
|
|
20
|
-
return result;
|
|
21
|
-
} ();
|
|
22
|
-
|
|
23
|
-
export function fromCode(code: number): RequestFailureReason {
|
|
24
|
-
if (code < 0) {
|
|
25
|
-
throw new Error(`Invalid RequestFailure code: ${code}`);
|
|
26
|
-
}
|
|
27
|
-
return codeToReason[code] ?? 'UNKNOWN';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function forError(error: Error): RequestFailureReason {
|
|
31
|
-
return 'UNKNOWN';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const RequestFailureReasonSerializer: VersionedSerializer<RequestFailureReason> = new (class implements VersionedSerializer<RequestFailureReason> {
|
|
35
|
-
serializedSize(value: RequestFailureReason, version?: number): number {
|
|
36
|
-
return 1;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
serialize(value: RequestFailureReason, output: DataOutput, version?: number) {
|
|
40
|
-
const code = RequestFailure[value];
|
|
41
|
-
output.writeUint8(code);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
deserialize(input: DataInput, version?: number): RequestFailureReason {
|
|
45
|
-
const code = input.readUint8();
|
|
46
|
-
return fromCode(code);
|
|
47
|
-
}
|
|
48
|
-
})
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const ApplicationStates = {
|
|
2
|
-
_STATUS: 0,
|
|
3
|
-
LOAD: 1,
|
|
4
|
-
_SCHEMA: 2,
|
|
5
|
-
DC: 3,
|
|
6
|
-
RACK: 4,
|
|
7
|
-
RELEASE_VERSION: 5,
|
|
8
|
-
_INTERNAL_IP: 7,
|
|
9
|
-
_RPC_ADDRESS: 8,
|
|
10
|
-
NET_VERSION: 11,
|
|
11
|
-
HOST_ID: 12,
|
|
12
|
-
_TOKENS: 13,
|
|
13
|
-
_RPC_READY: 14,
|
|
14
|
-
INTERNAL_ADDRESS: 15,
|
|
15
|
-
NATIVE_ADDRESS: 16,
|
|
16
|
-
STATUS: 17,
|
|
17
|
-
_SSTABLE_VERSIONS: 18,
|
|
18
|
-
_DISK_USAGE: 19,
|
|
19
|
-
_INDEX_STATUS: 20,
|
|
20
|
-
_X1: 21,
|
|
21
|
-
_X2: 22,
|
|
22
|
-
_X3: 23,
|
|
23
|
-
_X4: 24,
|
|
24
|
-
_X5: 25,
|
|
25
|
-
_X6: 26,
|
|
26
|
-
_X7: 27,
|
|
27
|
-
_X8: 28,
|
|
28
|
-
_X9: 29
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function forId(id: number): ApplicationState {
|
|
32
|
-
const entry = Object.entries(ApplicationStates)
|
|
33
|
-
.find(([, code]) => id === code);
|
|
34
|
-
if (!entry) {
|
|
35
|
-
throw new Error(`No ApplicationState found for id ${id}`);
|
|
36
|
-
}
|
|
37
|
-
return entry[0] as ApplicationState;
|
|
38
|
-
}
|
|
39
|
-
export function toId(name: ApplicationState): number {
|
|
40
|
-
const entry = Object.entries(ApplicationStates)
|
|
41
|
-
.find(([state]) => name === state);
|
|
42
|
-
if (!entry) {
|
|
43
|
-
throw new Error(`No ApplicationState found for name ${name}`);
|
|
44
|
-
}
|
|
45
|
-
return entry[1] as number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type ApplicationState = keyof typeof ApplicationStates;
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import type {DataInput, DataOutput, VersionedSerializer} from '../io/VersionedSerializer.ts';
|
|
2
|
-
import {HeartbeatState, HeartbeatStateSerializer} from './HeartbeatState.ts';
|
|
3
|
-
import {type ApplicationState, forId, toId} from './ApplicationState.ts';
|
|
4
|
-
import {type VersionedValue, VersionedValueSerializer} from './VersionedValue.ts';
|
|
5
|
-
import {globalClock} from '../utils/clock.ts';
|
|
6
|
-
|
|
7
|
-
export class EndpointState {
|
|
8
|
-
private _heartbeat: HeartbeatState
|
|
9
|
-
private _application: Map<ApplicationState, VersionedValue>
|
|
10
|
-
private _timestamp: bigint
|
|
11
|
-
private _alive: boolean
|
|
12
|
-
|
|
13
|
-
constructor(heartbeat: HeartbeatState, states?: Map<ApplicationState, VersionedValue>) {
|
|
14
|
-
this._heartbeat = heartbeat;
|
|
15
|
-
this._application = states ?? new Map<ApplicationState, VersionedValue>();
|
|
16
|
-
this._timestamp = globalClock.nanoTime();
|
|
17
|
-
this._alive = true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
copy(): EndpointState {
|
|
21
|
-
return new EndpointState(this.heartbeat.copy(), new Map(this._application));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
updateTimestamp(): void {
|
|
25
|
-
this._timestamp = globalClock.nanoTime();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get timestamp(): bigint {
|
|
29
|
-
return this._timestamp;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/*testing*/ set timestamp(timestamp: bigint) {
|
|
33
|
-
this._timestamp = timestamp
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public get heartbeat(): HeartbeatState {
|
|
37
|
-
return this._heartbeat;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
set heartbeat(heartbeat: HeartbeatState) {
|
|
41
|
-
this.updateTimestamp();
|
|
42
|
-
this._heartbeat = heartbeat
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getApplicationState(key: ApplicationState): VersionedValue {
|
|
46
|
-
return this._application.get(key);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
hasApplicationState(key: ApplicationState): boolean {
|
|
50
|
-
return this._application.has(key);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public states() {
|
|
54
|
-
return this._application.entries();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
addApplicationState(key: ApplicationState, value: VersionedValue): void {
|
|
58
|
-
this.addApplicationStates([[key, value]]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
addApplicationStates(values: [ApplicationState, VersionedValue][]): void {
|
|
62
|
-
while (true) {
|
|
63
|
-
const orig = this._application;
|
|
64
|
-
const copy = new Map(orig);
|
|
65
|
-
|
|
66
|
-
for (const [key, value] of values) {
|
|
67
|
-
copy.set(key, value);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (this._application === orig) {
|
|
71
|
-
this._application = copy;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get isAlive(): boolean {
|
|
78
|
-
return this._alive;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
markAsDead(): void {
|
|
82
|
-
this._alive = false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
markAsAlive(): void {
|
|
86
|
-
this._alive = true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
get isStateEmpty(): boolean {
|
|
90
|
-
return this._application.size === 0;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
get isEmptyWithoutStatus(): boolean {
|
|
94
|
-
const state = this._application;
|
|
95
|
-
const hasStatus = state.has('STATUS') || state.has('_STATUS');
|
|
96
|
-
return this._heartbeat.isEmpty() && !hasStatus;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
toString() {
|
|
100
|
-
return `EndpointState{heartbeat=${this._heartbeat}, ApplicationState=${this._application}`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export const EndpointStateSerializer = new (class implements VersionedSerializer<EndpointState> {
|
|
105
|
-
serialize(value: EndpointState, output: DataOutput, version: number) {
|
|
106
|
-
const heartbeat = value.heartbeat;
|
|
107
|
-
HeartbeatStateSerializer.serialize(heartbeat, output, version);
|
|
108
|
-
|
|
109
|
-
const states = Array.from(value.states());
|
|
110
|
-
output.writeInt32(states.length);
|
|
111
|
-
for (const [key, state] of states) {
|
|
112
|
-
output.writeInt32(toId(key));
|
|
113
|
-
VersionedValueSerializer.serialize(state, output, version);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
deserialize(input: DataInput, version: number): EndpointState {
|
|
118
|
-
const heartbeat = HeartbeatStateSerializer.deserialize(input, version);
|
|
119
|
-
const size = input.readInt32();
|
|
120
|
-
|
|
121
|
-
const state = new EndpointState(heartbeat);
|
|
122
|
-
for (let i = 0; i < size; i++) {
|
|
123
|
-
const key = input.readInt32();
|
|
124
|
-
const value = VersionedValueSerializer.deserialize(input, version);
|
|
125
|
-
state.addApplicationState(forId(key), value);
|
|
126
|
-
}
|
|
127
|
-
return state;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
serializedSize(value: EndpointState, version: number): number {
|
|
131
|
-
let size = HeartbeatStateSerializer.serializedSize(value.heartbeat, version);
|
|
132
|
-
const states = value.states();
|
|
133
|
-
size += 4; // size of states
|
|
134
|
-
for (const [key, state] of states) {
|
|
135
|
-
size += 4; // key
|
|
136
|
-
size += VersionedValueSerializer.serializedSize(state, version);
|
|
137
|
-
}
|
|
138
|
-
return size;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import {type Address, addressAsString} from '../cluster/Address.ts';
|
|
2
|
-
import {ObjectMap} from '../utils/collections.ts';
|
|
3
|
-
import {preciseClock} from '../utils/clock.ts';
|
|
4
|
-
import {convert} from '../config/Duration.ts';
|
|
5
|
-
import getLogger from '../logging.ts';
|
|
6
|
-
|
|
7
|
-
export interface FailureDetector {
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* invoked by any entity (gossiper) wanting to interrogate the status of the endpoint.
|
|
11
|
-
*
|
|
12
|
-
* @param endpoint
|
|
13
|
-
*/
|
|
14
|
-
interpret(endpoint: Address): void;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* invoked by the receiver of the heartbeat (gossiper).
|
|
18
|
-
* @param endpoint endpoint being reported
|
|
19
|
-
*/
|
|
20
|
-
report(endpoint: Address): void
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* remove an endpoint
|
|
24
|
-
*/
|
|
25
|
-
remove(endpoint: Address): void
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* force conviction of the specified endpoint in the failure detector.
|
|
29
|
-
* @param endpoint endpoint to be convicted
|
|
30
|
-
*/
|
|
31
|
-
forceConviction(endpoint: Address): void;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export interface IFailureDetectionEventListener {
|
|
38
|
-
/**
|
|
39
|
-
* Convict the specified endpoint
|
|
40
|
-
* @param endpoint endpoint to be convicted
|
|
41
|
-
* @param phi the value of phi with which the endpoint was convicted
|
|
42
|
-
*/
|
|
43
|
-
convict(endpoint: Address, phi: number): void;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
class ArrayBackedBoundedStats {
|
|
47
|
-
private readonly _arrivalIntervals: Array<bigint>;
|
|
48
|
-
private _sum: bigint = 0n;
|
|
49
|
-
private _index: number = 0;
|
|
50
|
-
private _full = false;
|
|
51
|
-
private _mean: number = 0.0;
|
|
52
|
-
|
|
53
|
-
constructor(size: number) {
|
|
54
|
-
this._arrivalIntervals = new Array<bigint>(size);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
add(interval: bigint): void {
|
|
58
|
-
if (this._index === this._arrivalIntervals.length) {
|
|
59
|
-
this._full = true;
|
|
60
|
-
this._index = 0;
|
|
61
|
-
}
|
|
62
|
-
if (this._full) {
|
|
63
|
-
this._sum = this._sum - this._arrivalIntervals[this._index];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this._arrivalIntervals[this._index++] = interval;
|
|
67
|
-
this._sum += interval;
|
|
68
|
-
|
|
69
|
-
this._mean = Number(this._sum) / this.size();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private size(): number {
|
|
73
|
-
return this._full ? this._arrivalIntervals.length : this._index;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public get mean(): number {
|
|
77
|
-
return this._mean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
toString(): string {
|
|
81
|
-
return `${this._arrivalIntervals}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const logger = getLogger('gossip.fd');
|
|
87
|
-
|
|
88
|
-
const INITIAL_VALUE_NS = convert('nanoseconds', 2 * 1000/*gossiper interval*/, 'milliseconds');
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class ArrivalWindow {
|
|
93
|
-
private static readonly MAX_INTERVAL_NS = ArrivalWindow.getMaxInterval();
|
|
94
|
-
private readonly _arrivalIntervals: ArrayBackedBoundedStats;
|
|
95
|
-
private _lastReportedPhi: number = Number.MIN_VALUE;
|
|
96
|
-
private _last: bigint = 0n;
|
|
97
|
-
|
|
98
|
-
constructor(size: number) {
|
|
99
|
-
this._arrivalIntervals = new ArrayBackedBoundedStats(size);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static getMaxInterval(): bigint {
|
|
103
|
-
return INITIAL_VALUE_NS;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
add(now: bigint, endpoint: Address) {
|
|
107
|
-
if (this._last < 0n) {
|
|
108
|
-
throw new Error();
|
|
109
|
-
}
|
|
110
|
-
if (this._last > 0n) {
|
|
111
|
-
const interval = now - this._last;
|
|
112
|
-
if (interval <= ArrivalWindow.MAX_INTERVAL_NS) {
|
|
113
|
-
this._arrivalIntervals.add(interval);
|
|
114
|
-
logger.enabledFor(`trace`) && logger.debug(`Reporting Interval time for ${addressAsString(endpoint)}: ${interval}ns`);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
logger.enabledFor(`trace`) && logger.debug(`Ignoring Interval time for ${addressAsString(endpoint)}: ${interval}ns`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
this._arrivalIntervals.add(INITIAL_VALUE_NS);
|
|
122
|
-
}
|
|
123
|
-
this._last = now;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
get mean(): number {
|
|
127
|
-
return this._arrivalIntervals.mean;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
get lastReportedPhi(): number {
|
|
131
|
-
return this._lastReportedPhi;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
phi(now: bigint): number {
|
|
135
|
-
if (!(this._arrivalIntervals.mean > 0 && this._last > 0n)) {
|
|
136
|
-
throw new Error('mean and last should be > 0 (mean: ' + this._arrivalIntervals.mean + ', last: ' + this._last + ')');
|
|
137
|
-
}
|
|
138
|
-
const t = Number(now - this._last);
|
|
139
|
-
this._lastReportedPhi = t / this.mean;
|
|
140
|
-
return this._lastReportedPhi;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// "The Phi Accrual Failure Detector"
|
|
145
|
-
export const failureDetector: FailureDetector = new (class implements FailureDetector {
|
|
146
|
-
private readonly SAMPLE_SIZE = 1000;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
private readonly PHI_FACTOR = 1.0 / Math.log(10.0);
|
|
150
|
-
private readonly arrivalSamples: Map<Address, ArrivalWindow> = new ObjectMap<Address, ArrivalWindow>();
|
|
151
|
-
private readonly listeners = new Array<IFailureDetectionEventListener>()
|
|
152
|
-
|
|
153
|
-
private lastPause: bigint = 0n;
|
|
154
|
-
private lastInterpret = preciseClock.now();
|
|
155
|
-
|
|
156
|
-
report(endpoint: Address) {
|
|
157
|
-
|
|
158
|
-
const now = preciseClock.now();
|
|
159
|
-
let arrivalWindow = this.arrivalSamples.get(endpoint);
|
|
160
|
-
if (arrivalWindow === undefined) {
|
|
161
|
-
arrivalWindow = new ArrivalWindow(this.SAMPLE_SIZE);
|
|
162
|
-
arrivalWindow.add(now, endpoint);
|
|
163
|
-
|
|
164
|
-
this.arrivalSamples.set(endpoint, arrivalWindow);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
arrivalWindow.add(now, endpoint);
|
|
168
|
-
}
|
|
169
|
-
if (logger.enabledFor('trace')) {
|
|
170
|
-
logger.debug(`Average for ${addressAsString(endpoint)}: ${arrivalWindow.mean}ns`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
interpret(endpoint: Address) {
|
|
175
|
-
const arrivalWindow = this.arrivalSamples.get(endpoint);
|
|
176
|
-
if (arrivalWindow === undefined) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const now = preciseClock.now();
|
|
180
|
-
const diff = now - this.lastInterpret;
|
|
181
|
-
this.lastInterpret = now;
|
|
182
|
-
|
|
183
|
-
if (diff > MAX_LOCAL_PAUSE_NS) {
|
|
184
|
-
logger.warn(`Not marking nodes down due to local pause of ${diff}ns > ${MAX_LOCAL_PAUSE_NS}ns`);
|
|
185
|
-
this.lastPause = now;
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if ((preciseClock.now() - this.lastPause) < MAX_LOCAL_PAUSE_NS) {
|
|
189
|
-
if (logger.enabledFor('debug')) {
|
|
190
|
-
logger.debug(`Still not marking nodes down due to local pause`);
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const phi = arrivalWindow.phi(now);
|
|
196
|
-
if (logger.enabledFor('trace')) {
|
|
197
|
-
logger.debug(`PHI for ${addressAsString(endpoint)}: ${phi}`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (this.PHI_FACTOR * phi > this.getPhiConvictThreshold()) {
|
|
201
|
-
if (logger.enabledFor('trace')) {
|
|
202
|
-
logger.debug(`Node ${addressAsString(endpoint)} phi ${this.PHI_FACTOR * phi} > ${this.getPhiConvictThreshold()}; intervals ${arrivalWindow}, mean: ${arrivalWindow.mean}ns`);
|
|
203
|
-
}
|
|
204
|
-
for (const listener of this.listeners) {
|
|
205
|
-
listener.convict(endpoint, phi);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
else if (logger.enabledFor('debug') && (this.PHI_FACTOR * phi * DEBUG_PERCENTAGE /100.0 > this.getPhiConvictThreshold())) {
|
|
209
|
-
logger.debug(`PHI for ${addressAsString(endpoint)}: ${phi}`);
|
|
210
|
-
} else if (logger.enabledFor('trace')) {
|
|
211
|
-
logger.debug(`PHI for ${addressAsString(endpoint)}: ${phi}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
forceConviction(endpoint: Address) {
|
|
216
|
-
if (logger.enabledFor('debug')) {
|
|
217
|
-
logger.debug(`Forcing conviction of ${addressAsString(endpoint)}`);
|
|
218
|
-
}
|
|
219
|
-
for (const listener of this.listeners) {
|
|
220
|
-
listener.convict(endpoint, this.getPhiConvictThreshold());
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
remove(endpoint: Address) {
|
|
225
|
-
this.arrivalSamples.delete(endpoint);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private getPhiConvictThreshold(): number {
|
|
229
|
-
return 8.0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
});
|
|
234
|
-
const MAX_LOCAL_PAUSE_NS = convert('nanoseconds', 5000, 'milliseconds');
|
|
235
|
-
const DEBUG_PERCENTAGE = 80;
|