@celerispay/hazelcast-client 3.12.5-4 → 3.12.5-6

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.
@@ -82,9 +82,14 @@ export declare class ClientConnectionManager extends EventEmitter {
82
82
  destroyConnection(address: Address): void;
83
83
  /**
84
84
  * Cleans up all connections to a specific address during failover
85
- * @param address
85
+ * @param address The address to cleanup
86
86
  */
87
87
  cleanupConnectionsForFailover(address: Address): void;
88
+ /**
89
+ * Handles authentication errors by clearing failed connections and allowing retry
90
+ * @param address The address that had authentication issues
91
+ */
92
+ handleAuthenticationError(address: Address): void;
88
93
  shutdown(): void;
89
94
  private triggerConnect(address, asOwner);
90
95
  private connectTLSSocket(address, configOpts);
@@ -133,6 +133,16 @@ var ClientConnectionManager = /** @class */ (function (_super) {
133
133
  _this.failedConnections.delete(address.toString());
134
134
  return connection;
135
135
  }).catch(function (error) {
136
+ // Check if it's an authentication error
137
+ var isAuthError = error.message && (error.message.includes('Invalid Credentials') ||
138
+ error.message.includes('authentication') ||
139
+ error.message.includes('credentials'));
140
+ if (isAuthError) {
141
+ _this.logger.error('ClientConnectionManager', "Authentication failed for " + address.toString() + ", not retrying: " + error.message);
142
+ // Handle authentication errors specially
143
+ _this.handleAuthenticationError(address);
144
+ throw error;
145
+ }
136
146
  if (retryCount < _this.maxConnectionRetries) {
137
147
  _this.logger.warn('ClientConnectionManager', "Connection attempt " + (retryCount + 1) + " failed for " + address.toString() + ", retrying in " + _this.connectionRetryDelay + "ms");
138
148
  return new Promise(function (resolve, reject) {
@@ -328,7 +338,7 @@ var ClientConnectionManager = /** @class */ (function (_super) {
328
338
  };
329
339
  /**
330
340
  * Cleans up all connections to a specific address during failover
331
- * @param address
341
+ * @param address The address to cleanup
332
342
  */
333
343
  ClientConnectionManager.prototype.cleanupConnectionsForFailover = function (address) {
334
344
  var addressStr = address.toString();
@@ -338,6 +348,27 @@ var ClientConnectionManager = /** @class */ (function (_super) {
338
348
  // Remove from failed connections to allow reconnection after failover
339
349
  this.failedConnections.delete(addressStr);
340
350
  };
351
+ /**
352
+ * Handles authentication errors by clearing failed connections and allowing retry
353
+ * @param address The address that had authentication issues
354
+ */
355
+ ClientConnectionManager.prototype.handleAuthenticationError = function (address) {
356
+ var addressStr = address.toString();
357
+ this.logger.warn('ClientConnectionManager', "Handling authentication error for " + addressStr);
358
+ // Clear from failed connections to allow retry with fresh credentials
359
+ this.failedConnections.delete(addressStr);
360
+ // Also clear any established connections to this address
361
+ if (this.establishedConnections.hasOwnProperty(addressStr)) {
362
+ this.logger.info('ClientConnectionManager', "Clearing established connection to " + addressStr + " due to auth error");
363
+ this.destroyConnection(address);
364
+ }
365
+ // Clear any pending connections
366
+ if (this.pendingConnections.hasOwnProperty(addressStr)) {
367
+ this.logger.info('ClientConnectionManager', "Clearing pending connection to " + addressStr + " due to auth error");
368
+ this.pendingConnections[addressStr].reject(new Error('Authentication error, connection cleared'));
369
+ delete this.pendingConnections[addressStr];
370
+ }
371
+ };
341
372
  ClientConnectionManager.prototype.shutdown = function () {
342
373
  if (this.connectionHealthCheckInterval) {
343
374
  clearInterval(this.connectionHealthCheckInterval);
@@ -47,6 +47,10 @@ export declare class ClusterService {
47
47
  */
48
48
  connectToCluster(): Promise<void>;
49
49
  getPossibleMemberAddresses(): Promise<string[]>;
50
+ /**
51
+ * Returns the owner connection if available
52
+ */
53
+ getOwnerConnection(): ClientConnection | null;
50
54
  /**
51
55
  * Returns the list of members in the cluster.
52
56
  * @returns
@@ -63,11 +67,6 @@ export declare class ClusterService {
63
67
  * @returns {ClientInfo}
64
68
  */
65
69
  getClientInfo(): ClientInfo;
66
- /**
67
- * Returns the connection associated with owner node of this client.
68
- * @returns {ClientConnection}
69
- */
70
- getOwnerConnection(): ClientConnection;
71
70
  /**
72
71
  * Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
73
72
  * so if you register the listener twice, it will get events twice.
@@ -87,6 +86,10 @@ export declare class ClusterService {
87
86
  private onConnectionClosed(connection);
88
87
  private onHeartbeatStopped(connection);
89
88
  private triggerFailover();
89
+ /**
90
+ * Attempts emergency recovery when failover fails
91
+ */
92
+ private attemptEmergencyRecovery();
90
93
  private isAddressKnownDown(address);
91
94
  private markAddressAsDown(address);
92
95
  private getDownAddressesInfo();
@@ -50,7 +50,7 @@ var ClusterService = /** @class */ (function () {
50
50
  this.lastFailoverAttempt = 0;
51
51
  this.failoverCooldown = 5000; // 5 seconds cooldown between failover attempts
52
52
  this.downAddresses = new Map(); // address -> timestamp when marked down
53
- this.addressBlockDuration = 30000; // 30 seconds block duration for down addresses
53
+ this.addressBlockDuration = 15000; // Reduced from 30000ms to 15000ms
54
54
  this.reconnectionTask = null;
55
55
  this.reconnectionInterval = 10000; // 10 seconds between reconnection attempts
56
56
  this.client = client;
@@ -103,6 +103,12 @@ var ClusterService = /** @class */ (function () {
103
103
  return Array.from(new Set(Array.from(addresses).concat(Array.from(providerAddresses))));
104
104
  });
105
105
  };
106
+ /**
107
+ * Returns the owner connection if available
108
+ */
109
+ ClusterService.prototype.getOwnerConnection = function () {
110
+ return this.ownerConnection;
111
+ };
106
112
  /**
107
113
  * Returns the list of members in the cluster.
108
114
  * @returns
@@ -144,16 +150,15 @@ var ClusterService = /** @class */ (function () {
144
150
  ClusterService.prototype.getClientInfo = function () {
145
151
  var info = new ClientInfo_1.ClientInfo();
146
152
  info.uuid = this.uuid;
147
- info.localAddress = this.getOwnerConnection().getLocalAddress();
153
+ var ownerConnection = this.getOwnerConnection();
154
+ if (ownerConnection) {
155
+ info.localAddress = ownerConnection.getLocalAddress();
156
+ }
157
+ else {
158
+ info.localAddress = null;
159
+ }
148
160
  return info;
149
161
  };
150
- /**
151
- * Returns the connection associated with owner node of this client.
152
- * @returns {ClientConnection}
153
- */
154
- ClusterService.prototype.getOwnerConnection = function () {
155
- return this.ownerConnection;
156
- };
157
162
  /**
158
163
  * Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
159
164
  * so if you register the listener twice, it will get events twice.
@@ -185,7 +190,11 @@ var ClusterService = /** @class */ (function () {
185
190
  var handleAttributeChange = _this.handleMemberAttributeChange.bind(_this);
186
191
  ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.handle(m, handleMember, handleMemberList, handleAttributeChange, null);
187
192
  };
188
- return this.client.getInvocationService().invokeOnConnection(this.getOwnerConnection(), request, handler)
193
+ var ownerConnection = this.getOwnerConnection();
194
+ if (!ownerConnection) {
195
+ return Promise.reject(new Error('Cannot initialize membership listener: no owner connection available'));
196
+ }
197
+ return this.client.getInvocationService().invokeOnConnection(ownerConnection, request, handler)
189
198
  .then(function (resp) {
190
199
  _this.logger.trace('ClusterService', 'Registered listener with id '
191
200
  + ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.decodeResponse(resp).response);
@@ -242,14 +251,50 @@ var ClusterService = /** @class */ (function () {
242
251
  })
243
252
  .catch(function (error) {
244
253
  _this.logger.error('ClusterService', 'Failover failed', error);
254
+ // If failover fails, try to unblock at least one address to allow recovery
255
+ _this.attemptEmergencyRecovery();
245
256
  _this.logCurrentState(); // Log state after failed failover
246
- // If failover fails, shutdown the client to prevent further issues
247
- _this.client.shutdown();
257
+ // Don't shutdown immediately, give recovery a chance
248
258
  })
249
259
  .finally(function () {
250
260
  _this.failoverInProgress = false;
251
261
  });
252
262
  };
263
+ /**
264
+ * Attempts emergency recovery when failover fails
265
+ */
266
+ ClusterService.prototype.attemptEmergencyRecovery = function () {
267
+ var _this = this;
268
+ this.logger.warn('ClusterService', 'Attempting emergency recovery...');
269
+ // Unblock at least one address to allow recovery
270
+ if (this.downAddresses.size > 0) {
271
+ var firstBlockedAddress_1 = Array.from(this.downAddresses.keys())[0];
272
+ this.logger.info('ClusterService', "Emergency unblocking address " + firstBlockedAddress_1);
273
+ this.downAddresses.delete(firstBlockedAddress_1);
274
+ // Try to connect to the unblocked address
275
+ try {
276
+ var _a = firstBlockedAddress_1.split(':'), host = _a[0], portStr = _a[1];
277
+ var port = parseInt(portStr, 10);
278
+ if (host && !isNaN(port)) {
279
+ var address_1 = new Address(host, port);
280
+ this.logger.info('ClusterService', "Attempting emergency connection to " + firstBlockedAddress_1);
281
+ // Try to connect without blocking
282
+ this.client.getConnectionManager().getOrConnect(address_1, false)
283
+ .then(function (connection) {
284
+ _this.logger.info('ClusterService', "Emergency connection successful to " + firstBlockedAddress_1);
285
+ _this.evaluateOwnershipChange(address_1, connection);
286
+ _this.client.getPartitionService().refresh();
287
+ })
288
+ .catch(function (error) {
289
+ _this.logger.warn('ClusterService', "Emergency connection failed to " + firstBlockedAddress_1 + ":", error);
290
+ });
291
+ }
292
+ }
293
+ catch (error) {
294
+ this.logger.error('ClusterService', 'Error during emergency recovery:', error);
295
+ }
296
+ }
297
+ };
253
298
  ClusterService.prototype.isAddressKnownDown = function (address) {
254
299
  var addressStr = address.toString();
255
300
  var downTime = this.downAddresses.get(addressStr);
@@ -567,7 +612,7 @@ var ClusterService = /** @class */ (function () {
567
612
  // Check if we're already trying to connect to this address
568
613
  var connectionManager = this.client.getConnectionManager();
569
614
  var establishedConnections = connectionManager.getEstablishedConnections();
570
- var pendingConnections = Object.keys(connectionManager.getPendingConnections || {}).length;
615
+ var pendingConnections = Object.keys(connectionManager.getPendingConnections()).length;
571
616
  if (pendingConnections > 0) {
572
617
  this.logger.debug('ClusterService', "Already have pending connections, skipping reconnection to " + addressStr);
573
618
  return;
@@ -651,6 +696,18 @@ var ClusterService = /** @class */ (function () {
651
696
  var _this = this;
652
697
  var addressStr = address.toString();
653
698
  var now = Date.now();
699
+ // Don't block if we already have a healthy connection to this address
700
+ if (this.client.getConnectionManager().hasConnection(address)) {
701
+ this.logger.debug('ClusterService', "Not blocking " + addressStr + " as we have a healthy connection");
702
+ return;
703
+ }
704
+ // Don't block if this would leave us with no available nodes
705
+ var totalDownAddresses = this.downAddresses.size;
706
+ var totalMembers = this.members.length;
707
+ if (totalDownAddresses >= totalMembers - 1) {
708
+ this.logger.warn('ClusterService', "Not blocking " + addressStr + " as it would leave us with no available nodes");
709
+ return;
710
+ }
654
711
  this.downAddresses.set(addressStr, now);
655
712
  this.logger.warn('ClusterService', "Marked address " + addressStr + " as down, will be blocked for " + blockDuration + "ms");
656
713
  // Schedule cleanup of this address after block duration
@@ -37,4 +37,8 @@ export declare class ProxyManager {
37
37
  private findNextAddress();
38
38
  private initializeProxy(proxyObject, promise, deadline);
39
39
  private createDistributedObjectListener();
40
+ /**
41
+ * Checks if the cluster is healthy enough to create proxies
42
+ */
43
+ private isClusterHealthy();
40
44
  }
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ var Promise = require("bluebird");
18
19
  var ClientAddDistributedObjectListenerCodec_1 = require("../codec/ClientAddDistributedObjectListenerCodec");
19
20
  var ClientCreateProxyCodec_1 = require("../codec/ClientCreateProxyCodec");
20
21
  var ClientDestroyProxyCodec_1 = require("../codec/ClientDestroyProxyCodec");
@@ -68,6 +69,12 @@ var ProxyManager = /** @class */ (function () {
68
69
  if (this.proxies[fullName]) {
69
70
  return this.proxies[fullName];
70
71
  }
72
+ // Check if cluster is healthy before creating proxy
73
+ if (!this.isClusterHealthy()) {
74
+ var error = new Error('Cluster is not healthy, cannot create proxy for ' + name);
75
+ this.logger.error('ProxyManager', error.message);
76
+ return Promise.reject(error);
77
+ }
71
78
  var deferred = Util_1.DeferredPromise();
72
79
  var newProxy;
73
80
  if (serviceName === ProxyManager.MAP_SERVICE && this.client.getConfig().getNearCacheConfig(name)) {
@@ -91,6 +98,9 @@ var ProxyManager = /** @class */ (function () {
91
98
  if (createAtServer) {
92
99
  this.createProxy(newProxy).then(function () {
93
100
  deferred.resolve(newProxy);
101
+ }).catch(function (error) {
102
+ _this.logger.error('ProxyManager', 'Failed to create proxy for ' + name + ': ' + error);
103
+ deferred.reject(error);
94
104
  });
95
105
  }
96
106
  this.proxies[fullName] = deferred.promise;
@@ -130,46 +140,68 @@ var ProxyManager = /** @class */ (function () {
130
140
  };
131
141
  ProxyManager.prototype.findNextAddress = function () {
132
142
  var members = this.client.getClusterService().getMembers();
143
+ // If no members available, return null but log the issue
144
+ if (!members || members.length === 0) {
145
+ this.logger.warn('ProxyManager', 'No cluster members available for proxy creation');
146
+ return null;
147
+ }
133
148
  var liteMember = null;
149
+ var dataMember = null;
134
150
  for (var _i = 0, members_1 = members; _i < members_1.length; _i++) {
135
151
  var member = members_1[_i];
136
152
  if (member != null && member.isLiteMember === false) {
137
- return member.address;
153
+ dataMember = member;
154
+ break; // Prefer data members
138
155
  }
139
156
  else if (member != null && member.isLiteMember) {
140
157
  liteMember = member;
141
158
  }
142
159
  }
143
- if (liteMember != null) {
160
+ // Return data member if available, otherwise lite member, otherwise null
161
+ if (dataMember != null) {
162
+ return dataMember.address;
163
+ }
164
+ else if (liteMember != null) {
144
165
  return liteMember.address;
145
166
  }
146
167
  else {
168
+ this.logger.warn('ProxyManager', 'No valid members found for proxy creation');
147
169
  return null;
148
170
  }
149
171
  };
150
172
  ProxyManager.prototype.initializeProxy = function (proxyObject, promise, deadline) {
151
173
  var _this = this;
152
- if (Date.now() <= deadline) {
153
- var address = this.findNextAddress();
154
- var request = ClientCreateProxyCodec_1.ClientCreateProxyCodec.encodeRequest(proxyObject.getName(), proxyObject.getServiceName(), address);
155
- var invocation = new InvocationService_1.Invocation(this.client, request);
156
- invocation.address = address;
157
- this.client.getInvocationService().invoke(invocation).then(function (response) {
158
- promise.resolve(response);
159
- }).catch(function (error) {
160
- if (_this.isRetryable(error)) {
161
- _this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() +
162
- ' failed. Retrying in ' + _this.invocationRetryPauseMillis + 'ms. ' + error);
163
- setTimeout(_this.initializeProxy.bind(_this, proxyObject, promise, deadline), _this.invocationRetryPauseMillis);
164
- }
165
- else {
166
- _this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() + ' failed ' + error);
167
- }
168
- });
174
+ if (Date.now() > deadline) {
175
+ var error = new Error('Create proxy request timed-out for ' + proxyObject.getName());
176
+ this.logger.error('ProxyManager', error.message);
177
+ promise.reject(error);
178
+ return;
169
179
  }
170
- else {
171
- promise.reject('Create proxy request timed-out for ' + proxyObject.getName());
180
+ var address = this.findNextAddress();
181
+ if (!address) {
182
+ var error = new Error('No cluster members available for proxy creation: ' + proxyObject.getName());
183
+ this.logger.error('ProxyManager', error.message);
184
+ promise.reject(error);
185
+ return;
172
186
  }
187
+ var request = ClientCreateProxyCodec_1.ClientCreateProxyCodec.encodeRequest(proxyObject.getName(), proxyObject.getServiceName(), address);
188
+ var invocation = new InvocationService_1.Invocation(this.client, request);
189
+ invocation.address = address;
190
+ this.client.getInvocationService().invoke(invocation).then(function (response) {
191
+ promise.resolve(response);
192
+ }).catch(function (error) {
193
+ if (_this.isRetryable(error)) {
194
+ _this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() +
195
+ ' failed. Retrying in ' + _this.invocationRetryPauseMillis + 'ms. ' + error);
196
+ setTimeout(function () {
197
+ _this.initializeProxy(proxyObject, promise, deadline);
198
+ }, _this.invocationRetryPauseMillis);
199
+ }
200
+ else {
201
+ _this.logger.error('ProxyManager', 'Create proxy request for ' + proxyObject.getName() + ' failed ' + error);
202
+ promise.reject(error);
203
+ }
204
+ });
173
205
  };
174
206
  ProxyManager.prototype.createDistributedObjectListener = function () {
175
207
  return {
@@ -184,6 +216,30 @@ var ProxyManager = /** @class */ (function () {
184
216
  },
185
217
  };
186
218
  };
219
+ /**
220
+ * Checks if the cluster is healthy enough to create proxies
221
+ */
222
+ ProxyManager.prototype.isClusterHealthy = function () {
223
+ var members = this.client.getClusterService().getMembers();
224
+ var hasMembers = members && members.length > 0;
225
+ if (!hasMembers) {
226
+ this.logger.warn('ProxyManager', 'No cluster members available');
227
+ return false;
228
+ }
229
+ // Check if we have at least one data member
230
+ var hasDataMember = members.some(function (member) { return member && !member.isLiteMember; });
231
+ if (!hasDataMember) {
232
+ this.logger.warn('ProxyManager', 'No data members available in cluster');
233
+ return false;
234
+ }
235
+ // Check if we have an owner connection
236
+ var ownerConnection = this.client.getClusterService().getOwnerConnection();
237
+ if (!ownerConnection || !ownerConnection.isHealthy()) {
238
+ this.logger.warn('ProxyManager', 'No healthy owner connection available');
239
+ return false;
240
+ }
241
+ return true;
242
+ };
187
243
  ProxyManager.MAP_SERVICE = 'hz:impl:mapService';
188
244
  ProxyManager.SET_SERVICE = 'hz:impl:setService';
189
245
  ProxyManager.LOCK_SERVICE = 'hz:impl:lockService';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celerispay/hazelcast-client",
3
- "version": "3.12.5-4",
3
+ "version": "3.12.5-6",
4
4
  "description": "Hazelcast - open source In-Memory Data Grid - client for NodeJS with critical connection failover fixes",
5
5
  "main": "./lib/index.js",
6
6
  "scripts": {