Skip to content

TokenBucketRateLimiter.WaitAsync() does not immediately reduce the _queueCount on cancellation #64078

@halter73

Description

@halter73

Description

TokenBucketRateLimiter.WaitAsync(int, CancellationToken) still counts the requested tokens against the QueueLimit after they've been canceled by the CancellationToken after an incomplete ValueTask is returned. This is eventually fixed when the request is dequeued from the Deque<RequestRegistration> by ReplenishInternal, but that could be too late.

Reproduction Steps

var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 1,
    TimeSpan.Zero, 1, autoReplenishment: false));

limiter.Acquire(1);

var cts = new CancellationTokenSource();
var firstWaitTask = limiter.WaitAsync(1, cts.Token);

cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(() => firstWaitTask.AsTask());

// This incorrectly returns a failed lease immediately because ctc.Cancel() did not reduce the _queueCount.
var secondWaitTask = limiter.WaitAsync(1, cts.Token);
//Assert.False(secondWaitTask.IsCompleted)

Assert.True(limiter.TryReplenish());

// WaitAsync() no longer returns a failed lease because we called TryReplenish(), but that shouldn't be necessary
var thirdWaitTask = limiter.WaitAsync(1, cts.Token);
Assert.False(thirdWaitTask.IsCompleted)

Expected behavior

The secondWaitTask should initially be incomplete and later complete successfully when the TokenBucketRateLimiter is replenished.

Actual behavior

The secondWaitTask immediately returns a non-acquired lease because the QueueLimit has supposedly been exceeded (but not really).

Regression?

No.

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions