Skip to content

Commit 4115627

Browse files
authored
Ensure connection info is processed before durable sync (#2048)
This update waits until the ConnectionInfo command is processed by the entire broker chain without error before sending the BrokerSubscriptionInfo command for durable sync back to a remote broker requesting it
1 parent 9c5639d commit 4115627

4 files changed

Lines changed: 431 additions & 86 deletions

File tree

activemq-broker/src/main/java/org/apache/activemq/broker/TransportConnection.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Properties;
29+
import java.util.concurrent.CompletableFuture;
2930
import java.util.concurrent.ConcurrentHashMap;
3031
import java.util.concurrent.CopyOnWriteArrayList;
3132
import java.util.concurrent.CountDownLatch;
@@ -164,6 +165,7 @@ public class TransportConnection implements Connection, Task, CommandVisitor {
164165
private final ReentrantReadWriteLock serviceLock = new ReentrantReadWriteLock();
165166
private String duplexNetworkConnectorId;
166167
private final long connectedTimestamp;
168+
private final CompletableFuture<ConnectionId> initialConnectionId = new CompletableFuture<>();
167169

168170
/**
169171
* @param taskRunnerFactory - can be null if you want direct dispatch to the transport
@@ -852,11 +854,16 @@ public Response processAddConnection(ConnectionInfo info) throws Exception {
852854

853855
try {
854856
broker.addConnection(context, info);
857+
// Complete the future with the connectionId if we completed
858+
// the broker.addConnection() chain successfully
859+
initialConnectionId.complete(info.getConnectionId());
855860
} catch (Exception e) {
856861
synchronized (brokerConnectionStates) {
857862
brokerConnectionStates.remove(info.getConnectionId());
858863
}
859864
unregisterConnectionState(info.getConnectionId());
865+
// complete with the exception
866+
initialConnectionId.completeExceptionally(e);
860867
LOG.warn("Failed to add Connection id={}, clientId={}, clientIP={} due to {}",
861868
info.getConnectionId(), clientId, info.getClientIp(), e.getLocalizedMessage());
862869
//AMQ-6561 - stop for all exceptions on addConnection
@@ -1390,13 +1397,10 @@ public Response processBrokerInfo(BrokerInfo info) {
13901397
LOG.error(" Slave Brokers are no longer supported - slave trying to attach is: {}", info.getBrokerName());
13911398
} else if (info.isNetworkConnection() && !info.isDuplexConnection()) {
13921399
try {
1393-
NetworkBridgeConfiguration config = getNetworkConfiguration(info);
1394-
if (config.isSyncDurableSubs() && protocolVersion.get() >= CommandTypes.PROTOCOL_VERSION_DURABLE_SYNC) {
1395-
LOG.debug("SyncDurableSubs is enabled, Sending BrokerSubscriptionInfo");
1396-
dispatchSync(NetworkBridgeUtils.getBrokerSubscriptionInfo(this.broker.getBrokerService(), config));
1397-
}
1400+
// register durable sync to be sent after ConnectionInfo has been handled
1401+
registerDurableSync(getNetworkConfiguration(info), info);
13981402
} catch (Exception e) {
1399-
LOG.error("Failed to respond to network bridge creation from broker {}", info.getBrokerId(), e);
1403+
LOG.error("Failed to register durable sync for network bridge creation from broker {}", info.getBrokerId(), e);
14001404
return null;
14011405
}
14021406
} else if (info.isNetworkConnection() && info.isDuplexConnection()) {
@@ -1406,10 +1410,8 @@ public Response processBrokerInfo(BrokerInfo info) {
14061410
NetworkBridgeConfiguration config = getNetworkConfiguration(info);
14071411
config.setBrokerName(broker.getBrokerName());
14081412

1409-
if (config.isSyncDurableSubs() && protocolVersion.get() >= CommandTypes.PROTOCOL_VERSION_DURABLE_SYNC) {
1410-
LOG.debug("SyncDurableSubs is enabled, Sending BrokerSubscriptionInfo");
1411-
dispatchSync(NetworkBridgeUtils.getBrokerSubscriptionInfo(this.broker.getBrokerService(), config));
1412-
}
1413+
// register durable sync to be sent after ConnectionInfo has been handled
1414+
registerDurableSync(config, info);
14131415

14141416
// check for existing duplex connection hanging about
14151417

@@ -1475,6 +1477,30 @@ public Response processBrokerInfo(BrokerInfo info) {
14751477
return null;
14761478
}
14771479

1480+
private void registerDurableSync(final NetworkBridgeConfiguration config, final BrokerInfo info) {
1481+
if (config.isSyncDurableSubs() && protocolVersion.get() >= CommandTypes.PROTOCOL_VERSION_DURABLE_SYNC) {
1482+
// this will complete when the connection id has been set, or immediately if already set
1483+
initialConnectionId.whenComplete((connectionId, t) -> {
1484+
try {
1485+
if (t != null) {
1486+
LOG.warn("SyncDurableSubs will be skipped due to error {}",
1487+
t.getMessage());
1488+
return;
1489+
}
1490+
// check connection still registered
1491+
if (lookupConnectionState(connectionId) != null) {
1492+
LOG.debug("SyncDurableSubs is enabled, Sending BrokerSubscriptionInfo");
1493+
dispatchSync(NetworkBridgeUtils.getBrokerSubscriptionInfo(
1494+
this.broker.getBrokerService(), config));
1495+
}
1496+
} catch (Exception e) {
1497+
LOG.error("Failed to respond to network bridge creation from broker {}",
1498+
info.getBrokerId(), e);
1499+
}
1500+
});
1501+
}
1502+
}
1503+
14781504
@SuppressWarnings({"unchecked", "rawtypes"})
14791505
private HashMap<String, String> createMap(Properties properties) {
14801506
return new HashMap(properties);
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.activemq.network;
18+
19+
import static org.junit.Assert.assertTrue;
20+
21+
import java.io.File;
22+
import java.io.IOException;
23+
import java.util.List;
24+
import java.util.concurrent.TimeUnit;
25+
import org.apache.activemq.broker.BrokerService;
26+
import org.apache.activemq.util.Wait;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
public abstract class AbstractDurableSyncNetworkBridgeTest extends DynamicNetworkTestSupport {
31+
32+
protected static final Logger LOG = LoggerFactory.getLogger(
33+
AbstractDurableSyncNetworkBridgeTest.class);
34+
35+
protected abstract void doSetUpLocalBroker(boolean deleteAllMessages, boolean startNetworkConnector, File dataDir) throws Exception;
36+
37+
protected abstract void doSetUpRemoteBroker(boolean deleteAllMessages, File dataDir, int port) throws Exception;
38+
39+
protected void restartLocalBroker(boolean startNetworkConnector) throws Exception {
40+
stopLocalBroker();
41+
doSetUpLocalBroker(false, startNetworkConnector, localBroker.getDataDirectoryFile());
42+
}
43+
44+
protected void restartRemoteBroker() throws Exception {
45+
final int previousPort = remoteBroker.getTransportConnectors().get(0).getConnectUri().getPort();
46+
final File dataDir = remoteBroker.getDataDirectoryFile();
47+
stopRemoteBroker();
48+
try {
49+
doSetUpRemoteBroker(false, dataDir, previousPort);
50+
} catch (final IOException e) {
51+
if (e.getCause() instanceof java.net.BindException) {
52+
// Previous port still in TIME_WAIT — use a new ephemeral port
53+
doSetUpRemoteBroker(false, dataDir, 0);
54+
// Update the local broker's network connector to point to the new port
55+
updateLocalNetworkConnectorUri();
56+
} else {
57+
throw e;
58+
}
59+
}
60+
}
61+
62+
protected void restartBroker(BrokerService broker, boolean startNetworkConnector) throws Exception {
63+
if (broker.getBrokerName().equals("localBroker")) {
64+
restartLocalBroker(startNetworkConnector);
65+
} else {
66+
restartRemoteBroker();
67+
}
68+
}
69+
70+
protected void waitForBridgeFullyStarted() throws Exception {
71+
waitForBridgeFullyStarted(TimeUnit.SECONDS.toMillis(15), true);
72+
}
73+
74+
protected void waitForBridgeFullyStarted(long millis, boolean duplex) throws Exception {
75+
// Wait for the local bridge to be fully started (advisory consumers registered)
76+
assertTrue("Local bridge should be fully started", Wait.waitFor(() -> {
77+
if (localBroker.getNetworkConnectors().get(0).activeBridges().isEmpty()) {
78+
return false;
79+
}
80+
final NetworkBridge bridge = localBroker.getNetworkConnectors().get(0).activeBridges().iterator().next();
81+
if (bridge instanceof DemandForwardingBridgeSupport) {
82+
return ((DemandForwardingBridgeSupport) bridge).startedLatch.getCount() == 0;
83+
}
84+
return true;
85+
}, millis, 100));
86+
87+
// Also wait for the duplex bridge on the remote broker to be fully started.
88+
// The duplex connector creates a separate DemandForwardingBridge on the remote side
89+
// that also needs its advisory consumers registered before it can process events.
90+
if (duplex) {
91+
assertTrue("Duplex bridge should be fully started", Wait.waitFor(() -> {
92+
final DemandForwardingBridge duplexBridge = findDuplexBridge(
93+
remoteBroker.getTransportConnectors().get(0));
94+
return duplexBridge != null && duplexBridge.startedLatch.getCount() == 0;
95+
}, millis, 100));
96+
}
97+
}
98+
99+
100+
/**
101+
* When the remote broker restarts on a new ephemeral port (BindException fallback),
102+
* any existing network connector on the local broker still points to the old port.
103+
* This method stops the old connector and replaces it with one targeting the new URI.
104+
*/
105+
protected void updateLocalNetworkConnectorUri() throws Exception {
106+
if (localBroker == null) {
107+
return;
108+
}
109+
final List<NetworkConnector> connectors = localBroker.getNetworkConnectors();
110+
if (connectors.isEmpty()) {
111+
return;
112+
}
113+
final NetworkConnector oldConnector = connectors.get(0);
114+
oldConnector.stop();
115+
localBroker.removeNetworkConnector(oldConnector);
116+
final NetworkConnector newConnector = configureLocalNetworkConnector();
117+
localBroker.addNetworkConnector(newConnector);
118+
newConnector.start();
119+
}
120+
121+
protected abstract NetworkConnector configureLocalNetworkConnector() throws Exception;
122+
123+
}

0 commit comments

Comments
 (0)