@celerispay/hazelcast-client 3.12.5-1 → 3.12.5-10
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.
- package/lib/PartitionService.d.ts +0 -3
- package/lib/PartitionService.js +1 -20
- package/lib/invocation/ClientConnection.d.ts +14 -0
- package/lib/invocation/ClientConnection.js +65 -1
- package/lib/invocation/ClientConnectionManager.d.ts +37 -1
- package/lib/invocation/ClientConnectionManager.js +121 -5
- package/lib/invocation/ClusterService.d.ts +16 -5
- package/lib/invocation/ClusterService.js +172 -23
- package/lib/invocation/InvocationService.js +8 -0
- package/lib/proxy/ProxyManager.js +25 -4
- package/package.json +1 -1
|
@@ -11,9 +11,6 @@ export declare class PartitionService {
|
|
|
11
11
|
private logger;
|
|
12
12
|
private lastRefreshTime;
|
|
13
13
|
private readonly minRefreshInterval;
|
|
14
|
-
private refreshInProgress;
|
|
15
|
-
private readonly maxRefreshRetries;
|
|
16
|
-
private refreshRetryCount;
|
|
17
14
|
constructor(client: HazelcastClient);
|
|
18
15
|
initialize(): Promise<void>;
|
|
19
16
|
shutdown(): void;
|
package/lib/PartitionService.js
CHANGED
|
@@ -23,9 +23,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
23
23
|
this.partitionMap = {};
|
|
24
24
|
this.lastRefreshTime = 0;
|
|
25
25
|
this.minRefreshInterval = 2000; // Minimum 2 seconds between refreshes
|
|
26
|
-
this.refreshInProgress = false;
|
|
27
|
-
this.maxRefreshRetries = 3;
|
|
28
|
-
this.refreshRetryCount = 0;
|
|
29
26
|
this.client = client;
|
|
30
27
|
this.logger = client.getLoggingService().getLogger();
|
|
31
28
|
this.isShutdown = false;
|
|
@@ -37,7 +34,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
37
34
|
PartitionService.prototype.shutdown = function () {
|
|
38
35
|
clearInterval(this.partitionRefreshTask);
|
|
39
36
|
this.isShutdown = true;
|
|
40
|
-
this.refreshInProgress = false;
|
|
41
37
|
};
|
|
42
38
|
/**
|
|
43
39
|
* Clears the partition table, forcing a refresh on next operation
|
|
@@ -47,7 +43,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
47
43
|
this.partitionMap = {};
|
|
48
44
|
this.partitionCount = 0;
|
|
49
45
|
this.lastRefreshTime = 0;
|
|
50
|
-
this.refreshRetryCount = 0;
|
|
51
46
|
};
|
|
52
47
|
/**
|
|
53
48
|
* Refreshes the internal partition table.
|
|
@@ -62,16 +57,11 @@ var PartitionService = /** @class */ (function () {
|
|
|
62
57
|
this.logger.debug('PartitionService', 'Skipping refresh, too soon since last refresh');
|
|
63
58
|
return Promise.resolve();
|
|
64
59
|
}
|
|
65
|
-
if (this.refreshInProgress) {
|
|
66
|
-
this.logger.debug('PartitionService', 'Refresh already in progress, skipping');
|
|
67
|
-
return Promise.resolve();
|
|
68
|
-
}
|
|
69
60
|
var ownerConnection = this.client.getClusterService().getOwnerConnection();
|
|
70
61
|
if (ownerConnection == null) {
|
|
71
62
|
this.logger.warn('PartitionService', 'Cannot refresh partitions, no owner connection available');
|
|
72
63
|
return Promise.resolve();
|
|
73
64
|
}
|
|
74
|
-
this.refreshInProgress = true;
|
|
75
65
|
var clientMessage = GetPartitionsCodec.encodeRequest();
|
|
76
66
|
return this.client.getInvocationService()
|
|
77
67
|
.invokeOnConnection(ownerConnection, clientMessage)
|
|
@@ -80,21 +70,12 @@ var PartitionService = /** @class */ (function () {
|
|
|
80
70
|
_this.partitionMap = receivedPartitionMap;
|
|
81
71
|
_this.partitionCount = Object.keys(_this.partitionMap).length;
|
|
82
72
|
_this.lastRefreshTime = now;
|
|
83
|
-
_this.refreshRetryCount = 0; // Reset retry count on success
|
|
84
73
|
_this.logger.debug('PartitionService', "Refreshed partition table with " + _this.partitionCount + " partitions");
|
|
85
74
|
}).catch(function (e) {
|
|
86
75
|
if (_this.client.getLifecycleService().isRunning()) {
|
|
87
76
|
_this.logger.warn('PartitionService', 'Error while fetching cluster partition table from '
|
|
88
|
-
+ _this.client.getClusterService().ownerUuid, e);
|
|
89
|
-
// Increment retry count and clear table if too many failures
|
|
90
|
-
_this.refreshRetryCount++;
|
|
91
|
-
if (_this.refreshRetryCount >= _this.maxRefreshRetries) {
|
|
92
|
-
_this.logger.error('PartitionService', "Max refresh retries (" + _this.maxRefreshRetries + ") exceeded, clearing partition table");
|
|
93
|
-
_this.clearPartitionTable();
|
|
94
|
-
}
|
|
77
|
+
+ _this.client.getClusterService().ownerUuid || 'unknown', e);
|
|
95
78
|
}
|
|
96
|
-
}).finally(function () {
|
|
97
|
-
_this.refreshInProgress = false;
|
|
98
79
|
});
|
|
99
80
|
};
|
|
100
81
|
/**
|
|
@@ -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;
|
|
@@ -36,6 +36,37 @@ export declare class ClientConnectionManager extends EventEmitter {
|
|
|
36
36
|
getActiveConnections(): {
|
|
37
37
|
[address: string]: ClientConnection;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Checks if we already have a connection to the given address
|
|
41
|
+
* @param address The address to check
|
|
42
|
+
* @returns true if connection exists and is healthy, false otherwise
|
|
43
|
+
*/
|
|
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;
|
|
50
|
+
/**
|
|
51
|
+
* Gets an existing connection to a specific address if it exists
|
|
52
|
+
* @param address The address to check for existing connection
|
|
53
|
+
* @returns The existing connection or undefined if none exists
|
|
54
|
+
*/
|
|
55
|
+
getConnection(address: Address): ClientConnection | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Gets all established connections
|
|
58
|
+
* @returns Object containing all established connections
|
|
59
|
+
*/
|
|
60
|
+
getEstablishedConnections(): {
|
|
61
|
+
[address: string]: ClientConnection;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Gets all pending connections
|
|
65
|
+
* @returns Object containing all pending connections
|
|
66
|
+
*/
|
|
67
|
+
getPendingConnections(): {
|
|
68
|
+
[address: string]: Promise.Resolver<ClientConnection>;
|
|
69
|
+
};
|
|
39
70
|
/**
|
|
40
71
|
* Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
|
|
41
72
|
* it first connects to the address and then return the {@link ClientConnection}.
|
|
@@ -51,9 +82,14 @@ export declare class ClientConnectionManager extends EventEmitter {
|
|
|
51
82
|
destroyConnection(address: Address): void;
|
|
52
83
|
/**
|
|
53
84
|
* Cleans up all connections to a specific address during failover
|
|
54
|
-
* @param address
|
|
85
|
+
* @param address The address to cleanup
|
|
55
86
|
*/
|
|
56
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;
|
|
57
93
|
shutdown(): void;
|
|
58
94
|
private triggerConnect(address, asOwner);
|
|
59
95
|
private connectTLSSocket(address, configOpts);
|
|
@@ -98,6 +98,17 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
98
98
|
_this.destroyConnection(connection.getAddress());
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
|
+
// Log current connection state
|
|
102
|
+
var activeConnections = Object.keys(this.establishedConnections).length;
|
|
103
|
+
var pendingConnections = Object.keys(this.pendingConnections).length;
|
|
104
|
+
var failedConnections = this.failedConnections.size;
|
|
105
|
+
this.logger.debug('ClientConnectionManager', "Connection State - Active: " + activeConnections + ", Pending: " + pendingConnections + ", Failed: " + failedConnections);
|
|
106
|
+
if (activeConnections > 0) {
|
|
107
|
+
Object.keys(this.establishedConnections).forEach(function (addressStr) {
|
|
108
|
+
var connection = _this.establishedConnections[addressStr];
|
|
109
|
+
_this.logger.debug('ClientConnectionManager', "Connection to " + addressStr + ": Alive=" + connection.isAlive() + ", Owner=" + connection.isAuthenticatedAsOwner());
|
|
110
|
+
});
|
|
111
|
+
}
|
|
101
112
|
};
|
|
102
113
|
ClientConnectionManager.prototype.checkConnectionHealth = function () {
|
|
103
114
|
// Use Object.keys() instead of Object.values() for compatibility
|
|
@@ -112,8 +123,8 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
112
123
|
}
|
|
113
124
|
};
|
|
114
125
|
ClientConnectionManager.prototype.isConnectionHealthy = function (connection) {
|
|
115
|
-
//
|
|
116
|
-
return connection.
|
|
126
|
+
// Use the improved health check method
|
|
127
|
+
return connection.isHealthy();
|
|
117
128
|
};
|
|
118
129
|
ClientConnectionManager.prototype.retryConnection = function (address, asOwner, retryCount) {
|
|
119
130
|
var _this = this;
|
|
@@ -122,11 +133,21 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
122
133
|
_this.failedConnections.delete(address.toString());
|
|
123
134
|
return connection;
|
|
124
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
|
+
}
|
|
125
146
|
if (retryCount < _this.maxConnectionRetries) {
|
|
126
147
|
_this.logger.warn('ClientConnectionManager', "Connection attempt " + (retryCount + 1) + " failed for " + address.toString() + ", retrying in " + _this.connectionRetryDelay + "ms");
|
|
127
|
-
return new Promise(function (resolve) {
|
|
148
|
+
return new Promise(function (resolve, reject) {
|
|
128
149
|
setTimeout(function () {
|
|
129
|
-
_this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(
|
|
150
|
+
_this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(reject);
|
|
130
151
|
}, _this.connectionRetryDelay);
|
|
131
152
|
});
|
|
132
153
|
}
|
|
@@ -160,6 +181,73 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
160
181
|
ClientConnectionManager.prototype.getActiveConnections = function () {
|
|
161
182
|
return this.establishedConnections;
|
|
162
183
|
};
|
|
184
|
+
/**
|
|
185
|
+
* Checks if we already have a connection to the given address
|
|
186
|
+
* @param address The address to check
|
|
187
|
+
* @returns true if connection exists and is healthy, false otherwise
|
|
188
|
+
*/
|
|
189
|
+
ClientConnectionManager.prototype.hasConnection = function (address) {
|
|
190
|
+
var addressStr = address.toString();
|
|
191
|
+
var connection = this.establishedConnections[addressStr];
|
|
192
|
+
return connection && connection.isAlive();
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Forces cleanup of all dead connections
|
|
196
|
+
* This is useful during failover to prevent connection leakage
|
|
197
|
+
*/
|
|
198
|
+
ClientConnectionManager.prototype.forceCleanupDeadConnections = function () {
|
|
199
|
+
var _this = this;
|
|
200
|
+
this.logger.info('ClientConnectionManager', 'Forcing cleanup of all dead connections');
|
|
201
|
+
var addressesToRemove = [];
|
|
202
|
+
// Check all established connections
|
|
203
|
+
Object.keys(this.establishedConnections).forEach(function (addressStr) {
|
|
204
|
+
var connection = _this.establishedConnections[addressStr];
|
|
205
|
+
if (connection && !connection.isHealthy()) {
|
|
206
|
+
_this.logger.warn('ClientConnectionManager', "Found dead connection to " + addressStr + ", marking for cleanup");
|
|
207
|
+
addressesToRemove.push(addressStr);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Remove dead connections
|
|
211
|
+
addressesToRemove.forEach(function (addressStr) {
|
|
212
|
+
var connection = _this.establishedConnections[addressStr];
|
|
213
|
+
if (connection) {
|
|
214
|
+
_this.destroyConnection(connection.getAddress());
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// Also clean up any pending connections that might be stuck
|
|
218
|
+
Object.keys(this.pendingConnections).forEach(function (addressStr) {
|
|
219
|
+
var pendingConnection = _this.pendingConnections[addressStr];
|
|
220
|
+
if (pendingConnection) {
|
|
221
|
+
_this.logger.warn('ClientConnectionManager', "Cleaning up stuck pending connection to " + addressStr);
|
|
222
|
+
pendingConnection.reject(new Error('Connection cleanup forced'));
|
|
223
|
+
delete _this.pendingConnections[addressStr];
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
this.logger.info('ClientConnectionManager', "Cleanup completed. Removed " + addressesToRemove.length + " dead connections");
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Gets an existing connection to a specific address if it exists
|
|
230
|
+
* @param address The address to check for existing connection
|
|
231
|
+
* @returns The existing connection or undefined if none exists
|
|
232
|
+
*/
|
|
233
|
+
ClientConnectionManager.prototype.getConnection = function (address) {
|
|
234
|
+
var addressStr = address.toString();
|
|
235
|
+
return this.establishedConnections[addressStr];
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Gets all established connections
|
|
239
|
+
* @returns Object containing all established connections
|
|
240
|
+
*/
|
|
241
|
+
ClientConnectionManager.prototype.getEstablishedConnections = function () {
|
|
242
|
+
return this.establishedConnections;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Gets all pending connections
|
|
246
|
+
* @returns Object containing all pending connections
|
|
247
|
+
*/
|
|
248
|
+
ClientConnectionManager.prototype.getPendingConnections = function () {
|
|
249
|
+
return this.pendingConnections;
|
|
250
|
+
};
|
|
163
251
|
/**
|
|
164
252
|
* Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
|
|
165
253
|
* it first connects to the address and then return the {@link ClientConnection}.
|
|
@@ -195,6 +283,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
195
283
|
this.pendingConnections[addressIndex] = connectionResolver;
|
|
196
284
|
this.retryConnection(address, asOwner)
|
|
197
285
|
.then(function (clientConnection) {
|
|
286
|
+
// Safety check: ensure we got a proper ClientConnection
|
|
287
|
+
if (!clientConnection || typeof clientConnection.getAddress !== 'function') {
|
|
288
|
+
var error = new Error("Invalid connection object returned: " + typeof clientConnection);
|
|
289
|
+
_this.logger.error('ClientConnectionManager', error.message);
|
|
290
|
+
connectionResolver.reject(error);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
198
293
|
_this.establishedConnections[clientConnection.getAddress().toString()] = clientConnection;
|
|
199
294
|
_this.onConnectionOpened(clientConnection);
|
|
200
295
|
connectionResolver.resolve(clientConnection);
|
|
@@ -243,7 +338,7 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
243
338
|
};
|
|
244
339
|
/**
|
|
245
340
|
* Cleans up all connections to a specific address during failover
|
|
246
|
-
* @param address
|
|
341
|
+
* @param address The address to cleanup
|
|
247
342
|
*/
|
|
248
343
|
ClientConnectionManager.prototype.cleanupConnectionsForFailover = function (address) {
|
|
249
344
|
var addressStr = address.toString();
|
|
@@ -253,6 +348,27 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
253
348
|
// Remove from failed connections to allow reconnection after failover
|
|
254
349
|
this.failedConnections.delete(addressStr);
|
|
255
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
|
+
};
|
|
256
372
|
ClientConnectionManager.prototype.shutdown = function () {
|
|
257
373
|
if (this.connectionHealthCheckInterval) {
|
|
258
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();
|
|
@@ -98,7 +101,15 @@ export declare class ClusterService {
|
|
|
98
101
|
private handleMemberAttributeChange(uuid, key, operationType, value);
|
|
99
102
|
private memberAdded(member);
|
|
100
103
|
private memberRemoved(member);
|
|
104
|
+
/**
|
|
105
|
+
* Logs the current state for debugging purposes
|
|
106
|
+
*/
|
|
107
|
+
private logCurrentState();
|
|
101
108
|
private startReconnectionTask();
|
|
109
|
+
/**
|
|
110
|
+
* Starts a periodic task to log the current state for debugging
|
|
111
|
+
*/
|
|
112
|
+
private startStateLoggingTask();
|
|
102
113
|
private attemptReconnectionToFailedNodes();
|
|
103
114
|
/**
|
|
104
115
|
* Attempts to establish a connection to a specific address
|
|
@@ -50,13 +50,14 @@ 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 =
|
|
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;
|
|
57
57
|
this.logger = this.client.getLoggingService().getLogger();
|
|
58
58
|
this.members = [];
|
|
59
59
|
this.startReconnectionTask();
|
|
60
|
+
this.startStateLoggingTask();
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Starts cluster service.
|
|
@@ -102,6 +103,12 @@ var ClusterService = /** @class */ (function () {
|
|
|
102
103
|
return Array.from(new Set(Array.from(addresses).concat(Array.from(providerAddresses))));
|
|
103
104
|
});
|
|
104
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Returns the owner connection if available
|
|
108
|
+
*/
|
|
109
|
+
ClusterService.prototype.getOwnerConnection = function () {
|
|
110
|
+
return this.ownerConnection;
|
|
111
|
+
};
|
|
105
112
|
/**
|
|
106
113
|
* Returns the list of members in the cluster.
|
|
107
114
|
* @returns
|
|
@@ -143,16 +150,15 @@ var ClusterService = /** @class */ (function () {
|
|
|
143
150
|
ClusterService.prototype.getClientInfo = function () {
|
|
144
151
|
var info = new ClientInfo_1.ClientInfo();
|
|
145
152
|
info.uuid = this.uuid;
|
|
146
|
-
|
|
153
|
+
var ownerConnection = this.getOwnerConnection();
|
|
154
|
+
if (ownerConnection) {
|
|
155
|
+
info.localAddress = ownerConnection.getLocalAddress();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
info.localAddress = null;
|
|
159
|
+
}
|
|
147
160
|
return info;
|
|
148
161
|
};
|
|
149
|
-
/**
|
|
150
|
-
* Returns the connection associated with owner node of this client.
|
|
151
|
-
* @returns {ClientConnection}
|
|
152
|
-
*/
|
|
153
|
-
ClusterService.prototype.getOwnerConnection = function () {
|
|
154
|
-
return this.ownerConnection;
|
|
155
|
-
};
|
|
156
162
|
/**
|
|
157
163
|
* Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
|
|
158
164
|
* so if you register the listener twice, it will get events twice.
|
|
@@ -184,7 +190,11 @@ var ClusterService = /** @class */ (function () {
|
|
|
184
190
|
var handleAttributeChange = _this.handleMemberAttributeChange.bind(_this);
|
|
185
191
|
ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.handle(m, handleMember, handleMemberList, handleAttributeChange, null);
|
|
186
192
|
};
|
|
187
|
-
|
|
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)
|
|
188
198
|
.then(function (resp) {
|
|
189
199
|
_this.logger.trace('ClusterService', 'Registered listener with id '
|
|
190
200
|
+ ClientAddMembershipListenerCodec_1.ClientAddMembershipListenerCodec.decodeResponse(resp).response);
|
|
@@ -227,22 +237,64 @@ var ClusterService = /** @class */ (function () {
|
|
|
227
237
|
this.failoverInProgress = true;
|
|
228
238
|
this.lastFailoverAttempt = now;
|
|
229
239
|
this.logger.info('ClusterService', 'Starting failover process...');
|
|
240
|
+
// Log state before failover
|
|
241
|
+
this.logCurrentState();
|
|
242
|
+
// Force cleanup of all dead connections to prevent leakage
|
|
243
|
+
this.client.getConnectionManager().forceCleanupDeadConnections();
|
|
230
244
|
// Clear any stale partition information
|
|
231
245
|
this.client.getPartitionService().clearPartitionTable();
|
|
232
246
|
// Attempt to reconnect to cluster
|
|
233
247
|
this.connectToCluster()
|
|
234
248
|
.then(function () {
|
|
235
249
|
_this.logger.info('ClusterService', 'Failover completed successfully');
|
|
250
|
+
_this.logCurrentState(); // Log state after successful failover
|
|
236
251
|
})
|
|
237
252
|
.catch(function (error) {
|
|
238
253
|
_this.logger.error('ClusterService', 'Failover failed', error);
|
|
239
|
-
// If failover fails,
|
|
240
|
-
_this.
|
|
254
|
+
// If failover fails, try to unblock at least one address to allow recovery
|
|
255
|
+
_this.attemptEmergencyRecovery();
|
|
256
|
+
_this.logCurrentState(); // Log state after failed failover
|
|
257
|
+
// Don't shutdown immediately, give recovery a chance
|
|
241
258
|
})
|
|
242
259
|
.finally(function () {
|
|
243
260
|
_this.failoverInProgress = false;
|
|
244
261
|
});
|
|
245
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
|
+
};
|
|
246
298
|
ClusterService.prototype.isAddressKnownDown = function (address) {
|
|
247
299
|
var addressStr = address.toString();
|
|
248
300
|
var downTime = this.downAddresses.get(addressStr);
|
|
@@ -349,6 +401,8 @@ var ClusterService = /** @class */ (function () {
|
|
|
349
401
|
this.members = members;
|
|
350
402
|
this.client.getPartitionService().refresh();
|
|
351
403
|
this.logger.info('ClusterService', 'Members received.', this.members);
|
|
404
|
+
// Log current state after member list update
|
|
405
|
+
this.logCurrentState();
|
|
352
406
|
var events = this.detectMembershipEvents(prevMembers);
|
|
353
407
|
for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
|
|
354
408
|
var event = events_1[_i];
|
|
@@ -416,10 +470,55 @@ var ClusterService = /** @class */ (function () {
|
|
|
416
470
|
var removedMemberList = this.members.splice(memberIndex, 1);
|
|
417
471
|
assert(removedMemberList.length === 1);
|
|
418
472
|
}
|
|
419
|
-
this
|
|
473
|
+
// Check if we have a healthy connection to this member
|
|
474
|
+
var connectionManager = this.client.getConnectionManager();
|
|
475
|
+
var existingConnection = connectionManager.getConnection(member.address);
|
|
476
|
+
if (existingConnection && existingConnection.isHealthy()) {
|
|
477
|
+
// If the connection is healthy, don't destroy it immediately
|
|
478
|
+
// This prevents unnecessary disconnections during temporary network issues
|
|
479
|
+
this.logger.info('ClusterService', "Member removed but connection is healthy: " + member.address.toString() + ", preserving connection");
|
|
480
|
+
// Only destroy if we're not in failover mode
|
|
481
|
+
if (!this.failoverInProgress) {
|
|
482
|
+
this.logger.debug('ClusterService', "Destroying healthy connection to removed member: " + member.address.toString());
|
|
483
|
+
connectionManager.destroyConnection(member.address);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
this.logger.debug('ClusterService', "Preserving healthy connection during failover: " + member.address.toString());
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
// If connection is unhealthy, destroy it
|
|
491
|
+
this.logger.info('ClusterService', "Member removed with unhealthy connection: " + member.address.toString() + ", destroying connection");
|
|
492
|
+
connectionManager.destroyConnection(member.address);
|
|
493
|
+
}
|
|
420
494
|
var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.REMOVED, this.members);
|
|
421
495
|
this.fireMembershipEvent(membershipEvent);
|
|
422
496
|
};
|
|
497
|
+
/**
|
|
498
|
+
* Logs the current state for debugging purposes
|
|
499
|
+
*/
|
|
500
|
+
ClusterService.prototype.logCurrentState = function () {
|
|
501
|
+
var _this = this;
|
|
502
|
+
var activeConnections = Object.keys(this.client.getConnectionManager().getEstablishedConnections()).length;
|
|
503
|
+
var memberCount = this.members.length;
|
|
504
|
+
var downAddressesCount = this.downAddresses.size;
|
|
505
|
+
var hasOwner = !!this.ownerConnection;
|
|
506
|
+
this.logger.info('ClusterService', "Current State - Members: " + memberCount + ", Active Connections: " + activeConnections + ", Down Addresses: " + downAddressesCount + ", Has Owner: " + hasOwner);
|
|
507
|
+
if (this.ownerConnection) {
|
|
508
|
+
this.logger.info('ClusterService', "Owner Connection: " + this.ownerConnection.getAddress().toString() + ", Alive: " + this.ownerConnection.isAlive());
|
|
509
|
+
}
|
|
510
|
+
// Log all active connections
|
|
511
|
+
var connections = this.client.getConnectionManager().getEstablishedConnections();
|
|
512
|
+
Object.keys(connections).forEach(function (addressStr) {
|
|
513
|
+
var connection = connections[addressStr];
|
|
514
|
+
_this.logger.debug('ClusterService', "Connection to " + addressStr + ": Alive=" + connection.isAlive() + ", Owner=" + connection.isAuthenticatedAsOwner());
|
|
515
|
+
});
|
|
516
|
+
// Log down addresses
|
|
517
|
+
if (downAddressesCount > 0) {
|
|
518
|
+
var downAddresses = Array.from(this.downAddresses.keys());
|
|
519
|
+
this.logger.debug('ClusterService', "Down Addresses: " + downAddresses.join(', '));
|
|
520
|
+
}
|
|
521
|
+
};
|
|
423
522
|
ClusterService.prototype.startReconnectionTask = function () {
|
|
424
523
|
var _this = this;
|
|
425
524
|
// Periodically attempt to reconnect to previously failed addresses
|
|
@@ -427,15 +526,29 @@ var ClusterService = /** @class */ (function () {
|
|
|
427
526
|
_this.attemptReconnectionToFailedNodes();
|
|
428
527
|
}, this.reconnectionInterval);
|
|
429
528
|
};
|
|
529
|
+
/**
|
|
530
|
+
* Starts a periodic task to log the current state for debugging
|
|
531
|
+
*/
|
|
532
|
+
ClusterService.prototype.startStateLoggingTask = function () {
|
|
533
|
+
var _this = this;
|
|
534
|
+
// Log state every 30 seconds for debugging
|
|
535
|
+
setInterval(function () {
|
|
536
|
+
if (_this.client.getLifecycleService().isRunning()) {
|
|
537
|
+
_this.logCurrentState();
|
|
538
|
+
}
|
|
539
|
+
}, 30000);
|
|
540
|
+
};
|
|
430
541
|
ClusterService.prototype.attemptReconnectionToFailedNodes = function () {
|
|
431
542
|
var _this = this;
|
|
432
|
-
|
|
543
|
+
// Allow reconnection even during failover, but be more careful
|
|
544
|
+
if (this.failoverInProgress) {
|
|
545
|
+
this.logger.debug('ClusterService', 'Skipping reconnection attempt during failover');
|
|
433
546
|
return;
|
|
434
547
|
}
|
|
435
548
|
var now = Date.now();
|
|
436
549
|
var addressesToReconnect = [];
|
|
437
550
|
var totalDownAddresses = this.downAddresses.size;
|
|
438
|
-
// If we have no down addresses, we can
|
|
551
|
+
// If we have no down addresses, we can skip
|
|
439
552
|
if (totalDownAddresses === 0) {
|
|
440
553
|
return;
|
|
441
554
|
}
|
|
@@ -449,6 +562,12 @@ var ClusterService = /** @class */ (function () {
|
|
|
449
562
|
var port = parseInt(portStr, 10);
|
|
450
563
|
if (host && !isNaN(port)) {
|
|
451
564
|
var address = new Address(host, port);
|
|
565
|
+
// Check if we already have a connection to this address
|
|
566
|
+
if (_this.client.getConnectionManager().hasConnection(address)) {
|
|
567
|
+
_this.logger.debug('ClusterService', "Already have active connection to " + addressStr + ", removing from down addresses");
|
|
568
|
+
_this.downAddresses.delete(addressStr);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
452
571
|
addressesToReconnect.push(address);
|
|
453
572
|
}
|
|
454
573
|
}
|
|
@@ -474,6 +593,8 @@ var ClusterService = /** @class */ (function () {
|
|
|
474
593
|
});
|
|
475
594
|
this.logger.debug('ClusterService', "Still waiting for " + totalDownAddresses + " addresses to unblock: " + remainingBlocked.join(', '));
|
|
476
595
|
}
|
|
596
|
+
// Log current state after reconnection attempts
|
|
597
|
+
this.logCurrentState();
|
|
477
598
|
};
|
|
478
599
|
/**
|
|
479
600
|
* Attempts to establish a connection to a specific address
|
|
@@ -482,6 +603,20 @@ var ClusterService = /** @class */ (function () {
|
|
|
482
603
|
ClusterService.prototype.attemptReconnectionToAddress = function (address) {
|
|
483
604
|
var _this = this;
|
|
484
605
|
var addressStr = address.toString();
|
|
606
|
+
// Check if we already have a connection to this address
|
|
607
|
+
if (this.client.getConnectionManager().hasConnection(address)) {
|
|
608
|
+
this.logger.debug('ClusterService', "Already have active connection to " + addressStr + ", skipping reconnection");
|
|
609
|
+
this.downAddresses.delete(addressStr);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
// Check if we're already trying to connect to this address
|
|
613
|
+
var connectionManager = this.client.getConnectionManager();
|
|
614
|
+
var establishedConnections = connectionManager.getEstablishedConnections();
|
|
615
|
+
var pendingConnections = Object.keys(connectionManager.getPendingConnections()).length;
|
|
616
|
+
if (pendingConnections > 0) {
|
|
617
|
+
this.logger.debug('ClusterService', "Already have pending connections, skipping reconnection to " + addressStr);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
485
620
|
// Remove from down addresses to allow connection attempt
|
|
486
621
|
this.downAddresses.delete(addressStr);
|
|
487
622
|
this.logger.debug('ClusterService', "Attempting reconnection to " + addressStr);
|
|
@@ -489,8 +624,14 @@ var ClusterService = /** @class */ (function () {
|
|
|
489
624
|
this.client.getConnectionManager().getOrConnect(address, false)
|
|
490
625
|
.then(function (connection) {
|
|
491
626
|
_this.logger.info('ClusterService', "Successfully reconnected to " + addressStr);
|
|
492
|
-
//
|
|
493
|
-
_this.
|
|
627
|
+
// Only evaluate ownership change if we don't have an owner or current owner is unhealthy
|
|
628
|
+
if (!_this.ownerConnection || !_this.ownerConnection.isHealthy()) {
|
|
629
|
+
_this.logger.info('ClusterService', "Evaluating ownership change for " + addressStr);
|
|
630
|
+
_this.evaluateOwnershipChange(address, connection);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
_this.logger.debug('ClusterService', "Keeping " + addressStr + " as member connection, current owner is healthy");
|
|
634
|
+
}
|
|
494
635
|
// Trigger partition service refresh to update routing information
|
|
495
636
|
_this.client.getPartitionService().refresh();
|
|
496
637
|
}).catch(function (error) {
|
|
@@ -518,12 +659,8 @@ var ClusterService = /** @class */ (function () {
|
|
|
518
659
|
this.promoteToOwner(connection, address);
|
|
519
660
|
return;
|
|
520
661
|
}
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
if (wasPreviousOwner) {
|
|
524
|
-
this.logger.debug('ClusterService', "Reconnected node " + address.toString() + " was previously known, monitoring for ownership opportunity");
|
|
525
|
-
// Don't switch ownership immediately, but keep the connection for potential future use
|
|
526
|
-
}
|
|
662
|
+
// Don't switch ownership if current owner is healthy
|
|
663
|
+
this.logger.debug('ClusterService', "Current owner is healthy, keeping " + address.toString() + " as member connection");
|
|
527
664
|
};
|
|
528
665
|
/**
|
|
529
666
|
* Promotes a connection to owner status
|
|
@@ -559,6 +696,18 @@ var ClusterService = /** @class */ (function () {
|
|
|
559
696
|
var _this = this;
|
|
560
697
|
var addressStr = address.toString();
|
|
561
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
|
+
}
|
|
562
711
|
this.downAddresses.set(addressStr, now);
|
|
563
712
|
this.logger.warn('ClusterService', "Marked address " + addressStr + " as down, will be blocked for " + blockDuration + "ms");
|
|
564
713
|
// Schedule cleanup of this address after block duration
|
|
@@ -289,6 +289,10 @@ var InvocationService = /** @class */ (function () {
|
|
|
289
289
|
throw new Error("Still no partition owner for partition " + partitionId + " after refresh");
|
|
290
290
|
}
|
|
291
291
|
return _this.invokeOnAddress(invocation, newOwnerAddress);
|
|
292
|
+
}).catch(function (error) {
|
|
293
|
+
_this.logger.error('InvocationService', "Failed to refresh partition table for partition " + partitionId + ":", error);
|
|
294
|
+
// If partition refresh fails, reject the invocation instead of hanging
|
|
295
|
+
throw new Error("Cannot find partition owner for partition " + partitionId + ": " + error.message);
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
return this.client.getConnectionManager().getOrConnect(ownerAddress).then(function (connection) {
|
|
@@ -301,6 +305,10 @@ var InvocationService = /** @class */ (function () {
|
|
|
301
305
|
return _this.client.getPartitionService().refresh().then(function () {
|
|
302
306
|
// Retry the invocation with updated partition information
|
|
303
307
|
return _this.doInvoke(invocation);
|
|
308
|
+
}).catch(function (refreshError) {
|
|
309
|
+
_this.logger.error('InvocationService', "Failed to refresh partition table after partition owner failure:", refreshError);
|
|
310
|
+
// If refresh fails, reject the invocation instead of hanging
|
|
311
|
+
throw new Error("Partition owner " + ownerAddress.toString() + " unavailable and partition refresh failed: " + refreshError.message);
|
|
304
312
|
});
|
|
305
313
|
}
|
|
306
314
|
throw new HazelcastError_1.IOError(ownerAddress.toString() + '(partition owner) is not available.', e);
|
|
@@ -130,20 +130,32 @@ var ProxyManager = /** @class */ (function () {
|
|
|
130
130
|
};
|
|
131
131
|
ProxyManager.prototype.findNextAddress = function () {
|
|
132
132
|
var members = this.client.getClusterService().getMembers();
|
|
133
|
+
// If no members available, return null but log the issue
|
|
134
|
+
if (!members || members.length === 0) {
|
|
135
|
+
this.logger.warn('ProxyManager', 'No cluster members available for proxy creation');
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
133
138
|
var liteMember = null;
|
|
139
|
+
var dataMember = null;
|
|
134
140
|
for (var _i = 0, members_1 = members; _i < members_1.length; _i++) {
|
|
135
141
|
var member = members_1[_i];
|
|
136
142
|
if (member != null && member.isLiteMember === false) {
|
|
137
|
-
|
|
143
|
+
dataMember = member;
|
|
144
|
+
break; // Prefer data members
|
|
138
145
|
}
|
|
139
146
|
else if (member != null && member.isLiteMember) {
|
|
140
147
|
liteMember = member;
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
|
-
if
|
|
150
|
+
// Return data member if available, otherwise lite member, otherwise null
|
|
151
|
+
if (dataMember != null) {
|
|
152
|
+
return dataMember.address;
|
|
153
|
+
}
|
|
154
|
+
else if (liteMember != null) {
|
|
144
155
|
return liteMember.address;
|
|
145
156
|
}
|
|
146
157
|
else {
|
|
158
|
+
this.logger.warn('ProxyManager', 'No valid members found for proxy creation');
|
|
147
159
|
return null;
|
|
148
160
|
}
|
|
149
161
|
};
|
|
@@ -151,6 +163,12 @@ var ProxyManager = /** @class */ (function () {
|
|
|
151
163
|
var _this = this;
|
|
152
164
|
if (Date.now() <= deadline) {
|
|
153
165
|
var address = this.findNextAddress();
|
|
166
|
+
if (!address) {
|
|
167
|
+
var error = new Error('No cluster members available for proxy creation: ' + proxyObject.getName());
|
|
168
|
+
this.logger.error('ProxyManager', error.message);
|
|
169
|
+
promise.reject(error);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
154
172
|
var request = ClientCreateProxyCodec_1.ClientCreateProxyCodec.encodeRequest(proxyObject.getName(), proxyObject.getServiceName(), address);
|
|
155
173
|
var invocation = new InvocationService_1.Invocation(this.client, request);
|
|
156
174
|
invocation.address = address;
|
|
@@ -160,10 +178,13 @@ var ProxyManager = /** @class */ (function () {
|
|
|
160
178
|
if (_this.isRetryable(error)) {
|
|
161
179
|
_this.logger.warn('ProxyManager', 'Create proxy request for ' + proxyObject.getName() +
|
|
162
180
|
' failed. Retrying in ' + _this.invocationRetryPauseMillis + 'ms. ' + error);
|
|
163
|
-
setTimeout(
|
|
181
|
+
setTimeout(function () {
|
|
182
|
+
_this.initializeProxy(proxyObject, promise, deadline);
|
|
183
|
+
}, _this.invocationRetryPauseMillis);
|
|
164
184
|
}
|
|
165
185
|
else {
|
|
166
|
-
_this.logger.
|
|
186
|
+
_this.logger.error('ProxyManager', 'Create proxy request for ' + proxyObject.getName() + ' failed ' + error);
|
|
187
|
+
promise.reject(error);
|
|
167
188
|
}
|
|
168
189
|
});
|
|
169
190
|
}
|
package/package.json
CHANGED