Endre Moen
Endre Moen

Reputation: 784

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;
}

Upvotes: 0

Views: 662

Answers (2)

Gary Russell
Gary Russell

Reputation: 174554

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).

Upvotes: 1

Artem Bilan
Artem Bilan

Reputation: 121272

It is not clear what and where you do with that Queue object. You must have a RabbitAdmin bean in the application context. As well as all those Declarable instances has to be declared as beans as well. Then when application context starts, that RabbitAdmin takes all the Declarable beans and declares them against the broker. The further logic around Queue is like this:

DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
                            queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());
if (StringUtils.hasText(declareOk.getQueue())) {
    queue.setActualName(declareOk.getQueue());
}

Just creating variables for those types does not initiate any connection to the broker to perform objects creation and bindings between them.

Upvotes: 1

Related Questions