Search code examples
springspring-bootrabbitmqspring-amqp

Spring-amqp with empty queue name is awaiting_declaration


I want to create a queue with empty name so that the name can be generated by RabbitMQ -

var queue = QueueBuilder
    .durable("")
    .exclusive()
    .autoDelete().build

var binding = BindingBuilder.bind(queue).to(exchange).with(bindingKey).noargs();
Declarables d = new Declarables(queue, binding);

But then calling getActualName returns: spring.gen-vuiRwjOmRkihAE8C72rbmw_awaiting_declaration

d.getDeclarablesByType(Queue.class).get(0).getActualName();

while in rabbitMQ the name is: amq.gen-wpaYnybu9vOdD5v2ej66IQ

In spring-amqp core the Queue constructor declares:

    public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete,
            @Nullable Map<String, Object> arguments) {
    
        super(arguments);
        Assert.notNull(name, "'name' cannot be null");
        this.name = name;
        this.actualName = StringUtils.hasText(name) ? name
                : (Base64UrlNamingStrategy.DEFAULT.generateName() 
+ "_awaiting_declaration");
        this.durable = durable;
        this.exclusive = exclusive;
        this.autoDelete = autoDelete;
    }

Why is spring Queue using Base64UrlNamingStrategy and adding "awaiting_declaration" when we want the rabbitMQ name? How can we get the rabbitMQ name and not the spring generated name?

Queue defintion: https://github.com/spring-projects/spring-amqp/blob/d4e0f5c366a7ffae073f608c3766c82064cab3d1/spring-amqp/src/main/java/org/springframework/amqp/core/Queue.java#L98

Reason for this use-case is because of race-condition on queues: "When auto-delete or exclusive queues use well-known (static) names, in case of client disconnection and immediate reconnection there will be a natural race condition between RabbitMQ nodes that will delete such queues and recovering clients that will try to re-declare them. This can result in client-side connection recovery failure or exceptions, and create unnecessary confusion or affect application availability."

https://www.rabbitmq.com/queues.html#properties

Spring suggests using broker-based queues which can result in race condition: https://docs.spring.io/spring-amqp/docs/current/reference/html/#containers-and-broker-named-queues

EDIT: We are are not initiating the connection ourselves, but the admin bean initiates it after d.setAdminsThatShouldDelcare(admin)

    public Declarables someEventsDeclarables(
    @Qualifier("rabbitAdmin") RabbitAdmin admin,
    @Qualifier("AmqpExchange") Exchange exchange
) {
    final var bindingKey = somePrefix +".*." +someSuffix
    final var cfg = new OurEventsDeclarables(
        exchange,
        "", // no queue name - RabbitMq generates it
        bindingKey,
        true
    );

    final var declarables = cfg.declarables();
    for (Declarable d : declarables.getDeclarables()) {
        d.setAdminsThatShouldDeclare(admin);
        admin.declareQueue();
    }
    return declarables;
}

Running the integration-test which uses the queue results in

org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[spring.gen-QUh8ffN0TimELGG_kF1wFw_awaiting_declaration]

Edit-2 - working solution (in other tests Declarables and AutoAckConsumer might need to be @MockBean):

public class AmqpConfig{
...
@Bean("someEventsDeclarables")
public Declarables someEventsDeclarables(
@Qualifier("rabbitAdmin") RabbitAdmin admin,
@Qualifier("AmqpExchange") Exchange exchange
) {
final var bindingKey = somePrefix +".*." +someSuffix
final var cfg = new OurEventsDeclarables(
    exchange,
    "", // no queue name - RabbitMq generates it
    bindingKey,
    true
);

final var declarables = cfg.declarables();

/** declare queue and bindings */
final List<Queue> queues = declarables.getDeclarablesByType(Queue.class);
final List<Binding> bindings = declarables.getDeclarablesByType(Binding.class);
if (queues.size() == 0) {
    throw new BeanCreationException("Queue for empty-queue-name is not found");
}
if (bindings.size() == 0) {
    throw new BeanCreationException("Binding for {} is not found of empty-queue-name", bindingKey);
}
Queue queue = queues.get(0);
Binding binding = bindings.get(0);
String declareQueue = admin.declareQueue(queue);
queue.setActualName(declareQueue);
admin.declareBinding(binding);
return declarables;
}

Solution

  • You are using admin.declareQueue() (and discarding its result) instead of admin.declareQueue(Queue queue).

    The first method just declares a broker named queue; the update to the actual name requires the Queue object (so it can update the name).