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

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.
@@ -46,7 +46,12 @@ export declare class ClientConnection {
46
46
  private readonly socket;
47
47
  private readonly writer;
48
48
  private readonly reader;
49
+ private readonly logger;
49
50
  constructor(client: HazelcastClient, address: Address, socket: net.Socket);
51
+ /**
52
+ * Sets up socket event handlers for proper connection state tracking
53
+ */
54
+ private setupSocketEventHandlers();
50
55
  /**
51
56
  * Returns the address of local port that is associated with this connection.
52
57
  * @returns
@@ -65,7 +70,16 @@ export declare class ClientConnection {
65
70
  * Closes this connection.
66
71
  */
67
72
  close(): void;
73
+ /**
74
+ * Checks if the connection is alive and healthy
75
+ * @returns true if connection is alive, false otherwise
76
+ */
68
77
  isAlive(): boolean;
78
+ /**
79
+ * Performs a more thorough health check of the connection
80
+ * @returns true if connection is healthy, false otherwise
81
+ */
82
+ isHealthy(): boolean;
69
83
  isHeartbeating(): boolean;
70
84
  setHeartbeating(heartbeating: boolean): void;
71
85
  isAuthenticatedAsOwner(): boolean;
@@ -217,7 +217,36 @@ var ClientConnection = /** @class */ (function () {
217
217
  _this.lastWriteTimeMillis = Date.now();
218
218
  });
219
219
  this.reader = new FrameReader();
220
+ this.logger = client.getLoggingService().getLogger();
221
+ // Add socket event handlers for proper connection state tracking
222
+ this.setupSocketEventHandlers();
220
223
  }
224
+ /**
225
+ * Sets up socket event handlers for proper connection state tracking
226
+ */
227
+ ClientConnection.prototype.setupSocketEventHandlers = function () {
228
+ var _this = this;
229
+ // Handle socket close events
230
+ this.socket.on('close', function () {
231
+ _this.closedTime = Date.now();
232
+ _this.logger.debug('ClientConnection', "Socket closed for " + _this.address.toString());
233
+ });
234
+ // Handle socket end events
235
+ this.socket.on('end', function () {
236
+ _this.closedTime = Date.now();
237
+ _this.logger.debug('ClientConnection', "Socket ended by remote for " + _this.address.toString());
238
+ });
239
+ // Handle socket errors
240
+ this.socket.on('error', function (error) {
241
+ _this.logger.warn('ClientConnection', "Socket error for " + _this.address.toString() + ":", error);
242
+ // Don't set closedTime here as the socket might still be usable
243
+ });
244
+ // Handle socket timeout
245
+ this.socket.on('timeout', function () {
246
+ _this.logger.warn('ClientConnection', "Socket timeout for " + _this.address.toString());
247
+ // Don't set closedTime here as the socket might still be usable
248
+ });
249
+ };
221
250
  /**
222
251
  * Returns the address of local port that is associated with this connection.
223
252
  * @returns
@@ -254,8 +283,43 @@ var ClientConnection = /** @class */ (function () {
254
283
  this.socket.end();
255
284
  this.closedTime = Date.now();
256
285
  };
286
+ /**
287
+ * Checks if the connection is alive and healthy
288
+ * @returns true if connection is alive, false otherwise
289
+ */
257
290
  ClientConnection.prototype.isAlive = function () {
258
- return this.closedTime === 0;
291
+ // Check if we've explicitly closed the connection
292
+ if (this.closedTime !== 0) {
293
+ return false;
294
+ }
295
+ // Check if the underlying socket is still connected
296
+ if (!this.socket || this.socket.destroyed) {
297
+ return false;
298
+ }
299
+ // Check if the socket is writable (indicates it's still connected)
300
+ if (!this.socket.writable) {
301
+ return false;
302
+ }
303
+ // Check if we've received data recently (within last 30 seconds)
304
+ var now = Date.now();
305
+ var lastReadThreshold = 30000; // 30 seconds
306
+ if (this.lastReadTimeMillis > 0 && (now - this.lastReadTimeMillis) > lastReadThreshold) {
307
+ // If we haven't received data for a while, the connection might be stale
308
+ return false;
309
+ }
310
+ return true;
311
+ };
312
+ /**
313
+ * Performs a more thorough health check of the connection
314
+ * @returns true if connection is healthy, false otherwise
315
+ */
316
+ ClientConnection.prototype.isHealthy = function () {
317
+ if (!this.isAlive()) {
318
+ return false;
319
+ }
320
+ // Additional health checks can be added here
321
+ // For now, just check if we're still heartbeating
322
+ return this.isHeartbeating();
259
323
  };
260
324
  ClientConnection.prototype.isHeartbeating = function () {
261
325
  return this.heartbeating;
@@ -42,6 +42,11 @@ export declare class ClientConnectionManager extends EventEmitter {
42
42
  * @returns true if connection exists and is healthy, false otherwise
43
43
  */
44
44
  hasConnection(address: Address): boolean;
45
+ /**
46
+ * Forces cleanup of all dead connections
47
+ * This is useful during failover to prevent connection leakage
48
+ */
49
+ forceCleanupDeadConnections(): void;
45
50
  /**
46
51
  * Gets an existing connection to a specific address if it exists
47
52
  * @param address The address to check for existing connection
@@ -55,6 +60,13 @@ export declare class ClientConnectionManager extends EventEmitter {
55
60
  getEstablishedConnections(): {
56
61
  [address: string]: ClientConnection;
57
62
  };
63
+ /**
64
+ * Gets all pending connections
65
+ * @returns Object containing all pending connections
66
+ */
67
+ getPendingConnections(): {
68
+ [address: string]: Promise.Resolver<ClientConnection>;
69
+ };
58
70
  /**
59
71
  * Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
60
72
  * it first connects to the address and then return the {@link ClientConnection}.
@@ -123,8 +123,8 @@ var ClientConnectionManager = /** @class */ (function (_super) {
123
123
  }
124
124
  };
125
125
  ClientConnectionManager.prototype.isConnectionHealthy = function (connection) {
126
- // Only check if connection is alive - isAuthenticated() method doesn't exist
127
- return connection.isAlive();
126
+ // Use the improved health check method
127
+ return connection.isHealthy();
128
128
  };
129
129
  ClientConnectionManager.prototype.retryConnection = function (address, asOwner, retryCount) {
130
130
  var _this = this;
@@ -135,9 +135,9 @@ var ClientConnectionManager = /** @class */ (function (_super) {
135
135
  }).catch(function (error) {
136
136
  if (retryCount < _this.maxConnectionRetries) {
137
137
  _this.logger.warn('ClientConnectionManager', "Connection attempt " + (retryCount + 1) + " failed for " + address.toString() + ", retrying in " + _this.connectionRetryDelay + "ms");
138
- return new Promise(function (resolve) {
138
+ return new Promise(function (resolve, reject) {
139
139
  setTimeout(function () {
140
- _this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(resolve);
140
+ _this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(reject);
141
141
  }, _this.connectionRetryDelay);
142
142
  });
143
143
  }
@@ -181,6 +181,40 @@ var ClientConnectionManager = /** @class */ (function (_super) {
181
181
  var connection = this.establishedConnections[addressStr];
182
182
  return connection && connection.isAlive();
183
183
  };
184
+ /**
185
+ * Forces cleanup of all dead connections
186
+ * This is useful during failover to prevent connection leakage
187
+ */
188
+ ClientConnectionManager.prototype.forceCleanupDeadConnections = function () {
189
+ var _this = this;
190
+ this.logger.info('ClientConnectionManager', 'Forcing cleanup of all dead connections');
191
+ var addressesToRemove = [];
192
+ // Check all established connections
193
+ Object.keys(this.establishedConnections).forEach(function (addressStr) {
194
+ var connection = _this.establishedConnections[addressStr];
195
+ if (connection && !connection.isHealthy()) {
196
+ _this.logger.warn('ClientConnectionManager', "Found dead connection to " + addressStr + ", marking for cleanup");
197
+ addressesToRemove.push(addressStr);
198
+ }
199
+ });
200
+ // Remove dead connections
201
+ addressesToRemove.forEach(function (addressStr) {
202
+ var connection = _this.establishedConnections[addressStr];
203
+ if (connection) {
204
+ _this.destroyConnection(connection.getAddress());
205
+ }
206
+ });
207
+ // Also clean up any pending connections that might be stuck
208
+ Object.keys(this.pendingConnections).forEach(function (addressStr) {
209
+ var pendingConnection = _this.pendingConnections[addressStr];
210
+ if (pendingConnection) {
211
+ _this.logger.warn('ClientConnectionManager', "Cleaning up stuck pending connection to " + addressStr);
212
+ pendingConnection.reject(new Error('Connection cleanup forced'));
213
+ delete _this.pendingConnections[addressStr];
214
+ }
215
+ });
216
+ this.logger.info('ClientConnectionManager', "Cleanup completed. Removed " + addressesToRemove.length + " dead connections");
217
+ };
184
218
  /**
185
219
  * Gets an existing connection to a specific address if it exists
186
220
  * @param address The address to check for existing connection
@@ -197,6 +231,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
197
231
  ClientConnectionManager.prototype.getEstablishedConnections = function () {
198
232
  return this.establishedConnections;
199
233
  };
234
+ /**
235
+ * Gets all pending connections
236
+ * @returns Object containing all pending connections
237
+ */
238
+ ClientConnectionManager.prototype.getPendingConnections = function () {
239
+ return this.pendingConnections;
240
+ };
200
241
  /**
201
242
  * Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
202
243
  * it first connects to the address and then return the {@link ClientConnection}.
@@ -232,6 +273,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
232
273
  this.pendingConnections[addressIndex] = connectionResolver;
233
274
  this.retryConnection(address, asOwner)
234
275
  .then(function (clientConnection) {
276
+ // Safety check: ensure we got a proper ClientConnection
277
+ if (!clientConnection || typeof clientConnection.getAddress !== 'function') {
278
+ var error = new Error("Invalid connection object returned: " + typeof clientConnection);
279
+ _this.logger.error('ClientConnectionManager', error.message);
280
+ connectionResolver.reject(error);
281
+ return;
282
+ }
235
283
  _this.establishedConnections[clientConnection.getAddress().toString()] = clientConnection;
236
284
  _this.onConnectionOpened(clientConnection);
237
285
  connectionResolver.resolve(clientConnection);
@@ -230,6 +230,8 @@ var ClusterService = /** @class */ (function () {
230
230
  this.logger.info('ClusterService', 'Starting failover process...');
231
231
  // Log state before failover
232
232
  this.logCurrentState();
233
+ // Force cleanup of all dead connections to prevent leakage
234
+ this.client.getConnectionManager().forceCleanupDeadConnections();
233
235
  // Clear any stale partition information
234
236
  this.client.getPartitionService().clearPartitionTable();
235
237
  // Attempt to reconnect to cluster
@@ -423,13 +425,26 @@ var ClusterService = /** @class */ (function () {
423
425
  var removedMemberList = this.members.splice(memberIndex, 1);
424
426
  assert(removedMemberList.length === 1);
425
427
  }
426
- // Don't automatically destroy connections during failover
427
- if (!this.failoverInProgress) {
428
- this.logger.info('ClusterService', "Member removed: " + member.address.toString() + ", destroying connection");
429
- this.client.getConnectionManager().destroyConnection(member.address);
428
+ // Check if we have a healthy connection to this member
429
+ var connectionManager = this.client.getConnectionManager();
430
+ var existingConnection = connectionManager.getConnection(member.address);
431
+ if (existingConnection && existingConnection.isHealthy()) {
432
+ // If the connection is healthy, don't destroy it immediately
433
+ // This prevents unnecessary disconnections during temporary network issues
434
+ this.logger.info('ClusterService', "Member removed but connection is healthy: " + member.address.toString() + ", preserving connection");
435
+ // Only destroy if we're not in failover mode
436
+ if (!this.failoverInProgress) {
437
+ this.logger.debug('ClusterService', "Destroying healthy connection to removed member: " + member.address.toString());
438
+ connectionManager.destroyConnection(member.address);
439
+ }
440
+ else {
441
+ this.logger.debug('ClusterService', "Preserving healthy connection during failover: " + member.address.toString());
442
+ }
430
443
  }
431
444
  else {
432
- this.logger.debug('ClusterService', "Member removed during failover: " + member.address.toString() + ", keeping connection for evaluation");
445
+ // If connection is unhealthy, destroy it
446
+ this.logger.info('ClusterService', "Member removed with unhealthy connection: " + member.address.toString() + ", destroying connection");
447
+ connectionManager.destroyConnection(member.address);
433
448
  }
434
449
  var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.REMOVED, this.members);
435
450
  this.fireMembershipEvent(membershipEvent);
@@ -549,6 +564,14 @@ var ClusterService = /** @class */ (function () {
549
564
  this.downAddresses.delete(addressStr);
550
565
  return;
551
566
  }
567
+ // Check if we're already trying to connect to this address
568
+ var connectionManager = this.client.getConnectionManager();
569
+ var establishedConnections = connectionManager.getEstablishedConnections();
570
+ var pendingConnections = Object.keys(connectionManager.getPendingConnections || {}).length;
571
+ if (pendingConnections > 0) {
572
+ this.logger.debug('ClusterService', "Already have pending connections, skipping reconnection to " + addressStr);
573
+ return;
574
+ }
552
575
  // Remove from down addresses to allow connection attempt
553
576
  this.downAddresses.delete(addressStr);
554
577
  this.logger.debug('ClusterService', "Attempting reconnection to " + addressStr);
@@ -557,7 +580,7 @@ var ClusterService = /** @class */ (function () {
557
580
  .then(function (connection) {
558
581
  _this.logger.info('ClusterService', "Successfully reconnected to " + addressStr);
559
582
  // Only evaluate ownership change if we don't have an owner or current owner is unhealthy
560
- if (!_this.ownerConnection || !_this.ownerConnection.isAlive()) {
583
+ if (!_this.ownerConnection || !_this.ownerConnection.isHealthy()) {
561
584
  _this.logger.info('ClusterService', "Evaluating ownership change for " + addressStr);
562
585
  _this.evaluateOwnershipChange(address, connection);
563
586
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celerispay/hazelcast-client",
3
- "version": "3.12.5-2",
3
+ "version": "3.12.5-4",
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": {