@celerispay/hazelcast-client 3.12.7-5 → 3.12.7-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.
@@ -98,9 +98,20 @@ export declare class ClusterService {
98
98
  private onHeartbeatStopped(connection);
99
99
  private triggerFailover();
100
100
  /**
101
- * Handles single-node cluster reset - treats node restart as fresh cluster
101
+ * Handles single-node node loss. Tears down all state immediately and starts
102
+ * a TCP probe loop. Only once the port accepts a raw TCP connection do we
103
+ * attempt the full Hazelcast handshake — eliminating the race between
104
+ * destroyConnection() re-adding failedConnections and our attempts to clear them.
102
105
  */
103
106
  private handleSingleNodeClusterReset();
107
+ /**
108
+ * Probes the single-node address with a raw TCP socket every probeIntervalMs.
109
+ * When the port responds (connect event), destroys the probe socket and
110
+ * calls connectToCluster() to perform the full Hazelcast authentication.
111
+ * If connectToCluster() fails (server still warming up), retries the probe.
112
+ * Stops if the client is shut down.
113
+ */
114
+ private startSingleNodeProbeLoop(address);
104
115
  /**
105
116
  * Handles multi-node failover - preserves existing logic
106
117
  */
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ var net = require("net");
18
19
  var Promise = require("bluebird");
19
20
  var ClientAddMembershipListenerCodec_1 = require("../codec/ClientAddMembershipListenerCodec");
20
21
  var ClientInfo_1 = require("../ClientInfo");
@@ -264,48 +265,78 @@ var ClusterService = /** @class */ (function () {
264
265
  }
265
266
  };
266
267
  /**
267
- * Handles single-node cluster reset - treats node restart as fresh cluster
268
+ * Handles single-node node loss. Tears down all state immediately and starts
269
+ * a TCP probe loop. Only once the port accepts a raw TCP connection do we
270
+ * attempt the full Hazelcast handshake — eliminating the race between
271
+ * destroyConnection() re-adding failedConnections and our attempts to clear them.
268
272
  */
269
273
  ClusterService.prototype.handleSingleNodeClusterReset = function () {
270
- var _this = this;
271
- this.logger.info('ClusterService', '🧹 SINGLE-NODE RESET: Clearing all credentials and state...');
272
- // Clear all stored credentials - node restart means fresh cluster
273
- this.client.getConnectionManager().clearAllCredentials();
274
- // Reset client UUIDs - will be assigned fresh by server
274
+ this.logger.info('ClusterService', 'SINGLE-NODE: Node lost. Tearing down state and starting TCP probe loop.');
275
+ // Full state teardown
275
276
  this.uuid = null;
276
277
  this.ownerUuid = null;
277
- // Log state before reset
278
- this.logCurrentState();
279
- // Force cleanup of all dead connections
278
+ this.members = [];
279
+ this.client.getConnectionManager().clearAllCredentials();
280
+ this.client.getConnectionManager().clearFailedConnections();
280
281
  this.client.getConnectionManager().forceCleanupDeadConnections();
281
- // Clear partition information
282
282
  this.client.getPartitionService().clearPartitionTable();
283
- // IMPORTANT: Unblock all down addresses before attempting reconnection.
284
- // We must clear BOTH blocklists:
285
- // 1. this.downAddresses — ClusterService-level block (checked by tryConnectingToAddresses)
286
- // 2. failedConnections — ClientConnectionManager-level block (checked by getOrConnect)
287
- // We delay by 200ms to let any in-flight async destroyConnection() calls finish,
288
- // because destroyConnection() re-adds the address to failedConnections after we clear it.
289
- // Clearing both right before connectToCluster() ensures neither blocklist interferes.
290
- this.logger.info('ClusterService', '🔓 SINGLE-NODE RESET: Scheduling reconnection after brief stabilization delay...');
291
- setTimeout(function () {
292
- // Clear both blocklists right before attempting reconnection
293
- _this.downAddresses.clear();
294
- _this.client.getConnectionManager().clearFailedConnections();
295
- _this.logger.info('ClusterService', '🔄 SINGLE-NODE RESET: Attempting direct reconnection...');
296
- _this.connectToCluster()
297
- .then(function () {
298
- _this.logger.info('ClusterService', '✅ Single-node cluster reset completed successfully');
299
- _this.logCurrentState();
300
- })
301
- .catch(function (error) {
302
- _this.logger.error('ClusterService', 'Single-node cluster reset failed', error);
303
- _this.logCurrentState();
304
- })
305
- .finally(function () {
283
+ this.downAddresses.clear();
284
+ // Start probing reconnect only when the port actually accepts TCP
285
+ var address = this.knownAddresses[0];
286
+ this.startSingleNodeProbeLoop(address);
287
+ };
288
+ /**
289
+ * Probes the single-node address with a raw TCP socket every probeIntervalMs.
290
+ * When the port responds (connect event), destroys the probe socket and
291
+ * calls connectToCluster() to perform the full Hazelcast authentication.
292
+ * If connectToCluster() fails (server still warming up), retries the probe.
293
+ * Stops if the client is shut down.
294
+ */
295
+ ClusterService.prototype.startSingleNodeProbeLoop = function (address) {
296
+ var _this = this;
297
+ var probeIntervalMs = 3000;
298
+ var host = address.host;
299
+ var port = address.port;
300
+ this.logger.info('ClusterService', "SINGLE-NODE PROBE: Waiting for " + host + ":" + port + " to accept connections (probing every " + probeIntervalMs + "ms)...");
301
+ var attempt = function () {
302
+ if (!_this.client.getLifecycleService().isRunning()) {
303
+ _this.logger.info('ClusterService', 'SINGLE-NODE PROBE: Client is shutting down, stopping probe loop.');
306
304
  _this.failoverInProgress = false;
305
+ return;
306
+ }
307
+ var socket = net.createConnection({ host: host, port: port });
308
+ socket.once('connect', function () {
309
+ socket.destroy();
310
+ _this.logger.info('ClusterService', "SINGLE-NODE PROBE: " + host + ":" + port + " is accepting connections. Reinitialising Hazelcast connection...");
311
+ // Clear both blocklists right before connecting — destroyConnection()
312
+ // may have re-added the address to failedConnections since the teardown above.
313
+ _this.downAddresses.clear();
314
+ _this.client.getConnectionManager().clearFailedConnections();
315
+ _this.connectToCluster()
316
+ .then(function () {
317
+ _this.logger.info('ClusterService', 'SINGLE-NODE: Reconnected successfully.');
318
+ _this.logCurrentState();
319
+ _this.failoverInProgress = false;
320
+ })
321
+ .catch(function (err) {
322
+ _this.logger.warn('ClusterService', "SINGLE-NODE: connectToCluster failed after probe success (" + err.message + "). Retrying probe...");
323
+ // Server accepted TCP but Hazelcast not fully ready yet — probe again
324
+ _this.downAddresses.clear();
325
+ _this.client.getConnectionManager().clearFailedConnections();
326
+ setTimeout(attempt, probeIntervalMs);
327
+ });
307
328
  });
308
- }, 200);
329
+ socket.once('error', function () {
330
+ socket.destroy();
331
+ setTimeout(attempt, probeIntervalMs);
332
+ });
333
+ // Guard against hung sockets that produce neither connect nor error
334
+ socket.setTimeout(2000, function () {
335
+ socket.destroy();
336
+ setTimeout(attempt, probeIntervalMs);
337
+ });
338
+ };
339
+ attempt();
309
340
  };
310
341
  /**
311
342
  * Handles multi-node failover - preserves existing logic
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celerispay/hazelcast-client",
3
- "version": "3.12.7-5",
3
+ "version": "3.12.7-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": {