@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.
@@ -24,6 +24,8 @@ var Util_1 = require("../Util");
24
24
  var MemberAttributeEvent_1 = require("../core/MemberAttributeEvent");
25
25
  var MembershipEvent_1 = require("../core/MembershipEvent");
26
26
  var UuidUtil_1 = require("../util/UuidUtil");
27
+ var Address = require("../Address");
28
+ var HazelcastFailoverManager_1 = require("./HazelcastFailoverManager");
27
29
  var MemberEvent;
28
30
  (function (MemberEvent) {
29
31
  MemberEvent[MemberEvent["ADDED"] = 1] = "ADDED";
@@ -49,10 +51,15 @@ var ClusterService = /** @class */ (function () {
49
51
  this.lastFailoverAttempt = 0;
50
52
  this.failoverCooldown = 5000; // 5 seconds cooldown between failover attempts
51
53
  this.downAddresses = new Map(); // address -> timestamp when marked down
52
- this.addressBlockDuration = 30000; // 30 seconds block duration for down addresses
54
+ this.addressBlockDuration = 15000; // Reduced from 30000ms to 15000ms
55
+ this.reconnectionTask = null;
56
+ this.reconnectionInterval = 10000; // 10 seconds between reconnection attempts
53
57
  this.client = client;
54
58
  this.logger = this.client.getLoggingService().getLogger();
55
59
  this.members = [];
60
+ this.startReconnectionTask();
61
+ this.startStateLoggingTask();
62
+ this.failoverManager = new HazelcastFailoverManager_1.HazelcastFailoverManager(client, this.logger);
56
63
  }
57
64
  /**
58
65
  * Starts cluster service.
@@ -98,6 +105,12 @@ var ClusterService = /** @class */ (function () {
98
105
  return Array.from(new Set(Array.from(addresses).concat(Array.from(providerAddresses))));
99
106
  });
100
107
  };
108
+ /**
109
+ * Returns the owner connection if available
110
+ */
111
+ ClusterService.prototype.getOwnerConnection = function () {
112
+ return this.ownerConnection;
113
+ };
101
114
  /**
102
115
  * Returns the list of members in the cluster.
103
116
  * @returns
@@ -139,16 +152,15 @@ var ClusterService = /** @class */ (function () {
139
152
  ClusterService.prototype.getClientInfo = function () {
140
153
  var info = new ClientInfo_1.ClientInfo();
141
154
  info.uuid = this.uuid;
142
- info.localAddress = this.getOwnerConnection().getLocalAddress();
155
+ var ownerConnection = this.getOwnerConnection();
156
+ if (ownerConnection) {
157
+ info.localAddress = ownerConnection.getLocalAddress();
158
+ }
159
+ else {
160
+ info.localAddress = null;
161
+ }
143
162
  return info;
144
163
  };
145
- /**
146
- * Returns the connection associated with owner node of this client.
147
- * @returns {ClientConnection}
148
- */
149
- ClusterService.prototype.getOwnerConnection = function () {
150
- return this.ownerConnection;
151
- };
152
164
  /**
153
165
  * Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
154
166
  * so if you register the listener twice, it will get events twice.
@@ -180,7 +192,11 @@ var ClusterService = /** @class */ (function () {
180
192
  var handleAttributeChange = _this.handleMemberAttributeChange.bind(_this);
181
193
  ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.handle(m, handleMember, handleMemberList, handleAttributeChange, null);
182
194
  };
183
- return this.client.getInvocationService().invokeOnConnection(this.getOwnerConnection(), request, handler)
195
+ var ownerConnection = this.getOwnerConnection();
196
+ if (!ownerConnection) {
197
+ return Promise.reject(new Error('Cannot initialize membership listener: no owner connection available'));
198
+ }
199
+ return this.client.getInvocationService().invokeOnConnection(ownerConnection, request, handler)
184
200
  .then(function (resp) {
185
201
  _this.logger.trace('ClusterService', 'Registered listener with id '
186
202
  + ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.decodeResponse(resp).response);
@@ -196,6 +212,8 @@ var ClusterService = /** @class */ (function () {
196
212
  };
197
213
  ClusterService.prototype.onConnectionClosed = function (connection) {
198
214
  this.logger.warn('ClusterService', 'Connection closed to ' + connection.toString());
215
+ // Mark the address as down when connection is closed
216
+ this.markAddressAsDown(connection.getAddress());
199
217
  if (connection.isAuthenticatedAsOwner()) {
200
218
  this.ownerConnection = null;
201
219
  this.triggerFailover();
@@ -203,6 +221,8 @@ var ClusterService = /** @class */ (function () {
203
221
  };
204
222
  ClusterService.prototype.onHeartbeatStopped = function (connection) {
205
223
  this.logger.warn('ClusterService', connection.toString() + ' stopped heartbeating.');
224
+ // Mark the address as down when heartbeat stops
225
+ this.markAddressAsDown(connection.getAddress());
206
226
  if (connection.isAuthenticatedAsOwner()) {
207
227
  this.client.getConnectionManager().destroyConnection(connection.getAddress());
208
228
  this.ownerConnection = null;
@@ -218,23 +238,69 @@ var ClusterService = /** @class */ (function () {
218
238
  }
219
239
  this.failoverInProgress = true;
220
240
  this.lastFailoverAttempt = now;
221
- this.logger.info('ClusterService', 'Starting failover process...');
241
+ this.logger.info('ClusterService', '🚀 Starting failover process - SERVER-FIRST APPROACH...');
242
+ // SERVER-FIRST: No credential preservation needed
243
+ // We trust the server will provide correct member information
244
+ this.logger.info('ClusterService', '🎯 SERVER-FIRST: No credential management - trusting server data');
245
+ // Log state before failover
246
+ this.logCurrentState();
247
+ // Force cleanup of all dead connections to prevent leakage
248
+ this.client.getConnectionManager().forceCleanupDeadConnections();
222
249
  // Clear any stale partition information
223
250
  this.client.getPartitionService().clearPartitionTable();
224
251
  // Attempt to reconnect to cluster
225
252
  this.connectToCluster()
226
253
  .then(function () {
227
- _this.logger.info('ClusterService', 'Failover completed successfully');
254
+ _this.logger.info('ClusterService', 'Failover completed successfully - SERVER-FIRST approach');
255
+ // No credential management needed - server handles everything
256
+ _this.logCurrentState(); // Log state after successful failover
228
257
  })
229
258
  .catch(function (error) {
230
259
  _this.logger.error('ClusterService', 'Failover failed', error);
231
- // If failover fails, shutdown the client to prevent further issues
232
- _this.client.shutdown();
260
+ // If failover fails, try to unblock at least one address to allow recovery
261
+ _this.attemptEmergencyRecovery();
262
+ _this.logCurrentState(); // Log state after failed failover
263
+ // Don't shutdown immediately, give recovery a chance
233
264
  })
234
265
  .finally(function () {
235
266
  _this.failoverInProgress = false;
236
267
  });
237
268
  };
269
+ /**
270
+ * Attempts emergency recovery when failover fails
271
+ */
272
+ ClusterService.prototype.attemptEmergencyRecovery = function () {
273
+ var _this = this;
274
+ this.logger.warn('ClusterService', 'Attempting emergency recovery...');
275
+ // Unblock at least one address to allow recovery
276
+ if (this.downAddresses.size > 0) {
277
+ var firstBlockedAddress_1 = Array.from(this.downAddresses.keys())[0];
278
+ this.logger.info('ClusterService', "Emergency unblocking address " + firstBlockedAddress_1);
279
+ this.downAddresses.delete(firstBlockedAddress_1);
280
+ // Try to connect to the unblocked address
281
+ try {
282
+ var _a = firstBlockedAddress_1.split(':'), host = _a[0], portStr = _a[1];
283
+ var port = parseInt(portStr, 10);
284
+ if (host && !isNaN(port)) {
285
+ var address_1 = new Address(host, port);
286
+ this.logger.info('ClusterService', "Attempting emergency connection to " + firstBlockedAddress_1);
287
+ // Try to connect without blocking
288
+ this.client.getConnectionManager().getOrConnect(address_1, false)
289
+ .then(function (connection) {
290
+ _this.logger.info('ClusterService', "Emergency connection successful to " + firstBlockedAddress_1);
291
+ _this.evaluateOwnershipChange(address_1, connection);
292
+ _this.client.getPartitionService().refresh();
293
+ })
294
+ .catch(function (error) {
295
+ _this.logger.warn('ClusterService', "Emergency connection failed to " + firstBlockedAddress_1 + ":", error);
296
+ });
297
+ }
298
+ }
299
+ catch (error) {
300
+ this.logger.error('ClusterService', 'Error during emergency recovery:', error);
301
+ }
302
+ }
303
+ };
238
304
  ClusterService.prototype.isAddressKnownDown = function (address) {
239
305
  var addressStr = address.toString();
240
306
  var downTime = this.downAddresses.get(addressStr);
@@ -341,6 +407,8 @@ var ClusterService = /** @class */ (function () {
341
407
  this.members = members;
342
408
  this.client.getPartitionService().refresh();
343
409
  this.logger.info('ClusterService', 'Members received.', this.members);
410
+ // Log current state after member list update
411
+ this.logCurrentState();
344
412
  var events = this.detectMembershipEvents(prevMembers);
345
413
  for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
346
414
  var event = events_1[_i];
@@ -399,6 +467,8 @@ var ClusterService = /** @class */ (function () {
399
467
  };
400
468
  ClusterService.prototype.memberAdded = function (member) {
401
469
  this.members.push(member);
470
+ // Handle member added and update preserved credentials
471
+ this.handleMemberAdded(member);
402
472
  var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.ADDED, this.members);
403
473
  this.fireMembershipEvent(membershipEvent);
404
474
  };
@@ -408,10 +478,355 @@ var ClusterService = /** @class */ (function () {
408
478
  var removedMemberList = this.members.splice(memberIndex, 1);
409
479
  assert(removedMemberList.length === 1);
410
480
  }
411
- this.client.getConnectionManager().destroyConnection(member.address);
481
+ // Check if we have a healthy connection to this member
482
+ var connectionManager = this.client.getConnectionManager();
483
+ var existingConnection = connectionManager.getConnection(member.address);
484
+ if (existingConnection && existingConnection.isHealthy()) {
485
+ // If the connection is healthy, don't destroy it immediately
486
+ // This prevents unnecessary disconnections during temporary network issues
487
+ this.logger.info('ClusterService', "Member removed but connection is healthy: " + member.address.toString() + ", preserving connection");
488
+ // Only destroy if we're not in failover mode
489
+ if (!this.failoverInProgress) {
490
+ this.logger.debug('ClusterService', "Destroying healthy connection to removed member: " + member.address.toString());
491
+ connectionManager.destroyConnection(member.address);
492
+ }
493
+ else {
494
+ this.logger.debug('ClusterService', "Preserving healthy connection during failover: " + member.address.toString());
495
+ }
496
+ }
497
+ else {
498
+ // If connection is unhealthy, destroy it
499
+ this.logger.info('ClusterService', "Member removed with unhealthy connection: " + member.address.toString() + ", destroying connection");
500
+ connectionManager.destroyConnection(member.address);
501
+ }
412
502
  var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.REMOVED, this.members);
413
503
  this.fireMembershipEvent(membershipEvent);
414
504
  };
505
+ /**
506
+ * Performs comprehensive credential cleanup when cluster membership changes
507
+ * This ensures ALL credentials are consistent with the current cluster owner UUID
508
+ * @param connectionManager The connection manager instance
509
+ * @param currentClusterOwnerUuid The current cluster owner UUID
510
+ */
511
+ /**
512
+ * Handles member added event - SERVER-FIRST APPROACH
513
+ * We trust what the server tells us and store it as credentials
514
+ * @param member The member that was added
515
+ */
516
+ ClusterService.prototype.handleMemberAdded = function (member) {
517
+ this.logger.info('ClusterService', "\u2705 SERVER CONFIRMED: Member[ uuid: " + member.uuid + ", address: " + member.address.toString() + "] added to cluster");
518
+ // SERVER-FIRST: Store server data as credentials
519
+ // The server is the authority - we store what it tells us
520
+ this.logger.info('ClusterService', "\uD83C\uDFAF SERVER-FIRST: Storing server member data as credentials - server is authority");
521
+ var connectionManager = this.client.getConnectionManager();
522
+ // Store the server-provided UUID as the authoritative credential
523
+ if (connectionManager && typeof connectionManager.updatePreservedCredentials === 'function') {
524
+ connectionManager.updatePreservedCredentials(member.address, member.uuid);
525
+ }
526
+ // Record that we received a member added event for this address
527
+ if (connectionManager && typeof connectionManager.recordMemberAddedEvent === 'function') {
528
+ connectionManager.recordMemberAddedEvent(member.address);
529
+ }
530
+ // Find the current owner from the cluster state
531
+ var currentOwner = this.findCurrentOwner();
532
+ if (currentOwner) {
533
+ this.logger.info('ClusterService', "\uD83D\uDD04 SERVER-FIRST: Updating ALL credentials with current owner UUID: " + currentOwner.uuid);
534
+ // Update all credentials with the current owner UUID from server
535
+ if (connectionManager && typeof connectionManager.updateAllCredentialsWithNewOwnerUuid === 'function') {
536
+ connectionManager.updateAllCredentialsWithNewOwnerUuid(currentOwner.uuid);
537
+ }
538
+ // CRITICAL FIX: Update client's own UUIDs to match server expectations
539
+ this.logger.info('ClusterService', "\uD83D\uDD04 SERVER-FIRST: Updating client UUIDs to match server state");
540
+ this.logger.info('ClusterService', " - Old Client UUID: " + (this.uuid || 'NOT SET'));
541
+ this.logger.info('ClusterService', " - Old Owner UUID: " + (this.ownerUuid || 'NOT SET'));
542
+ // Update client's own UUIDs with server-provided data
543
+ // The client UUID should match the owner's UUID for authentication
544
+ this.uuid = currentOwner.uuid;
545
+ this.ownerUuid = currentOwner.uuid;
546
+ this.logger.info('ClusterService', " - New Client UUID: " + this.uuid);
547
+ this.logger.info('ClusterService', " - New Owner UUID: " + this.ownerUuid);
548
+ }
549
+ // Refresh partition table (KEEPING REFRESH METHOD UNTOUCHED as requested)
550
+ this.client.getPartitionService().refresh();
551
+ this.logger.info('ClusterService', "\u2705 SERVER-FIRST: Member " + member.uuid + " at " + member.address.toString() + " credentials stored from server data");
552
+ };
553
+ /**
554
+ * Finds the current owner from the cluster state
555
+ * @returns The current owner member or null if not found
556
+ */
557
+ ClusterService.prototype.findCurrentOwner = function () {
558
+ // Check if we have an active owner connection
559
+ var ownerConnection = this.ownerConnection;
560
+ if (ownerConnection && ownerConnection.isAlive()) {
561
+ var ownerAddress = ownerConnection.getAddress();
562
+ // Find the member with this address
563
+ for (var _i = 0, _a = this.members; _i < _a.length; _i++) {
564
+ var member = _a[_i];
565
+ if (member.address.toString() === ownerAddress.toString()) {
566
+ this.logger.debug('ClusterService', "Found current owner: " + member.uuid + " at " + member.address.toString());
567
+ return member;
568
+ }
569
+ }
570
+ }
571
+ // Fallback: look for any member that might be the owner
572
+ this.logger.debug('ClusterService', "No active owner connection found, checking member list for potential owner");
573
+ return null;
574
+ };
575
+ /**
576
+ * Logs the current state for debugging purposes
577
+ */
578
+ ClusterService.prototype.logCurrentState = function () {
579
+ var _this = this;
580
+ var activeConnections = Object.keys(this.client.getConnectionManager().getEstablishedConnections()).length;
581
+ var memberCount = this.members.length;
582
+ var downAddressesCount = this.downAddresses.size;
583
+ var hasOwner = !!this.ownerConnection;
584
+ this.logger.info('ClusterService', "Current State - Members: " + memberCount + ", Active Connections: " + activeConnections + ", Down Addresses: " + downAddressesCount + ", Has Owner: " + hasOwner);
585
+ if (this.ownerConnection) {
586
+ this.logger.info('ClusterService', "Owner Connection: " + this.ownerConnection.getAddress().toString() + ", Alive: " + this.ownerConnection.isAlive());
587
+ }
588
+ // Log all active connections
589
+ var connections = this.client.getConnectionManager().getEstablishedConnections();
590
+ Object.keys(connections).forEach(function (addressStr) {
591
+ var connection = connections[addressStr];
592
+ _this.logger.debug('ClusterService', "Connection to " + addressStr + ": Alive=" + connection.isAlive() + ", Owner=" + connection.isAuthenticatedAsOwner());
593
+ });
594
+ // Log down addresses
595
+ if (downAddressesCount > 0) {
596
+ var downAddresses = Array.from(this.downAddresses.keys());
597
+ this.logger.debug('ClusterService', "Down Addresses: " + downAddresses.join(', '));
598
+ }
599
+ };
600
+ ClusterService.prototype.startReconnectionTask = function () {
601
+ var _this = this;
602
+ // Periodically attempt to reconnect to previously failed addresses
603
+ this.reconnectionTask = setInterval(function () {
604
+ _this.attemptReconnectionToFailedNodes();
605
+ }, this.reconnectionInterval);
606
+ };
607
+ /**
608
+ * Starts a periodic task to log the current state for debugging
609
+ */
610
+ ClusterService.prototype.startStateLoggingTask = function () {
611
+ var _this = this;
612
+ // Log state every 30 seconds for debugging
613
+ setInterval(function () {
614
+ if (_this.client.getLifecycleService().isRunning()) {
615
+ _this.logCurrentState();
616
+ }
617
+ }, 30000);
618
+ };
619
+ ClusterService.prototype.attemptReconnectionToFailedNodes = function () {
620
+ var _this = this;
621
+ // Allow reconnection even during failover, but be more careful
622
+ if (this.failoverInProgress) {
623
+ this.logger.debug('ClusterService', 'Skipping reconnection attempt during failover');
624
+ return;
625
+ }
626
+ var now = Date.now();
627
+ var addressesToReconnect = [];
628
+ var totalDownAddresses = this.downAddresses.size;
629
+ // If we have no down addresses, we can skip
630
+ if (totalDownAddresses === 0) {
631
+ return;
632
+ }
633
+ // Find addresses that are no longer blocked
634
+ this.downAddresses.forEach(function (downTime, addressStr) {
635
+ var timeSinceDown = now - downTime;
636
+ if (timeSinceDown > _this.addressBlockDuration) {
637
+ // Parse the address string back to Address object
638
+ try {
639
+ var _a = addressStr.split(':'), host = _a[0], portStr = _a[1];
640
+ var port = parseInt(portStr, 10);
641
+ if (host && !isNaN(port)) {
642
+ var address = new Address(host, port);
643
+ // Check if we already have a connection to this address
644
+ if (_this.client.getConnectionManager().hasConnection(address)) {
645
+ _this.logger.debug('ClusterService', "Already have active connection to " + addressStr + ", removing from down addresses");
646
+ _this.downAddresses.delete(addressStr);
647
+ return;
648
+ }
649
+ addressesToReconnect.push(address);
650
+ }
651
+ }
652
+ catch (error) {
653
+ _this.logger.warn('ClusterService', "Failed to parse address " + addressStr + " for reconnection:", error);
654
+ }
655
+ }
656
+ });
657
+ if (addressesToReconnect.length > 0) {
658
+ this.logger.info('ClusterService', "Attempting to reconnect to " + addressesToReconnect.length + " previously failed nodes: " + addressesToReconnect.map(function (addr) { return addr.toString(); }).join(', '));
659
+ // Attempt to establish connections to each unblocked address
660
+ addressesToReconnect.forEach(function (address) {
661
+ _this.attemptReconnectionToAddress(address);
662
+ });
663
+ }
664
+ else if (totalDownAddresses > 0) {
665
+ // Log remaining blocked addresses for debugging
666
+ var remainingBlocked = Array.from(this.downAddresses.keys()).map(function (addr) {
667
+ var downTime = _this.downAddresses.get(addr);
668
+ var timeSinceDown = now - downTime;
669
+ var remainingTime = Math.max(0, _this.addressBlockDuration - timeSinceDown);
670
+ return addr + " (" + Math.ceil(remainingTime / 1000) + "s remaining)";
671
+ });
672
+ this.logger.debug('ClusterService', "Still waiting for " + totalDownAddresses + " addresses to unblock: " + remainingBlocked.join(', '));
673
+ }
674
+ // Log current state after reconnection attempts
675
+ this.logCurrentState();
676
+ };
677
+ /**
678
+ * Attempts to establish a connection to a specific address
679
+ * @param address The address to reconnect to
680
+ */
681
+ ClusterService.prototype.attemptReconnectionToAddress = function (address) {
682
+ var _this = this;
683
+ var addressStr = address.toString();
684
+ // Check if we already have a connection to this address
685
+ if (this.client.getConnectionManager().hasConnection(address)) {
686
+ this.logger.debug('ClusterService', "Already have active connection to " + addressStr + ", skipping reconnection");
687
+ this.downAddresses.delete(addressStr);
688
+ return;
689
+ }
690
+ // Check if we're already trying to connect to this address
691
+ var connectionManager = this.client.getConnectionManager();
692
+ var establishedConnections = connectionManager.getEstablishedConnections();
693
+ var pendingConnections = Object.keys(connectionManager.getPendingConnections()).length;
694
+ if (pendingConnections > 0) {
695
+ this.logger.debug('ClusterService', "Already have pending connections, skipping reconnection to " + addressStr);
696
+ return;
697
+ }
698
+ // Remove from down addresses to allow connection attempt
699
+ this.downAddresses.delete(addressStr);
700
+ this.logger.debug('ClusterService', "Attempting reconnection to " + addressStr);
701
+ // Attempt to establish connection (not as owner, just as regular member connection)
702
+ this.client.getConnectionManager().getOrConnect(address, false)
703
+ .then(function (connection) {
704
+ _this.logger.info('ClusterService', "Successfully reconnected to " + addressStr);
705
+ // Only evaluate ownership change if we don't have an owner or current owner is unhealthy
706
+ if (!_this.ownerConnection || !_this.ownerConnection.isHealthy()) {
707
+ _this.logger.info('ClusterService', "Evaluating ownership change for " + addressStr);
708
+ _this.evaluateOwnershipChange(address, connection);
709
+ }
710
+ else {
711
+ _this.logger.debug('ClusterService', "Keeping " + addressStr + " as member connection, current owner is healthy");
712
+ }
713
+ // Trigger partition service refresh to update routing information
714
+ _this.client.getPartitionService().refresh();
715
+ }).catch(function (error) {
716
+ _this.logger.warn('ClusterService', "Reconnection attempt to " + addressStr + " failed:", error);
717
+ // Mark the address as down again, but with a shorter block duration for reconnection attempts
718
+ var shorterBlockDuration = Math.min(_this.addressBlockDuration / 2, 15000); // Max 15 seconds
719
+ _this.markAddressAsDownWithDuration(address, shorterBlockDuration);
720
+ });
721
+ };
722
+ /**
723
+ * Evaluates whether we should switch ownership to a reconnected node
724
+ * @param address The address of the reconnected node
725
+ * @param connection The connection to the reconnected node
726
+ */
727
+ ClusterService.prototype.evaluateOwnershipChange = function (address, connection) {
728
+ // If we don't have an owner connection, this reconnected node becomes the owner
729
+ if (!this.ownerConnection) {
730
+ this.logger.info('ClusterService', "Promoting reconnected node " + address.toString() + " to owner status");
731
+ this.promoteToOwner(connection, address);
732
+ return;
733
+ }
734
+ // If our current owner connection is having issues, consider switching
735
+ if (this.ownerConnection && !this.ownerConnection.isAlive()) {
736
+ this.logger.info('ClusterService', "Current owner is unhealthy, switching to reconnected node " + address.toString());
737
+ this.promoteToOwner(connection, address);
738
+ return;
739
+ }
740
+ // Don't switch ownership if current owner is healthy
741
+ this.logger.debug('ClusterService', "Current owner is healthy, keeping " + address.toString() + " as member connection");
742
+ };
743
+ /**
744
+ * Promotes a connection to owner status
745
+ * @param connection The connection to promote
746
+ * @param address The address of the promoted connection
747
+ */
748
+ ClusterService.prototype.promoteToOwner = function (connection, address) {
749
+ try {
750
+ // Close the old owner connection if it exists
751
+ if (this.ownerConnection && this.ownerConnection !== connection) {
752
+ this.logger.info('ClusterService', "Closing previous owner connection to " + this.ownerConnection.getAddress().toString());
753
+ this.client.getConnectionManager().destroyConnection(this.ownerConnection.getAddress());
754
+ }
755
+ // Set the new owner connection
756
+ connection.setAuthenticatedAsOwner(true);
757
+ this.ownerConnection = connection;
758
+ this.logger.info('ClusterService', "Successfully promoted " + address.toString() + " to owner status");
759
+ // Refresh partition information with the new owner
760
+ this.client.getPartitionService().refresh();
761
+ }
762
+ catch (error) {
763
+ this.logger.error('ClusterService', "Failed to promote " + address.toString() + " to owner:", error);
764
+ // If promotion fails, mark the address as down again
765
+ this.markAddressAsDown(address);
766
+ }
767
+ };
768
+ /**
769
+ * Marks an address as down with a custom block duration
770
+ * @param address The address to mark as down
771
+ * @param blockDuration The duration to block the address (in milliseconds)
772
+ */
773
+ ClusterService.prototype.markAddressAsDownWithDuration = function (address, blockDuration) {
774
+ var _this = this;
775
+ var addressStr = address.toString();
776
+ var now = Date.now();
777
+ // Don't block if we already have a healthy connection to this address
778
+ if (this.client.getConnectionManager().hasConnection(address)) {
779
+ this.logger.debug('ClusterService', "Not blocking " + addressStr + " as we have a healthy connection");
780
+ return;
781
+ }
782
+ // Don't block if this would leave us with no available nodes
783
+ var totalDownAddresses = this.downAddresses.size;
784
+ var totalMembers = this.members.length;
785
+ if (totalDownAddresses >= totalMembers - 1) {
786
+ this.logger.warn('ClusterService', "Not blocking " + addressStr + " as it would leave us with no available nodes");
787
+ return;
788
+ }
789
+ this.downAddresses.set(addressStr, now);
790
+ this.logger.warn('ClusterService', "Marked address " + addressStr + " as down, will be blocked for " + blockDuration + "ms");
791
+ // Schedule cleanup of this address after block duration
792
+ setTimeout(function () {
793
+ if (_this.downAddresses.has(addressStr)) {
794
+ _this.logger.info('ClusterService', "Unblocking address " + addressStr + " after block duration");
795
+ _this.downAddresses.delete(addressStr);
796
+ }
797
+ }, blockDuration);
798
+ };
799
+ /**
800
+ * Handles ownership change when failover occurs
801
+ * @param newOwnerAddress The address of the new owner
802
+ * @param newOwnerConnection The connection to the new owner
803
+ */
804
+ ClusterService.prototype.handleOwnershipChange = function (newOwnerAddress, newOwnerConnection) {
805
+ this.logger.info('ClusterService', "Handling ownership change to " + newOwnerAddress.toString());
806
+ // Update owner connection
807
+ this.ownerConnection = newOwnerConnection;
808
+ // Note: Owner UUID will be updated when authentication completes
809
+ // For now, we'll keep the existing owner UUID
810
+ // Clear any stale partition information
811
+ this.client.getPartitionService().clearPartitionTable();
812
+ // Refresh partition information with the new owner
813
+ this.client.getPartitionService().refresh();
814
+ this.logger.info('ClusterService', "Ownership change completed, new owner: " + newOwnerAddress.toString());
815
+ };
816
+ /**
817
+ * Gets the failover manager for external access
818
+ * @returns The failover manager instance
819
+ */
820
+ ClusterService.prototype.getFailoverManager = function () {
821
+ return this.failoverManager;
822
+ };
823
+ ClusterService.prototype.shutdown = function () {
824
+ if (this.reconnectionTask) {
825
+ clearInterval(this.reconnectionTask);
826
+ this.reconnectionTask = null;
827
+ }
828
+ this.downAddresses.clear();
829
+ };
415
830
  return ClusterService;
416
831
  }());
417
832
  exports.ClusterService = ClusterService;
@@ -10,5 +10,16 @@ export declare class ConnectionAuthenticator {
10
10
  private logger;
11
11
  constructor(connection: ClientConnection, client: HazelcastClient);
12
12
  authenticate(asOwner: boolean): Promise<void>;
13
+ /**
14
+ * Gets a human-readable description of authentication status
15
+ */
16
+ private getStatusDescription(status);
17
+ /**
18
+ * Creates credentials with optional restoration from preservation service
19
+ * @param asOwner Whether this is an owner connection
20
+ * @param preservedCredentials Optional preserved credentials to use
21
+ * @returns The credentials message
22
+ */
23
+ createCredentialsWithRestoration(asOwner: boolean, preservedCredentials?: any): ClientMessage;
13
24
  createCredentials(asOwner: boolean): ClientMessage;
14
25
  }