7777import java .lang .invoke .MethodHandles ;
7878import java .util .function .BiPredicate ;
7979
80+ import static org .apache .activemq .artemis .api .core .ActiveMQExceptionType .DISCONNECTED ;
81+
8082public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal , ClientConnectionLifeCycleListener {
8183
8284 private static final Logger logger = LoggerFactory .getLogger (MethodHandles .lookup ().lookupClass ());
@@ -93,6 +95,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
9395
9496 private volatile TransportConfiguration backupConnectorConfig ;
9597
98+ private TransportConfiguration failbackConnectorConfig ;
99+
96100 private ConnectorFactory connectorFactory ;
97101
98102 private final long callTimeout ;
@@ -135,6 +139,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
135139
136140 private int failoverAttempts ;
137141
142+ private int failbackAttempts ;
143+
138144 private final Set <SessionFailureListener > listeners = new ConcurrentHashSet <>();
139145
140146 private final Set <FailoverEventListener > failoverListeners = new ConcurrentHashSet <>();
@@ -144,6 +150,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
144150 private Future <?> pingerFuture ;
145151 private PingRunnable pingRunnable ;
146152
153+ private FailbackRunnable failbackRunnable ;
154+
147155 private final List <Interceptor > incomingInterceptors ;
148156
149157 private final List <Interceptor > outgoingInterceptors ;
@@ -244,6 +252,8 @@ public ClientSessionFactoryImpl(final ServerLocatorInternal serverLocator,
244252
245253 this .failoverAttempts = locatorConfig .failoverAttempts ;
246254
255+ this .failbackAttempts = locatorConfig .failbackAttempts ;
256+
247257 this .scheduledThreadPool = scheduledThreadPool ;
248258
249259 this .threadPool = threadPool ;
@@ -722,6 +732,12 @@ private void failoverOrReconnect(final Object connectionID,
722732 int connectorsCount = 0 ;
723733 int failoverRetries = 0 ;
724734 long failoverRetryInterval = retryInterval ;
735+
736+ //Save current connector config for failback purposes
737+ if (failbackAttempts != 0 && failbackConnectorConfig == null ) {
738+ failbackConnectorConfig = connectorConfig ;
739+ }
740+
725741 Pair <TransportConfiguration , TransportConfiguration > connectorPair ;
726742 BiPredicate <Boolean , Integer > failoverRetryPredicate =
727743 (reconnected , retries ) -> clientProtocolManager .isAlive () &&
@@ -815,6 +831,128 @@ private void failoverOrReconnect(final Object connectionID,
815831 }
816832 }
817833
834+ private void failback (final ActiveMQException me ,
835+ final TransportConfiguration previousConnectorConfig ) {
836+
837+ logger .debug ("Original node has come back online, performing failback now" );
838+
839+ for (ClientSessionInternal session : sessions ) {
840+ SessionContext context = session .getSessionContext ();
841+ if (context instanceof ActiveMQSessionContext ) {
842+ ActiveMQSessionContext sessionContext = (ActiveMQSessionContext ) context ;
843+ if (sessionContext .isKilled ()) {
844+ setReconnectAttempts (0 );
845+ }
846+ }
847+ }
848+
849+ Set <ClientSessionInternal > sessionsToClose = null ;
850+ if (!clientProtocolManager .isAlive ()) {
851+ return ;
852+ }
853+
854+ Lock localFailoverLock = lockFailover ();
855+
856+ try {
857+
858+ callFailoverListeners (FailoverEventType .FAILURE_DETECTED );
859+ callSessionFailureListeners (me , false , false , null );
860+
861+ if (clientProtocolManager .cleanupBeforeFailover (me )) {
862+
863+ RemotingConnection oldConnection = connection ;
864+
865+ connection = null ;
866+
867+ Connector localConnector = connector ;
868+ if (localConnector != null ) {
869+ try {
870+ localConnector .close ();
871+ } catch (Exception ignore ) {
872+ // no-op
873+ }
874+ }
875+
876+ cancelScheduledTasks ();
877+
878+ connector = null ;
879+
880+ HashSet <ClientSessionInternal > sessionsToFailover ;
881+ synchronized (sessions ) {
882+ sessionsToFailover = new HashSet <>(sessions );
883+ }
884+
885+ // Notify sessions before failover.
886+ for (ClientSessionInternal session : sessionsToFailover ) {
887+ session .preHandleFailover (connection );
888+ }
889+
890+ boolean sessionsReconnected = false ;
891+
892+ connectorConfig = previousConnectorConfig ;
893+ currentConnectorConfig = previousConnectorConfig ;
894+
895+ getConnection ();
896+
897+ if (connection != null ) {
898+ sessionsReconnected = reconnectSessions (sessionsToFailover , oldConnection , me );
899+
900+ if (!sessionsReconnected ) {
901+ if (oldConnection != null ) {
902+ oldConnection .destroy ();
903+ }
904+
905+ oldConnection = connection ;
906+ connection = null ;
907+ }
908+ }
909+
910+ // Notify sessions after failover.
911+ for (ClientSessionInternal session : sessionsToFailover ) {
912+ session .postHandleFailover (connection , sessionsReconnected );
913+ }
914+
915+ if (oldConnection != null ) {
916+ oldConnection .destroy ();
917+ }
918+
919+ if (connection != null ) {
920+ callFailoverListeners (FailoverEventType .FAILOVER_COMPLETED );
921+
922+ }
923+ }
924+
925+ if (connection == null ) {
926+ synchronized (sessions ) {
927+ sessionsToClose = new HashSet <>(sessions );
928+ }
929+ callFailoverListeners (FailoverEventType .FAILOVER_FAILED );
930+ callSessionFailureListeners (me , true , false , null );
931+ }
932+ } finally {
933+ localFailoverLock .unlock ();
934+ }
935+
936+ // This needs to be outside the failover lock to prevent deadlock
937+ if (connection != null ) {
938+ callSessionFailureListeners (me , true , true );
939+ }
940+
941+ if (sessionsToClose != null ) {
942+ // If connection is null it means we didn't succeed in failing over or reconnecting
943+ // so we close all the sessions, so they will throw exceptions when attempted to be used
944+
945+ for (ClientSessionInternal session : sessionsToClose ) {
946+ try {
947+ session .cleanUp (true );
948+ } catch (Exception cause ) {
949+ ActiveMQClientLogger .LOGGER .failedToCleanupSession (cause );
950+ }
951+ }
952+ }
953+
954+ }
955+
818956 private ClientSession createSessionInternal (final String rawUsername ,
819957 final String rawPassword ,
820958 final boolean xa ,
@@ -1018,6 +1156,10 @@ public boolean waitForRetry(long interval) {
10181156 return false ;
10191157 }
10201158
1159+ private long getRetryInterval () {
1160+ return retryInterval ;
1161+ }
1162+
10211163 private void cancelScheduledTasks () {
10221164 Future <?> pingerFutureLocal = pingerFuture ;
10231165 if (pingerFutureLocal != null ) {
@@ -1027,8 +1169,13 @@ private void cancelScheduledTasks() {
10271169 if (pingRunnableLocal != null ) {
10281170 pingRunnableLocal .cancel ();
10291171 }
1172+ FailbackRunnable failbackRunnableLocal = failbackRunnable ;
1173+ if (failbackRunnableLocal != null ) {
1174+ failbackRunnableLocal .cancel ();
1175+ }
10301176 pingerFuture = null ;
10311177 pingRunnable = null ;
1178+ failbackRunnable = null ;
10321179 }
10331180
10341181 private void checkCloseConnection () {
@@ -1492,6 +1639,68 @@ public synchronized void cancel() {
14921639 }
14931640 }
14941641
1642+ private void attemptFailback () {
1643+ if (failbackRunnable == null ) {
1644+ failbackRunnable = new FailbackRunnable ();
1645+ }
1646+ threadPool .execute (failbackRunnable );
1647+ }
1648+
1649+ private class FailbackRunnable implements Runnable {
1650+ private boolean first = true ;
1651+ private boolean cancelled ;
1652+
1653+ @ Override
1654+ public synchronized void run () {
1655+
1656+ if (!first ) {
1657+ return ;
1658+ }
1659+
1660+ first = false ;
1661+
1662+ logger .debug ("Attempting failback. Trying to reach {} for failback" , failbackConnectorConfig .toString ());
1663+
1664+ int attempts = 0 ;
1665+ long failbackRetryInterval = getRetryInterval ();
1666+
1667+ ConnectorFactory transportConnectorFactory ;
1668+ Connector transportConnector ;
1669+ Connection transportConnection ;
1670+
1671+ while (!cancelled && (failbackAttempts == -1 || attempts ++ < failbackAttempts )) {
1672+
1673+ waitForRetry (failbackRetryInterval );
1674+ failbackRetryInterval = getNextRetryInterval (failbackRetryInterval );
1675+
1676+ transportConnectorFactory = instantiateConnectorFactory (failbackConnectorConfig .getFactoryClassName ());
1677+ transportConnector = createConnector (transportConnectorFactory , failbackConnectorConfig );
1678+ transportConnection = openTransportConnection (transportConnector );
1679+
1680+ if (transportConnection != null ) {
1681+ transportConnector .close ();
1682+ transportConnection .close ();
1683+ ActiveMQException exception = new ActiveMQException ("Failing back to original broker: " + failbackConnectorConfig .toString (), DISCONNECTED );
1684+ failback (exception , failbackConnectorConfig );
1685+ break ;
1686+ }
1687+
1688+ }
1689+
1690+ if (failbackConnectorConfig .equals (currentConnectorConfig )) {
1691+ failbackConnectorConfig = null ;
1692+ }
1693+
1694+ first = true ;
1695+
1696+ }
1697+
1698+ public synchronized void cancel () {
1699+ cancelled = true ;
1700+ }
1701+
1702+ }
1703+
14951704 protected RemotingConnection establishNewConnection () {
14961705 Connection transportConnection = createTransportConnection ();
14971706
@@ -1572,6 +1781,13 @@ public void notifyNodeUp(long uniqueEventID,
15721781 boolean isLast ) {
15731782
15741783 try {
1784+
1785+ if (failbackConnectorConfig != null && connectorPair .getA () != null && TransportConfigurationUtil .isSameHost (connectorPair .getA (), failbackConnectorConfig )) {
1786+ if (!currentConnectorConfig .equals (failbackConnectorConfig ) && failbackRunnable == null ) {
1787+ attemptFailback ();
1788+ }
1789+ }
1790+
15751791 // if it is our connector then set the live id used for failover
15761792 if (connectorPair .getA () != null && TransportConfigurationUtil .isSameHost (connectorPair .getA (), currentConnectorConfig )) {
15771793 liveNodeID = nodeID ;
0 commit comments