Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing disposed cancellation token registration after setting result on Rate Limiting #76783

Closed
AliKhalili opened this issue Oct 8, 2022 · 3 comments

Comments

@AliKhalili
Copy link
Contributor

System.Threading.RateLimiting limiter's algorithms are using an internal queue, for queueing async acquire permit requests.
There are some missing calls to disposed of queued requests cancellation token registration after setting their result.
In general, limiters try to dequeue one request from the queue and set the result of the request with a failed or successful lease.
For example, in the replenishments phase(ReplenishInternal), the limiter dequeues one request and, after setting its result, disposes of its cancellation token registration object(nextPendingRequest.CancellationTokenRegistration.Dispose();). also limiter followe same bahvior on Dispose method(next.CancellationTokenRegistration.Dispose();).

private void ReplenishInternal(long nowTicks)
{
    ...
    nextPendingRequest = _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
        ? _queue.DequeueHead()
        : _queue.DequeueTail();

    if (!nextPendingRequest.Tcs.TrySetResult(SuccessfulLease)){ ... }
    else { ... }
    nextPendingRequest.CancellationTokenRegistration.Dispose();
}

The limiters should follow the same behavior in the other places, such as when they try to dequeue old requests in order to free some space for pushing new requests on the queue with the newest first processing order policy.

protected override ValueTask<RateLimitLease> AcquireAsyncCore(int permitCount, CancellationToken cancellationToken = default)
{
    ...
    if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && permitCount <= _options.QueueLimit){
        RequestRegistration oldestRequest = _queue.DequeueHead();
        if (!oldestRequest.Tcs.TrySetResult(FailedLease)){ ... }
        else { ... }
       // Mising dispose
      // oldestRequest.CancellationTokenRegistration.Dispose();
    }
}
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Oct 8, 2022
@ghost
Copy link

ghost commented Oct 8, 2022

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

Issue Details

System.Threading.RateLimiting limiter's algorithms are using an internal queue, for queueing async acquire permit requests.
There are some missing calls to disposed of queued requests cancellation token registration after setting their result.
In general, limiters try to dequeue one request from the queue and set the result of the request with a failed or successful lease.
For example, in the replenishments phase(ReplenishInternal), the limiter dequeues one request and, after setting its result, disposes of its cancellation token registration object(nextPendingRequest.CancellationTokenRegistration.Dispose();). also limiter followe same bahvior on Dispose method(next.CancellationTokenRegistration.Dispose();).

private void ReplenishInternal(long nowTicks)
{
    ...
    nextPendingRequest = _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
        ? _queue.DequeueHead()
        : _queue.DequeueTail();

    if (!nextPendingRequest.Tcs.TrySetResult(SuccessfulLease)){ ... }
    else { ... }
    nextPendingRequest.CancellationTokenRegistration.Dispose();
}

The limiters should follow the same behavior in the other places, such as when they try to dequeue old requests in order to free some space for pushing new requests on the queue with the newest first processing order policy.

protected override ValueTask<RateLimitLease> AcquireAsyncCore(int permitCount, CancellationToken cancellationToken = default)
{
    ...
    if (_options.QueueProcessingOrder == QueueProcessingOrder.NewestFirst && permitCount <= _options.QueueLimit){
        RequestRegistration oldestRequest = _queue.DequeueHead();
        if (!oldestRequest.Tcs.TrySetResult(FailedLease)){ ... }
        else { ... }
       // Mising dispose
      // oldestRequest.CancellationTokenRegistration.Dispose();
    }
}
Author: AliKhalili
Assignees: -
Labels:

area-System.Threading, untriaged

Milestone: -

@mangod9 mangod9 removed the untriaged New issue has not been triaged by the area owner label Oct 11, 2022
@mangod9 mangod9 added this to the 8.0.0 milestone Oct 11, 2022
@AliKhalili
Copy link
Contributor Author

@BrennanConroy, Could you please take a look at this issue and its PR?

@BrennanConroy
Copy link
Member

Fixed by #76784
Thanks for the find and fix @AliKhalili!

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants