@celerispay/hazelcast-client 3.12.5-2 → 3.12.5-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.
|
@@ -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
|
-
|
|
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
|
-
//
|
|
127
|
-
return connection.
|
|
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;
|
|
@@ -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}.
|
|
@@ -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
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
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.
|
|
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