ARTEMIS-5135 Use a sequence ID on address federation link names
Prevents unintended link stealing scenarios when link names match for a new link whose name is in use by another link still awaiting a detach response.
This commit is contained in:
parent
422446e033
commit
a3aea7d9a0
|
@ -37,6 +37,7 @@ import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@ -100,6 +101,11 @@ public class AMQPFederationAddressConsumer implements FederationConsumerInternal
|
||||||
private static final SimpleString MESSAGE_HOPS_ANNOTATION =
|
private static final SimpleString MESSAGE_HOPS_ANNOTATION =
|
||||||
SimpleString.of(AMQPFederationPolicySupport.MESSAGE_HOPS_ANNOTATION.toString());
|
SimpleString.of(AMQPFederationPolicySupport.MESSAGE_HOPS_ANNOTATION.toString());
|
||||||
|
|
||||||
|
// Sequence ID value used to keep links that would otherwise have the same name from overlapping
|
||||||
|
// this generally occurs when a remote link detach is delayed and new demand is added before it
|
||||||
|
// arrives resulting in an unintended link stealing scenario in the proton engine.
|
||||||
|
private static final AtomicLong LINK_SEQUENCE_ID = new AtomicLong();
|
||||||
|
|
||||||
private static final Symbol[] DEFAULT_OUTCOMES = new Symbol[]{Accepted.DESCRIPTOR_SYMBOL, Rejected.DESCRIPTOR_SYMBOL,
|
private static final Symbol[] DEFAULT_OUTCOMES = new Symbol[]{Accepted.DESCRIPTOR_SYMBOL, Rejected.DESCRIPTOR_SYMBOL,
|
||||||
Released.DESCRIPTOR_SYMBOL, Modified.DESCRIPTOR_SYMBOL};
|
Released.DESCRIPTOR_SYMBOL, Modified.DESCRIPTOR_SYMBOL};
|
||||||
|
|
||||||
|
@ -255,7 +261,8 @@ public class AMQPFederationAddressConsumer implements FederationConsumerInternal
|
||||||
private String generateLinkName() {
|
private String generateLinkName() {
|
||||||
return "federation-" + federation.getName() +
|
return "federation-" + federation.getName() +
|
||||||
"-address-receiver-" + consumerInfo.getAddress() +
|
"-address-receiver-" + consumerInfo.getAddress() +
|
||||||
"-" + federation.getServer().getNodeID();
|
"-" + federation.getServer().getNodeID() +
|
||||||
|
"-" + LINK_SEQUENCE_ID.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void asyncCreateReceiver() {
|
private void asyncCreateReceiver() {
|
||||||
|
|
|
@ -4185,6 +4185,104 @@ public class AMQPFederationAddressPolicyTest extends AmqpClientTestSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(20)
|
||||||
|
public void testNewFederationConsumerCreatedWhenDemandRemovedAndAddedWithDelayedPreviousDetach() throws Exception {
|
||||||
|
try (ProtonTestServer peer = new ProtonTestServer()) {
|
||||||
|
peer.expectSASLAnonymousConnect();
|
||||||
|
peer.expectOpen().respond();
|
||||||
|
peer.expectBegin().respond();
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withSenderSettleModeSettled()
|
||||||
|
.withSource().withDynamic(true)
|
||||||
|
.and()
|
||||||
|
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
|
||||||
|
.respondInKind()
|
||||||
|
.withTarget().withAddress("test-dynamic-events");
|
||||||
|
peer.expectFlow().withLinkCredit(10);
|
||||||
|
peer.start();
|
||||||
|
|
||||||
|
final URI remoteURI = peer.getServerURI();
|
||||||
|
logger.info("Test started, peer listening on: {}", remoteURI);
|
||||||
|
|
||||||
|
final AMQPFederationAddressPolicyElement receiveFromAddress = new AMQPFederationAddressPolicyElement();
|
||||||
|
receiveFromAddress.setName("address-policy");
|
||||||
|
receiveFromAddress.addToIncludes(getTestName());
|
||||||
|
|
||||||
|
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
|
||||||
|
element.setName(getTestName());
|
||||||
|
element.addLocalAddressPolicy(receiveFromAddress);
|
||||||
|
|
||||||
|
final AMQPBrokerConnectConfiguration amqpConnection =
|
||||||
|
new AMQPBrokerConnectConfiguration(getTestName(), "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
|
||||||
|
amqpConnection.setReconnectAttempts(0);// No reconnects
|
||||||
|
amqpConnection.addElement(element);
|
||||||
|
|
||||||
|
server.getConfiguration().addAMQPConnection(amqpConnection);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
|
||||||
|
.withName(allOf(containsString(getTestName()),
|
||||||
|
containsString("address-receiver"),
|
||||||
|
containsString(server.getNodeID().toString())))
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_ADDRESS_RECEIVER.toString());
|
||||||
|
peer.expectFlow().withLinkCredit(1000);
|
||||||
|
peer.expectDetach().respond().afterDelay(40); // Defer the detach response for a bit
|
||||||
|
|
||||||
|
server.addAddressInfo(new AddressInfo(SimpleString.of(getTestName()), RoutingType.MULTICAST));
|
||||||
|
|
||||||
|
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
|
||||||
|
|
||||||
|
// Create demand on the address which creates a federation consumer then let it close which
|
||||||
|
// should shut down that federation consumer.
|
||||||
|
try (Connection connection = factory.createConnection()) {
|
||||||
|
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
|
||||||
|
final MessageConsumer consumer = session.createConsumer(session.createTopic(getTestName()));
|
||||||
|
|
||||||
|
connection.start();
|
||||||
|
|
||||||
|
consumer.receiveNoWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
|
||||||
|
.withName(allOf(containsString(getTestName()),
|
||||||
|
containsString("address-receiver"),
|
||||||
|
containsString(server.getNodeID().toString())))
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_ADDRESS_RECEIVER.toString());
|
||||||
|
peer.expectFlow().withLinkCredit(1000);
|
||||||
|
peer.expectDetach().respond();
|
||||||
|
|
||||||
|
// Create demand on the address which creates a federation consumer again quickly which
|
||||||
|
// can trigger a new consumer before the previous one was fully closed with a Detach
|
||||||
|
// response and get stuck because it will steal the link in proton and not be treated
|
||||||
|
// as a new attach for this consumer.
|
||||||
|
try (Connection connection = factory.createConnection()) {
|
||||||
|
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
|
||||||
|
final MessageConsumer consumer = session.createConsumer(session.createTopic(getTestName()));
|
||||||
|
|
||||||
|
connection.start();
|
||||||
|
|
||||||
|
consumer.receiveNoWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectClose();
|
||||||
|
peer.remoteClose().now();
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void sendAddressAddedEvent(ProtonTestPeer peer, String address, int handle, int deliveryId) {
|
private static void sendAddressAddedEvent(ProtonTestPeer peer, String address, int handle, int deliveryId) {
|
||||||
final Map<String, Object> eventMap = new LinkedHashMap<>();
|
final Map<String, Object> eventMap = new LinkedHashMap<>();
|
||||||
eventMap.put(REQUESTED_ADDRESS_NAME, address);
|
eventMap.put(REQUESTED_ADDRESS_NAME, address);
|
||||||
|
|
|
@ -3995,6 +3995,102 @@ public class AMQPFederationQueuePolicyTest extends AmqpClientTestSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(20)
|
||||||
|
public void testNewFederationConsumerCreatedWhenDemandRemovedAndAddedWithDelayedPreviousDetach() throws Exception {
|
||||||
|
try (ProtonTestServer peer = new ProtonTestServer()) {
|
||||||
|
peer.expectSASLAnonymousConnect();
|
||||||
|
peer.expectOpen().respond();
|
||||||
|
peer.expectBegin().respond();
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withSenderSettleModeSettled()
|
||||||
|
.withSource().withDynamic(true)
|
||||||
|
.and()
|
||||||
|
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
|
||||||
|
.respondInKind()
|
||||||
|
.withTarget().withAddress("test-dynamic-events");
|
||||||
|
peer.expectFlow().withLinkCredit(10);
|
||||||
|
peer.start();
|
||||||
|
|
||||||
|
final URI remoteURI = peer.getServerURI();
|
||||||
|
logger.info("Test started, peer listening on: {}", remoteURI);
|
||||||
|
|
||||||
|
final AMQPFederationQueuePolicyElement receiveFromQueue = new AMQPFederationQueuePolicyElement();
|
||||||
|
receiveFromQueue.setName("queue-policy");
|
||||||
|
receiveFromQueue.addToIncludes(getTestName(), getTestName());
|
||||||
|
|
||||||
|
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
|
||||||
|
element.setName(getTestName());
|
||||||
|
element.addLocalQueuePolicy(receiveFromQueue);
|
||||||
|
|
||||||
|
final AMQPBrokerConnectConfiguration amqpConnection =
|
||||||
|
new AMQPBrokerConnectConfiguration(getTestName(), "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
|
||||||
|
amqpConnection.setReconnectAttempts(0);// No reconnects
|
||||||
|
amqpConnection.addElement(element);
|
||||||
|
|
||||||
|
server.getConfiguration().addAMQPConnection(amqpConnection);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
|
||||||
|
.withName(allOf(containsString(getTestName()),
|
||||||
|
containsString("queue-receiver"),
|
||||||
|
containsString(server.getNodeID().toString())))
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_QUEUE_RECEIVER.toString());
|
||||||
|
peer.expectFlow().withLinkCredit(1000);
|
||||||
|
peer.expectDetach().respond().afterDelay(40); // Defer the detach response for a bit
|
||||||
|
|
||||||
|
server.createQueue(QueueConfiguration.of(getTestName()).setRoutingType(RoutingType.ANYCAST)
|
||||||
|
.setAddress(getTestName())
|
||||||
|
.setAutoCreated(false));
|
||||||
|
|
||||||
|
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
|
||||||
|
|
||||||
|
// Create demand on the queue which creates a federation consumer then let it close which
|
||||||
|
// should shut down that federation consumer.
|
||||||
|
try (Connection connection = factory.createConnection()) {
|
||||||
|
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
|
||||||
|
final MessageConsumer consumer = session.createConsumer(session.createQueue(getTestName()));
|
||||||
|
|
||||||
|
connection.start();
|
||||||
|
|
||||||
|
consumer.receiveNoWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
|
||||||
|
.withName(allOf(containsString(getTestName()),
|
||||||
|
containsString("queue-receiver"),
|
||||||
|
containsString(server.getNodeID().toString())))
|
||||||
|
.respond()
|
||||||
|
.withOfferedCapabilities(FEDERATION_QUEUE_RECEIVER.toString());
|
||||||
|
peer.expectFlow().withLinkCredit(1000);
|
||||||
|
peer.expectDetach().respond();
|
||||||
|
|
||||||
|
try (Connection connection = factory.createConnection()) {
|
||||||
|
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
|
||||||
|
final MessageConsumer consumer = session.createConsumer(session.createQueue(getTestName()));
|
||||||
|
|
||||||
|
connection.start();
|
||||||
|
|
||||||
|
consumer.receiveNoWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectClose();
|
||||||
|
peer.remoteClose().now();
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void sendQueueAddedEvent(ProtonTestPeer peer, String address, String queue, int handle, int deliveryId) {
|
private static void sendQueueAddedEvent(ProtonTestPeer peer, String address, String queue, int handle, int deliveryId) {
|
||||||
final Map<String, Object> eventMap = new LinkedHashMap<>();
|
final Map<String, Object> eventMap = new LinkedHashMap<>();
|
||||||
eventMap.put(REQUESTED_ADDRESS_NAME, address);
|
eventMap.put(REQUESTED_ADDRESS_NAME, address);
|
||||||
|
|
Loading…
Reference in New Issue