Skip to content

ShutdownEventArgs.CancellationToken on AsyncEventingBasicConsumer.ShutdownAsync always in IsCancellationRequested state #1888

@gsolic

Description

@gsolic

Describe the bug

It's unclear whether this is a bug or intended behavior, but the ShutdownEventArgs.CancellationToken on the AsyncEventingBasicConsumer.ShutdownAsync event is always in a canceled state (IsCancellationRequested = true) when, for example, the RabbitMQ server is shut down or the channel is closed using IChannelExtensions.CloseAsync.

This seems inconsistent with the documentation for the event's CancellationToken, which states:

It's important for your handler to pass this token along to any asynchronous or long-running synchronous operations...

The immediate cancellation makes it unclear how the token is meant to be used in practice.

Reproduction steps

Minimal Example:

[Test]
public async Task Test(CancellationToken ct = default)
{
    var connectionFactory = new ConnectionFactory
    {
        Endpoint = new AmqpTcpEndpoint(ContainerContext.GetRabbitMqUri()),
        UserName = ContainerContext.UserName,
        Password = ContainerContext.UserPassword,
    };

    await using var connection = await connectionFactory.CreateConnectionAsync(ct);
    await using var channel = await connection.CreateChannelAsync(cancellationToken: ct);

    var declareOk = await channel.QueueDeclareAsync(queueName, true, false, false, cancellationToken: ct);
    await channel.QueueBindAsync(queueName, exchangeName, "", cancellationToken: ct);

    await using var consumerChannel = await connection.CreateChannelAsync(cancellationToken: ct);
    var consumer = new AsyncEventingBasicConsumer(consumerChannel);
    
    consumer.ShutdownAsync += async (_, args) =>
    {
        // args.CancellationToken is always initially canceled?
        await CleanupAsync(args.CancellationToken);
    };
    
    var consumerTag = await consumerChannel.BasicConsumeAsync(queueName, true, consumer, cancellationToken: ct);
    await consumerChannel.CloseAsync(cancellationToken: ct); // close consumer channel or shutdown rabbitmq server
    await Task.Delay(TimeSpan.FromSeconds(10), ct);
}

private async Task CleanupAsync(CancellationToken ct)
{
    // do some cleanup ...
    await Task.Delay(TimeSpan.FromSeconds(1), ct);
}

Expected behavior

The CancellationToken should remain non-canceled when the ShutdownAsync handler is invoked, allowing the handler to propagate the token to any asynchronous or long-running operations as intended. Cancellation should only be requested if the shutdown process itself is being canceled externally.

Additional context

Version 7.2.0

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions