|
1 | 1 | /*
|
2 |
| - * Copyright (C) 2005-2008 Jive Software, 2017-2024 Ignite Realtime Foundation. All rights reserved. |
| 2 | + * Copyright (C) 2005-2008 Jive Software, 2017-2025 Ignite Realtime Foundation. All rights reserved. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
51 | 51 | import java.net.UnknownHostException;
|
52 | 52 | import java.time.Duration;
|
53 | 53 | import java.util.*;
|
| 54 | +import java.util.concurrent.CompletableFuture; |
54 | 55 | import java.util.concurrent.ConcurrentHashMap;
|
55 | 56 | import java.util.concurrent.ConcurrentMap;
|
56 | 57 | import java.util.concurrent.atomic.AtomicInteger;
|
@@ -1332,107 +1333,131 @@ public Locale getLocaleForSession(JID address)
|
1332 | 1333 | return session.getLanguage();
|
1333 | 1334 | }
|
1334 | 1335 |
|
1335 |
| - private class ClientSessionListener implements ConnectionCloseListener { |
| 1336 | + private class ClientSessionListener implements ConnectionCloseListener |
| 1337 | + { |
1336 | 1338 | /**
|
1337 |
| - * Handle a session that just closed. |
| 1339 | + * Handle a client session that just closed. |
1338 | 1340 | *
|
1339 | 1341 | * @param handback The session that just closed
|
| 1342 | + * @return a Future representing pending completion of the event listener invocation. |
1340 | 1343 | */
|
1341 | 1344 | @Override
|
1342 |
| - public void onConnectionClose(Object handback) { |
1343 |
| - try { |
1344 |
| - LocalClientSession session = (LocalClientSession) handback; |
1345 |
| - if (session.isDetached()) { |
1346 |
| - Log.debug("Closing session with address {} and streamID {} is detached already.", session.getAddress(), session.getStreamID()); |
1347 |
| - return; |
1348 |
| - } |
1349 |
| - if (session.getStreamManager().getResume()) { |
1350 |
| - Log.debug("Closing session with address {} and streamID {} has SM enabled; detaching.", session.getAddress(), session.getStreamID()); |
1351 |
| - session.setDetached(); |
1352 |
| - return; |
1353 |
| - } else { |
1354 |
| - Log.debug("Closing session with address {} and streamID {} does not have SM enabled.", session.getAddress(), session.getStreamID()); |
1355 |
| - } |
1356 |
| - try { |
1357 |
| - if ((session.getPresence().isAvailable() || !session.wasAvailable()) && |
1358 |
| - routingTable.hasClientRoute(session.getAddress())) { |
1359 |
| - // Send an unavailable presence to the user's subscribers |
1360 |
| - // Note: This gives us a chance to send an unavailable presence to the |
1361 |
| - // entities that the user sent directed presences |
1362 |
| - Presence presence = new Presence(); |
1363 |
| - presence.setType(Presence.Type.unavailable); |
1364 |
| - presence.setFrom(session.getAddress()); |
1365 |
| - |
1366 |
| - // Broadcast asynchronously, to reduce the likelihood of the broadcast introducing a deadlock (OF-2921). |
1367 |
| - TaskEngine.getInstance().submit(() -> router.route(presence)); |
1368 |
| - } |
| 1345 | + public CompletableFuture<Void> onConnectionClosing(Object handback) |
| 1346 | + { |
| 1347 | + final LocalClientSession session = (LocalClientSession) handback; |
| 1348 | + if (session.isDetached()) { |
| 1349 | + Log.debug("Closing client session with address {} and streamID {} is detached already; this is a no-op.", session.getAddress(), session.getStreamID()); |
| 1350 | + return CompletableFuture.completedFuture(null); |
| 1351 | + } |
| 1352 | + if (session.getStreamManager().getResume()) { |
| 1353 | + Log.debug("Closing client session with address {} and streamID {} has SM enabled; detaching.", session.getAddress(), session.getStreamID()); |
| 1354 | + session.setDetached(); |
| 1355 | + return CompletableFuture.completedFuture(null); |
| 1356 | + } |
| 1357 | + |
| 1358 | + CompletableFuture<Void> result = CompletableFuture.runAsync(() -> Log.debug("Closing client session with address {} and streamID {} that does not have SM resume.", session.getAddress(), session.getStreamID())); |
| 1359 | + |
| 1360 | + if ((session.getPresence().isAvailable() || !session.wasAvailable()) && routingTable.hasClientRoute(session.getAddress())) { |
| 1361 | + // Send an unavailable presence to the user's subscribers. This gives us a chance to send an |
| 1362 | + // unavailable presence to the entities that the user sent directed presences |
| 1363 | + final Presence presence = new Presence(); |
| 1364 | + presence.setType(Presence.Type.unavailable); |
| 1365 | + presence.setFrom(session.getAddress()); |
| 1366 | + |
| 1367 | + result = result.thenRunAsync(() -> router.route(presence)); |
| 1368 | + } |
1369 | 1369 |
|
| 1370 | + // In the completion stage remove the session (which means it'll be removed no matter if the previous stage had exceptions). |
| 1371 | + return result.whenComplete((v,t) -> { |
| 1372 | + try { |
1370 | 1373 | session.getStreamManager().onClose(router, serverAddress);
|
1371 |
| - } |
1372 |
| - finally { |
1373 |
| - // Remove the session |
| 1374 | + } finally { |
| 1375 | + // Note that the session can't be removed before the unavailable presence has been sent (as session-provided data is used by the broadcast). |
1374 | 1376 | removeSession(session);
|
1375 | 1377 | }
|
1376 |
| - } |
1377 |
| - catch (Exception e) { |
1378 |
| - // Can't do anything about this problem... |
1379 |
| - Log.error(LocaleUtils.getLocalizedString("admin.error.close"), e); |
1380 |
| - } |
| 1378 | + }); |
1381 | 1379 | }
|
1382 | 1380 | }
|
1383 | 1381 |
|
1384 |
| - private class IncomingServerSessionListener implements ConnectionCloseListener { |
| 1382 | + private class IncomingServerSessionListener implements ConnectionCloseListener |
| 1383 | + { |
1385 | 1384 | /**
|
1386 |
| - * Handle a session that just closed. |
| 1385 | + * Handle an incoming server-to-server session that just closed. |
1387 | 1386 | *
|
1388 | 1387 | * @param handback The session that just closed
|
| 1388 | + * @return a Future representing pending completion of the event listener invocation. |
1389 | 1389 | */
|
1390 | 1390 | @Override
|
1391 |
| - public void onConnectionClose(Object handback) { |
1392 |
| - LocalIncomingServerSession session = (LocalIncomingServerSession)handback; |
| 1391 | + public CompletableFuture<Void> onConnectionClosing(Object handback) |
| 1392 | + { |
| 1393 | + final LocalIncomingServerSession session = (LocalIncomingServerSession)handback; |
| 1394 | + |
| 1395 | + CompletableFuture<Void> result = CompletableFuture.runAsync(() -> Log.debug("Closing incoming server session with address {} and streamID {}.", session.getAddress(), session.getStreamID())); |
| 1396 | + |
1393 | 1397 | // Remove all the domains that were registered for this server session.
|
| 1398 | + final Collection<CompletableFuture<Void>> tasks = new ArrayList<>(); |
1394 | 1399 | for (String domain : session.getValidatedDomains()) {
|
1395 |
| - unregisterIncomingServerSession(domain, session); |
| 1400 | + tasks.add(CompletableFuture.runAsync(() -> unregisterIncomingServerSession(domain, session))); |
1396 | 1401 | }
|
| 1402 | + |
| 1403 | + return result.thenCompose(e -> CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0]))); |
1397 | 1404 | }
|
1398 | 1405 | }
|
1399 | 1406 |
|
1400 |
| - private class OutgoingServerSessionListener implements ConnectionCloseListener { |
| 1407 | + private class OutgoingServerSessionListener implements ConnectionCloseListener |
| 1408 | + { |
1401 | 1409 | /**
|
1402 |
| - * Handle a session that just closed. |
| 1410 | + * Handle an outgoing server-to-server session that just closed. |
1403 | 1411 | *
|
1404 | 1412 | * @param handback The session that just closed
|
| 1413 | + * @return a Future representing pending completion of the event listener invocation. |
1405 | 1414 | */
|
1406 | 1415 | @Override
|
1407 |
| - public void onConnectionClose(Object handback) { |
1408 |
| - OutgoingServerSession session = (OutgoingServerSession)handback; |
| 1416 | + public CompletableFuture<Void> onConnectionClosing(Object handback) |
| 1417 | + { |
| 1418 | + final OutgoingServerSession session = (OutgoingServerSession)handback; |
| 1419 | + |
| 1420 | + CompletableFuture<Void> result = CompletableFuture.runAsync(() -> Log.debug("Closing outgoing server session with address {} and streamID {}.", session.getAddress(), session.getStreamID())); |
| 1421 | + |
1409 | 1422 | // Remove all the domains that were registered for this server session.
|
| 1423 | + final Collection<CompletableFuture<Void>> tasks = new ArrayList<>(); |
1410 | 1424 | for (DomainPair domainPair : session.getOutgoingDomainPairs()) {
|
1411 |
| - // Remove the route to the session using the domain. |
1412 |
| - server.getRoutingTable().removeServerRoute(domainPair); |
| 1425 | + tasks.add(CompletableFuture.runAsync(() -> server.getRoutingTable().removeServerRoute(domainPair))); |
1413 | 1426 | }
|
| 1427 | + |
| 1428 | + return result.thenCompose(e -> CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0]))); |
1414 | 1429 | }
|
1415 | 1430 | }
|
1416 | 1431 |
|
1417 |
| - private class ConnectionMultiplexerSessionListener implements ConnectionCloseListener { |
| 1432 | + private class ConnectionMultiplexerSessionListener implements ConnectionCloseListener |
| 1433 | + { |
1418 | 1434 | /**
|
1419 |
| - * Handle a session that just closed. |
| 1435 | + * Handle a multiplexer session that just closed. |
1420 | 1436 | *
|
1421 | 1437 | * @param handback The session that just closed
|
| 1438 | + * @return a Future representing pending completion of the event listener invocation. |
1422 | 1439 | */
|
1423 | 1440 | @Override
|
1424 |
| - public void onConnectionClose(Object handback) { |
1425 |
| - ConnectionMultiplexerSession session = (ConnectionMultiplexerSession)handback; |
| 1441 | + public CompletableFuture<Void> onConnectionClosing(Object handback) |
| 1442 | + { |
| 1443 | + final ConnectionMultiplexerSession session = (ConnectionMultiplexerSession)handback; |
| 1444 | + final String domain = session.getAddress().getDomain(); |
| 1445 | + |
| 1446 | + CompletableFuture<Void> result = CompletableFuture.runAsync(() -> Log.debug("Closing multiplexer session with address {} and streamID {}.", session.getAddress(), session.getStreamID())); |
| 1447 | + |
1426 | 1448 | // Remove all the domains that were registered for this server session
|
1427 |
| - String domain = session.getAddress().getDomain(); |
1428 |
| - localSessionManager.getConnnectionManagerSessions().remove(session.getAddress().toString()); |
| 1449 | + result = result.thenRunAsync(() -> localSessionManager.getConnnectionManagerSessions().remove(session.getAddress().toString())); |
| 1450 | + |
1429 | 1451 | // Remove track of the cluster node hosting the CM connection
|
1430 |
| - multiplexerSessionsCache.remove(session.getAddress().toString()); |
| 1452 | + result = result.thenRunAsync(() -> multiplexerSessionsCache.remove(session.getAddress().toString())); |
| 1453 | + |
1431 | 1454 | if (getConnectionMultiplexerSessions(domain).isEmpty()) {
|
1432 | 1455 | // Terminate ClientSessions originated from this connection manager
|
1433 | 1456 | // that are still active since the connection manager has gone down
|
1434 |
| - ConnectionMultiplexerManager.getInstance().multiplexerUnavailable(domain); |
| 1457 | + result = result.thenRunAsync(() -> ConnectionMultiplexerManager.getInstance().multiplexerUnavailable(domain)); |
1435 | 1458 | }
|
| 1459 | + |
| 1460 | + return result; |
1436 | 1461 | }
|
1437 | 1462 | }
|
1438 | 1463 |
|
|
0 commit comments