@celerispay/hazelcast-client 3.12.5 → 3.12.7
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/CHANGELOG.md +111 -87
- package/CHANGES_UNCOMMITTED.md +52 -0
- package/FAILOVER_FIXES.md +148 -230
- package/FAULT_TOLERANCE_IMPROVEMENTS.md +208 -0
- package/HAZELCAST_CLIENT_EVOLUTION.md +402 -0
- package/QUICK_START.md +184 -95
- package/RELEASE_SUMMARY.md +227 -147
- package/lib/HeartbeatService.js +11 -2
- package/lib/PartitionService.d.ts +14 -0
- package/lib/PartitionService.js +32 -9
- package/lib/invocation/ClientConnection.d.ts +14 -0
- package/lib/invocation/ClientConnection.js +95 -1
- package/lib/invocation/ClientConnectionManager.d.ts +95 -0
- package/lib/invocation/ClientConnectionManager.js +369 -7
- package/lib/invocation/ClusterService.d.ts +75 -5
- package/lib/invocation/ClusterService.js +430 -15
- package/lib/invocation/ConnectionAuthenticator.d.ts +11 -0
- package/lib/invocation/ConnectionAuthenticator.js +85 -12
- package/lib/invocation/CredentialPreservationService.d.ts +137 -0
- package/lib/invocation/CredentialPreservationService.js +369 -0
- package/lib/invocation/HazelcastFailoverManager.d.ts +102 -0
- package/lib/invocation/HazelcastFailoverManager.js +285 -0
- package/lib/invocation/InvocationService.js +8 -0
- package/lib/nearcache/StaleReadDetectorImpl.js +31 -4
- package/lib/proxy/ProxyManager.js +25 -4
- package/package.json +20 -28
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ILogger } from '../logging/ILogger';
|
|
2
|
+
import { ClientConnection } from './ClientConnection';
|
|
3
|
+
import Address = require('../Address');
|
|
4
|
+
import HazelcastClient from '../HazelcastClient';
|
|
5
|
+
/**
|
|
6
|
+
* Node state in the cluster
|
|
7
|
+
*/
|
|
8
|
+
export declare enum NodeState {
|
|
9
|
+
UNKNOWN = "unknown",
|
|
10
|
+
CONNECTING = "connecting",
|
|
11
|
+
CONNECTED = "connected",
|
|
12
|
+
OWNER = "owner",
|
|
13
|
+
CHILD = "child",
|
|
14
|
+
DISCONNECTED = "disconnected",
|
|
15
|
+
FAILED = "failed",
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Node information
|
|
19
|
+
*/
|
|
20
|
+
export interface NodeInfo {
|
|
21
|
+
address: Address;
|
|
22
|
+
state: NodeState;
|
|
23
|
+
connection: ClientConnection | null;
|
|
24
|
+
lastSeen: number;
|
|
25
|
+
failureCount: number;
|
|
26
|
+
isOwner: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Implements the Java client's failover behavior
|
|
30
|
+
* This ensures seamless node transitions and proper ownership handling
|
|
31
|
+
*/
|
|
32
|
+
export declare class HazelcastFailoverManager {
|
|
33
|
+
private readonly logger;
|
|
34
|
+
private readonly client;
|
|
35
|
+
private readonly nodes;
|
|
36
|
+
private readonly failoverInProgress;
|
|
37
|
+
private readonly failoverTimeout;
|
|
38
|
+
private currentOwner;
|
|
39
|
+
constructor(client: HazelcastClient, logger: ILogger);
|
|
40
|
+
/**
|
|
41
|
+
* Registers a node in the cluster
|
|
42
|
+
* @param address The node address
|
|
43
|
+
* @param connection The connection to the node
|
|
44
|
+
* @param isOwner Whether this node is the owner
|
|
45
|
+
*/
|
|
46
|
+
registerNode(address: Address, connection: ClientConnection, isOwner?: boolean): void;
|
|
47
|
+
/**
|
|
48
|
+
* Handles node disconnection gracefully
|
|
49
|
+
* @param address The disconnected node address
|
|
50
|
+
*/
|
|
51
|
+
handleNodeDisconnection(address: Address): void;
|
|
52
|
+
/**
|
|
53
|
+
* Handles owner node disconnection
|
|
54
|
+
* @param address The disconnected owner node address
|
|
55
|
+
*/
|
|
56
|
+
private handleOwnerDisconnection(address);
|
|
57
|
+
/**
|
|
58
|
+
* Handles child node disconnection
|
|
59
|
+
* @param address The disconnected child node address
|
|
60
|
+
*/
|
|
61
|
+
private handleChildDisconnection(address);
|
|
62
|
+
/**
|
|
63
|
+
* Selects the best candidate for new owner
|
|
64
|
+
* @returns The address of the best candidate, or null if none available
|
|
65
|
+
*/
|
|
66
|
+
private selectNewOwner();
|
|
67
|
+
/**
|
|
68
|
+
* Promotes a node to owner
|
|
69
|
+
* @param address The address of the node to promote
|
|
70
|
+
*/
|
|
71
|
+
private promoteToOwner(address);
|
|
72
|
+
/**
|
|
73
|
+
* Handles node reconnection
|
|
74
|
+
* @param address The reconnected node address
|
|
75
|
+
* @param connection The new connection
|
|
76
|
+
*/
|
|
77
|
+
handleNodeReconnection(address: Address, connection: ClientConnection): void;
|
|
78
|
+
/**
|
|
79
|
+
* Stops all failover and reconnection logic
|
|
80
|
+
*/
|
|
81
|
+
private stopFailoverLogic();
|
|
82
|
+
/**
|
|
83
|
+
* Gets the current owner address
|
|
84
|
+
* @returns The current owner address or null if no owner
|
|
85
|
+
*/
|
|
86
|
+
getCurrentOwner(): Address | null;
|
|
87
|
+
/**
|
|
88
|
+
* Gets all connected nodes
|
|
89
|
+
* @returns Array of connected node addresses
|
|
90
|
+
*/
|
|
91
|
+
getConnectedNodes(): Address[];
|
|
92
|
+
/**
|
|
93
|
+
* Gets the cluster state summary
|
|
94
|
+
* @returns Summary of cluster state
|
|
95
|
+
*/
|
|
96
|
+
getClusterStateSummary(): any;
|
|
97
|
+
/**
|
|
98
|
+
* Cleans up failed nodes that haven't recovered
|
|
99
|
+
* @param maxFailureAge Maximum age for failed nodes (default: 5 minutes)
|
|
100
|
+
*/
|
|
101
|
+
cleanupFailedNodes(maxFailureAge?: number): void;
|
|
102
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2008-2021, Hazelcast, Inc. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
/**
|
|
19
|
+
* Node state in the cluster
|
|
20
|
+
*/
|
|
21
|
+
var NodeState;
|
|
22
|
+
(function (NodeState) {
|
|
23
|
+
NodeState["UNKNOWN"] = "unknown";
|
|
24
|
+
NodeState["CONNECTING"] = "connecting";
|
|
25
|
+
NodeState["CONNECTED"] = "connected";
|
|
26
|
+
NodeState["OWNER"] = "owner";
|
|
27
|
+
NodeState["CHILD"] = "child";
|
|
28
|
+
NodeState["DISCONNECTED"] = "disconnected";
|
|
29
|
+
NodeState["FAILED"] = "failed";
|
|
30
|
+
})(NodeState = exports.NodeState || (exports.NodeState = {}));
|
|
31
|
+
/**
|
|
32
|
+
* Implements the Java client's failover behavior
|
|
33
|
+
* This ensures seamless node transitions and proper ownership handling
|
|
34
|
+
*/
|
|
35
|
+
var HazelcastFailoverManager = /** @class */ (function () {
|
|
36
|
+
function HazelcastFailoverManager(client, logger) {
|
|
37
|
+
this.nodes = new Map();
|
|
38
|
+
this.failoverInProgress = false;
|
|
39
|
+
this.failoverTimeout = 30000; // 30 seconds
|
|
40
|
+
this.currentOwner = null;
|
|
41
|
+
this.client = client;
|
|
42
|
+
this.logger = logger;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Registers a node in the cluster
|
|
46
|
+
* @param address The node address
|
|
47
|
+
* @param connection The connection to the node
|
|
48
|
+
* @param isOwner Whether this node is the owner
|
|
49
|
+
*/
|
|
50
|
+
HazelcastFailoverManager.prototype.registerNode = function (address, connection, isOwner) {
|
|
51
|
+
if (isOwner === void 0) { isOwner = false; }
|
|
52
|
+
var addressStr = address.toString();
|
|
53
|
+
var nodeInfo = {
|
|
54
|
+
address: address,
|
|
55
|
+
state: isOwner ? NodeState.OWNER : NodeState.CHILD,
|
|
56
|
+
connection: connection,
|
|
57
|
+
lastSeen: Date.now(),
|
|
58
|
+
failureCount: 0,
|
|
59
|
+
isOwner: isOwner
|
|
60
|
+
};
|
|
61
|
+
this.nodes.set(addressStr, nodeInfo);
|
|
62
|
+
if (isOwner) {
|
|
63
|
+
this.currentOwner = address;
|
|
64
|
+
this.logger.info('HazelcastFailoverManager', "Registered owner node: " + addressStr);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.logger.info('HazelcastFailoverManager', "Registered child node: " + addressStr);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Handles node disconnection gracefully
|
|
72
|
+
* @param address The disconnected node address
|
|
73
|
+
*/
|
|
74
|
+
HazelcastFailoverManager.prototype.handleNodeDisconnection = function (address) {
|
|
75
|
+
var addressStr = address.toString();
|
|
76
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
77
|
+
if (!nodeInfo) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.logger.warn('HazelcastFailoverManager', "Node " + addressStr + " disconnected, state: " + nodeInfo.state);
|
|
81
|
+
if (nodeInfo.isOwner) {
|
|
82
|
+
// Owner node disconnected - initiate failover
|
|
83
|
+
this.handleOwnerDisconnection(address);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Child node disconnected - just mark as disconnected
|
|
87
|
+
this.handleChildDisconnection(address);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Handles owner node disconnection
|
|
92
|
+
* @param address The disconnected owner node address
|
|
93
|
+
*/
|
|
94
|
+
HazelcastFailoverManager.prototype.handleOwnerDisconnection = function (address) {
|
|
95
|
+
var addressStr = address.toString();
|
|
96
|
+
this.logger.warn('HazelcastFailoverManager', "Owner node " + addressStr + " disconnected, initiating failover");
|
|
97
|
+
// Mark current owner as failed
|
|
98
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
99
|
+
if (nodeInfo) {
|
|
100
|
+
nodeInfo.state = NodeState.FAILED;
|
|
101
|
+
nodeInfo.connection = null;
|
|
102
|
+
}
|
|
103
|
+
this.currentOwner = null;
|
|
104
|
+
// Find the best candidate for new owner
|
|
105
|
+
var newOwner = this.selectNewOwner();
|
|
106
|
+
if (newOwner) {
|
|
107
|
+
this.promoteToOwner(newOwner);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.logger.error('HazelcastFailoverManager', 'No suitable node found for ownership, cluster may be down');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Handles child node disconnection
|
|
115
|
+
* @param address The disconnected child node address
|
|
116
|
+
*/
|
|
117
|
+
HazelcastFailoverManager.prototype.handleChildDisconnection = function (address) {
|
|
118
|
+
var addressStr = address.toString();
|
|
119
|
+
this.logger.info('HazelcastFailoverManager', "Child node " + addressStr + " disconnected");
|
|
120
|
+
// Mark as disconnected but keep in nodes map
|
|
121
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
122
|
+
if (nodeInfo) {
|
|
123
|
+
nodeInfo.state = NodeState.DISCONNECTED;
|
|
124
|
+
nodeInfo.connection = null;
|
|
125
|
+
}
|
|
126
|
+
// No failover needed for child nodes
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Selects the best candidate for new owner
|
|
130
|
+
* @returns The address of the best candidate, or null if none available
|
|
131
|
+
*/
|
|
132
|
+
HazelcastFailoverManager.prototype.selectNewOwner = function () {
|
|
133
|
+
var candidates = [];
|
|
134
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
135
|
+
if (nodeInfo.state === NodeState.CONNECTED &&
|
|
136
|
+
nodeInfo.connection &&
|
|
137
|
+
nodeInfo.connection.isAlive() &&
|
|
138
|
+
!nodeInfo.isOwner) {
|
|
139
|
+
candidates.push(nodeInfo);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
if (candidates.length === 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// Select the node with the lowest failure count and most recent connection
|
|
146
|
+
candidates.sort(function (a, b) {
|
|
147
|
+
if (a.failureCount !== b.failureCount) {
|
|
148
|
+
return a.failureCount - b.failureCount;
|
|
149
|
+
}
|
|
150
|
+
return b.lastSeen - a.lastSeen;
|
|
151
|
+
});
|
|
152
|
+
return candidates[0].address;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Promotes a node to owner
|
|
156
|
+
* @param address The address of the node to promote
|
|
157
|
+
*/
|
|
158
|
+
HazelcastFailoverManager.prototype.promoteToOwner = function (address) {
|
|
159
|
+
var addressStr = address.toString();
|
|
160
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
161
|
+
if (!nodeInfo) {
|
|
162
|
+
this.logger.error('HazelcastFailoverManager', "Cannot promote " + addressStr + " to owner - node not found");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.logger.info('HazelcastFailoverManager', "Promoting " + addressStr + " to owner");
|
|
166
|
+
// Update node state
|
|
167
|
+
nodeInfo.state = NodeState.OWNER;
|
|
168
|
+
nodeInfo.isOwner = true;
|
|
169
|
+
// Update current owner
|
|
170
|
+
this.currentOwner = address;
|
|
171
|
+
// Notify client of ownership change
|
|
172
|
+
this.client.getClusterService().handleOwnershipChange(address, nodeInfo.connection);
|
|
173
|
+
this.logger.info('HazelcastFailoverManager', "Ownership transferred to " + addressStr);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Handles node reconnection
|
|
177
|
+
* @param address The reconnected node address
|
|
178
|
+
* @param connection The new connection
|
|
179
|
+
*/
|
|
180
|
+
HazelcastFailoverManager.prototype.handleNodeReconnection = function (address, connection) {
|
|
181
|
+
var addressStr = address.toString();
|
|
182
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
183
|
+
if (!nodeInfo) {
|
|
184
|
+
// New node joining the cluster
|
|
185
|
+
this.registerNode(address, connection, false);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.logger.info('HazelcastFailoverManager', "Node " + addressStr + " reconnected, previous state: " + nodeInfo.state);
|
|
189
|
+
// Update connection and state
|
|
190
|
+
nodeInfo.connection = connection;
|
|
191
|
+
nodeInfo.lastSeen = Date.now();
|
|
192
|
+
if (nodeInfo.state === NodeState.FAILED) {
|
|
193
|
+
// Failed node recovered - treat as child
|
|
194
|
+
nodeInfo.state = NodeState.CHILD;
|
|
195
|
+
nodeInfo.isOwner = false;
|
|
196
|
+
this.logger.info('HazelcastFailoverManager', "Failed node " + addressStr + " recovered and rejoined as child");
|
|
197
|
+
}
|
|
198
|
+
else if (nodeInfo.state === NodeState.DISCONNECTED) {
|
|
199
|
+
// Disconnected node reconnected
|
|
200
|
+
nodeInfo.state = nodeInfo.isOwner ? NodeState.OWNER : NodeState.CHILD;
|
|
201
|
+
this.logger.info('HazelcastFailoverManager', "Disconnected node " + addressStr + " reconnected");
|
|
202
|
+
}
|
|
203
|
+
// Stop any ongoing failover/reconnection logic
|
|
204
|
+
this.stopFailoverLogic();
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Stops all failover and reconnection logic
|
|
208
|
+
*/
|
|
209
|
+
HazelcastFailoverManager.prototype.stopFailoverLogic = function () {
|
|
210
|
+
this.logger.info('HazelcastFailoverManager', 'Stopping all failover and reconnection logic - cluster stable');
|
|
211
|
+
// Reset failure counts for all nodes
|
|
212
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
213
|
+
nodeInfo.failureCount = 0;
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Gets the current owner address
|
|
218
|
+
* @returns The current owner address or null if no owner
|
|
219
|
+
*/
|
|
220
|
+
HazelcastFailoverManager.prototype.getCurrentOwner = function () {
|
|
221
|
+
return this.currentOwner;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Gets all connected nodes
|
|
225
|
+
* @returns Array of connected node addresses
|
|
226
|
+
*/
|
|
227
|
+
HazelcastFailoverManager.prototype.getConnectedNodes = function () {
|
|
228
|
+
var connected = [];
|
|
229
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
230
|
+
if (nodeInfo.state === NodeState.CONNECTED ||
|
|
231
|
+
nodeInfo.state === NodeState.OWNER) {
|
|
232
|
+
connected.push(nodeInfo.address);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return connected;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Gets the cluster state summary
|
|
239
|
+
* @returns Summary of cluster state
|
|
240
|
+
*/
|
|
241
|
+
HazelcastFailoverManager.prototype.getClusterStateSummary = function () {
|
|
242
|
+
var summary = {
|
|
243
|
+
totalNodes: this.nodes.size,
|
|
244
|
+
currentOwner: this.currentOwner ? this.currentOwner.toString() : 'none',
|
|
245
|
+
connectedNodes: this.getConnectedNodes().map(function (addr) { return addr.toString(); }),
|
|
246
|
+
nodeStates: {}
|
|
247
|
+
};
|
|
248
|
+
this.nodes.forEach(function (nodeInfo, addressStr) {
|
|
249
|
+
summary.nodeStates[addressStr] = {
|
|
250
|
+
state: nodeInfo.state,
|
|
251
|
+
isOwner: nodeInfo.isOwner,
|
|
252
|
+
failureCount: nodeInfo.failureCount,
|
|
253
|
+
lastSeen: new Date(nodeInfo.lastSeen).toISOString()
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
return summary;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Cleans up failed nodes that haven't recovered
|
|
260
|
+
* @param maxFailureAge Maximum age for failed nodes (default: 5 minutes)
|
|
261
|
+
*/
|
|
262
|
+
HazelcastFailoverManager.prototype.cleanupFailedNodes = function (maxFailureAge) {
|
|
263
|
+
var _this = this;
|
|
264
|
+
if (maxFailureAge === void 0) { maxFailureAge = 300000; }
|
|
265
|
+
var now = Date.now();
|
|
266
|
+
var toRemove = [];
|
|
267
|
+
this.nodes.forEach(function (nodeInfo, addressStr) {
|
|
268
|
+
if (nodeInfo.state === NodeState.FAILED) {
|
|
269
|
+
var timeSinceFailure = now - nodeInfo.lastSeen;
|
|
270
|
+
if (timeSinceFailure > maxFailureAge) {
|
|
271
|
+
toRemove.push(addressStr);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (toRemove.length > 0) {
|
|
276
|
+
this.logger.info('HazelcastFailoverManager', "Cleaning up " + toRemove.length + " failed nodes");
|
|
277
|
+
toRemove.forEach(function (addressStr) {
|
|
278
|
+
_this.nodes.delete(addressStr);
|
|
279
|
+
_this.logger.debug('HazelcastFailoverManager', "Removed failed node: " + addressStr);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
return HazelcastFailoverManager;
|
|
284
|
+
}());
|
|
285
|
+
exports.HazelcastFailoverManager = HazelcastFailoverManager;
|
|
@@ -289,6 +289,10 @@ var InvocationService = /** @class */ (function () {
|
|
|
289
289
|
throw new Error("Still no partition owner for partition " + partitionId + " after refresh");
|
|
290
290
|
}
|
|
291
291
|
return _this.invokeOnAddress(invocation, newOwnerAddress);
|
|
292
|
+
}).catch(function (error) {
|
|
293
|
+
_this.logger.error('InvocationService', "Failed to refresh partition table for partition " + partitionId + ":", error);
|
|
294
|
+
// If partition refresh fails, reject the invocation instead of hanging
|
|
295
|
+
throw new Error("Cannot find partition owner for partition " + partitionId + ": " + error.message);
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
return this.client.getConnectionManager().getOrConnect(ownerAddress).then(function (connection) {
|
|
@@ -301,6 +305,10 @@ var InvocationService = /** @class */ (function () {
|
|
|
301
305
|
return _this.client.getPartitionService().refresh().then(function () {
|
|
302
306
|
// Retry the invocation with updated partition information
|
|
303
307
|
return _this.doInvoke(invocation);
|
|
308
|
+
}).catch(function (refreshError) {
|
|
309
|
+
_this.logger.error('InvocationService', "Failed to refresh partition table after partition owner failure:", refreshError);
|
|
310
|
+
// If refresh fails, reject the invocation instead of hanging
|
|
311
|
+
throw new Error("Partition owner " + ownerAddress.toString() + " unavailable and partition refresh failed: " + refreshError.message);
|
|
304
312
|
});
|
|
305
313
|
}
|
|
306
314
|
throw new HazelcastError_1.IOError(ownerAddress.toString() + '(partition owner) is not available.', e);
|
|
@@ -21,14 +21,41 @@ var StaleReadDetectorImpl = /** @class */ (function () {
|
|
|
21
21
|
this.partitionService = partitionService;
|
|
22
22
|
}
|
|
23
23
|
StaleReadDetectorImpl.prototype.isStaleRead = function (key, record) {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
try {
|
|
25
|
+
var metadata = this.getMetadataContainer(this.getPartitionId(record.key));
|
|
26
|
+
// Add null checks to prevent errors during failover
|
|
27
|
+
if (!metadata || !metadata.getUuid()) {
|
|
28
|
+
// If metadata is not available, consider the read as potentially stale
|
|
29
|
+
// This can happen during failover when partition information is being updated
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return !record.hasSameUuid(metadata.getUuid()) || record.getInvalidationSequence().lessThan(metadata.getStaleSequence());
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// During failover, partition service might not be fully initialized
|
|
36
|
+
// Consider the read as stale to be safe
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
26
39
|
};
|
|
27
40
|
StaleReadDetectorImpl.prototype.getMetadataContainer = function (partitionId) {
|
|
28
|
-
|
|
41
|
+
try {
|
|
42
|
+
return this.repairingHandler.getMetadataContainer(partitionId);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// Return null if metadata container is not available
|
|
46
|
+
// This can happen during failover scenarios
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
29
49
|
};
|
|
30
50
|
StaleReadDetectorImpl.prototype.getPartitionId = function (key) {
|
|
31
|
-
|
|
51
|
+
try {
|
|
52
|
+
return this.partitionService.getPartitionId(key);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Return -1 if partition service is not available
|
|
56
|
+
// This can happen during failover scenarios
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
32
59
|
};
|
|
33
60
|
return StaleReadDetectorImpl;
|
|
34
61
|
}());
|
|
@@ -130,20 +130,32 @@ var ProxyManager = /** @class */ (function () {
|
|
|
130
130
|
};
|
|
131
131
|
ProxyManager.prototype.findNextAddress = function () {
|
|
132
132
|
var members = this.client.getClusterService().getMembers();
|
|
133
|
+
// If no members available, return null but log the issue
|
|
134
|
+
if (!members || members.length === 0) {
|
|
135
|
+
this.logger.warn('ProxyManager', 'No cluster members available for proxy creation');
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
133
138
|
var liteMember = null;
|
|
139
|
+
var dataMember = null;
|
|
134
140
|
for (var _i = 0, members_1 = members; _i < members_1.length; _i++) {
|
|
135
141
|
var member = members_1[_i];
|
|
136
142
|
if (member != null && member.isLiteMember === false) {
|
|
137
|
-
|
|
143
|
+
dataMember = member;
|
|
144
|
+
break; // Prefer data members
|
|
138
145
|
}
|
|
139
146
|
else if (member != null && member.isLiteMember) {
|
|
140
147
|
liteMember = member;
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
|
-
if
|
|
150
|
+
// Return data member if available, otherwise lite member, otherwise null
|
|
151
|
+
if (dataMember != null) {
|
|
152
|
+
return dataMember.address;
|
|
153
|
+
}
|
|
154
|
+
else if (liteMember != null) {
|
|
144
155
|
return liteMember.address;
|
|
145
156
|
}
|
|
146
157
|
else {
|
|
158
|
+
this.logger.warn('ProxyManager', 'No valid members found for proxy creation');
|
|
147
159
|
return null;
|
|
148
160
|
}
|
|
149
161
|
};
|
|
@@ -151,6 +163,12 @@ var ProxyManager = /** @class */ (function () {
|
|
|
151
163
|
var _this = this;
|
|
152
164
|
if (Date.now() <= deadline) {
|
|
153
165
|
var address = this.findNextAddress();
|
|
166
|
+
if (!address) {
|
|
167
|
+
var error = new Error('No cluster members available for proxy creation: ' + proxyObject.getName());
|
|
168
|
+
this.logger.error('ProxyManager', error.message);
|
|
169
|
+
promise.reject(error);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
154
172
|
var request = ClientCreateProxyCodec_1.ClientCreateProxyCodec.encodeRequest(proxyObject.getName(), proxyObject.getServiceName(), address);
|
|
155
173
|
var invocation = new InvocationService_1.Invocation(this.client, request);
|
|
156
174
|
invocation.address = address;
|
|
@@ -160,10 +178,13 @@ var ProxyManager = /** @class */ (function () {
|
|
|
160
178
|
if (_this.isRetryable(error)) {
|
|
161
179
|
_this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() +
|
|
162
180
|
' failed. Retrying in ' + _this.invocationRetryPauseMillis + 'ms. ' + error);
|
|
163
|
-
setTimeout(
|
|
181
|
+
setTimeout(function () {
|
|
182
|
+
_this.initializeProxy(proxyObject, promise, deadline);
|
|
183
|
+
}, _this.invocationRetryPauseMillis);
|
|
164
184
|
}
|
|
165
185
|
else {
|
|
166
|
-
_this.logger.
|
|
186
|
+
_this.logger.error('ProxyManager', 'Create proxy request for ' + proxyObject.getName() + ' failed ' + error);
|
|
187
|
+
promise.reject(error);
|
|
167
188
|
}
|
|
168
189
|
});
|
|
169
190
|
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celerispay/hazelcast-client",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.7",
|
|
4
4
|
"description": "Hazelcast - open source In-Memory Data Grid - client for NodeJS with critical connection failover fixes",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"bluebird": "3.7.2",
|
|
8
|
-
"long": "4.0.0",
|
|
9
|
-
"safe-buffer": "5.2.1"
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@types/bluebird": "3.5.21",
|
|
13
|
-
"@types/long": "3.0.32",
|
|
14
|
-
"@types/node": "6.0.87",
|
|
15
|
-
"chai": "4.1.2",
|
|
16
|
-
"chai-as-promised": "7.1.1",
|
|
17
|
-
"hazelcast-remote-controller": "^1.0.0",
|
|
18
|
-
"istanbul": "0.4.5",
|
|
19
|
-
"jsonschema": "1.2.4",
|
|
20
|
-
"mocha": "3.5.3",
|
|
21
|
-
"mocha-junit-reporter": "1.18.0",
|
|
22
|
-
"mousse": "0.3.1",
|
|
23
|
-
"remap-istanbul": "0.9.6",
|
|
24
|
-
"rimraf": "2.6.2",
|
|
25
|
-
"sinon": "4.0.0",
|
|
26
|
-
"tslint": "5.7.0",
|
|
27
|
-
"typedoc": "^0.16.11",
|
|
28
|
-
"typescript": "2.8.4",
|
|
29
|
-
"winston": "2.3.1"
|
|
30
|
-
},
|
|
5
|
+
"main": "./lib/index.js",
|
|
31
6
|
"scripts": {
|
|
32
|
-
"clean": "rimraf lib typings *.jar *.log",
|
|
33
7
|
"compile": "tsc",
|
|
34
8
|
"pretest": "node download-remote-controller.js",
|
|
9
|
+
"build": "npm run compile",
|
|
10
|
+
"prepublishOnly": "npm run compile",
|
|
35
11
|
"test": "mocha --recursive --reporter-options mochaFile=report.xml --reporter mocha-junit-reporter",
|
|
36
12
|
"precoverage": "node download-remote-controller.js",
|
|
37
13
|
"coverage": "rimraf coverage && istanbul cover --root lib/ --include-all-sources node_modules/mocha/bin/_mocha -- --recursive --reporter-options mochaFile=report.xml --reporter mocha-junit-reporter",
|
|
@@ -55,10 +31,26 @@
|
|
|
55
31
|
"failover",
|
|
56
32
|
"connection-fix"
|
|
57
33
|
],
|
|
34
|
+
"author": "Hazelcast Inc.",
|
|
58
35
|
"license": "Apache-2.0",
|
|
59
36
|
"bugs": {
|
|
60
37
|
"url": "https://github.com/celerispay/hazelcast-nodejs-client/issues"
|
|
61
38
|
},
|
|
62
39
|
"homepage": "https://github.com/celerispay/hazelcast-nodejs-client#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"bluebird": "^3.7.2",
|
|
42
|
+
"long": "^4.0.0",
|
|
43
|
+
"safe-buffer": "^5.2.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/bluebird": "^3.5.21",
|
|
47
|
+
"@types/long": "^3.0.32",
|
|
48
|
+
"@types/node": "8.0.0",
|
|
49
|
+
"mocha": "3.2.0",
|
|
50
|
+
"sinon": "4.0.0",
|
|
51
|
+
"tslint": "5.7.0",
|
|
52
|
+
"typedoc": "^0.16.11",
|
|
53
|
+
"typescript": "2.8.4"
|
|
54
|
+
},
|
|
63
55
|
"typings": "./lib/index"
|
|
64
56
|
}
|