@celerispay/hazelcast-client 3.12.5 → 3.12.7-3

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.
@@ -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
- var metadata = this.getMetadataContainer(this.getPartitionId(record.key));
25
- return !record.hasSameUuid(metadata.getUuid()) || record.getInvalidationSequence().lessThan(metadata.getStaleSequence());
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
- return this.repairingHandler.getMetadataContainer(partitionId);
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
- return this.partitionService.getPartitionId(key);
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
- return member.address;
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 (liteMember != null) {
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(_this.initializeProxy.bind(_this, proxyObject, promise, deadline), _this.invocationRetryPauseMillis);
181
+ setTimeout(function () {
182
+ _this.initializeProxy(proxyObject, promise, deadline);
183
+ }, _this.invocationRetryPauseMillis);
164
184
  }
165
185
  else {
166
- _this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() + ' failed ' + error);
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.5",
3
+ "version": "3.12.7-3",
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
  }