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

Added Prioritybased rate limiter #235

Merged
merged 13 commits into from
Oct 30, 2024
Merged

Conversation

hiteshk25
Copy link
Collaborator

No description provided.

@hiteshk25 hiteshk25 changed the base branch from fs/branch_9x to fs/branch_9_7 October 24, 2024 00:06
Copy link
Collaborator

@magibney magibney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed again with a strict focus on practicality.

I think this will work as intended. It would be a good idea to address at least the race condition I identified, and it'd be a trivial/minimal change to put the header-parsing logic in the PriorityBasedRateLimiter.handleRequest() method, updating that method to take HttpServletReqest param instead of String.

The rest of this seems ok for now, though I strongly suspect we'll want to fully revert this and re-approach with a mind to making RateLimiter API properly pluggable.

Comment on lines 88 to 98
if (this.priorityOneRequests.get() > this.totalAllowedRequests) {
// priority one request is waiting, let's inform it
this.exitFromQueue();
} else {
// next priority
CountDownLatch waiter = this.waitingList.poll();
if (waiter != null) {
waiter.countDown();
}
this.exitFromQueue();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a race condition here that could leak CountDownLatch instances into the waitingList. (because we check priorityOneRequests count before inserting into waitingList, it's possible that by the time we insert, another thread would have decremented priorityOneRequests and polled the waitingList).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can sync release/acquire method but I was avoiding this.

  1. As each release call will poll and then then notify the any waiting request.
  2. Also release call will make it decrements the ActiveRequests call.

I'm assuming this is enough to cover any race condition, but let me know if i'm missing something here ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's definitely a race condition here.

      if (this.activeRequests.get() < this.totalAllowedRequests) {
        return nextInQueue();
      } else {
        // RACE CONDITION SOMWHERE IN HERE
        CountDownLatch wait = new CountDownLatch(1);
        this.waitingList.put(wait);
        return wait.await(this.waitTimeoutInMillis, TimeUnit.MILLISECONDS) && nextInQueue();

It is possible for a ton of BACKGROUND requests to come in find activeRequests full, then have all (or some) of the activeRequests complete, and poll waitingList before the BACKGROUND requests have been added to it.

I think it's an edge case, but it's definitely there, and could yield unpredictable behavior.

@hiteshk25
Copy link
Collaborator Author

Reviewed again with a strict focus on practicality.

I think this will work as intended. It would be a good idea to address at least the race condition I identified, and it'd be a trivial/minimal change to put the header-parsing logic in the PriorityBasedRateLimiter.handleRequest() method, updating that method to take HttpServletReqest param instead of String.

The rest of this seems ok for now, though I strongly suspect we'll want to fully revert this and re-approach with a mind to making RateLimiter API properly pluggable.

Thanks @magibney I have created a follow up ticket. Let's discuss and propose this in the solr-community.

Copy link
Collaborator

@magibney magibney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The race condition is still not addressed. While considering whether it would be ok provisionally, I realized another related problem:

The rate at which CountDownLatch instances can be removed from the queue is limited by the actual throughput of work permits acquired. Under the (quite realistic) scenario of heavy BACKGROUND request load, you could easily end up adding more CountDownLatches to the queue than the executing requests would poll off the queue, allowing the queue to grow indefinitely.

The blocked background requests would fail properly in a reasonable amount of time, but the vestigial CountDownLatches would accumulate in the queue. The vestigial CountDownLatches would (FIFO) get notified despite the fact that their initiating request may have long since given up, allowing any actual waiting BACKGROUND requests (at the end of the queue) to languish, and could pile up requests in a way that the queue would become effectively a sink (and heap memory leak).

I'm pretty sure this is not just an edge case -- leaking memory is basically guaranteed if requests come in at a rate faster than the throughput at which they can actually execute (a common case for a rate limiter).

@hiteshk25
Copy link
Collaborator Author

The race condition is still not addressed. While considering whether it would be ok provisionally, I realized another related problem:

The rate at which CountDownLatch instances can be removed from the queue is limited by the actual throughput of work permits acquired. Under the (quite realistic) scenario of heavy BACKGROUND request load, you could easily end up adding more CountDownLatches to the queue than the executing requests would poll off the queue, allowing the queue to grow indefinitely.

The blocked background requests would fail properly in a reasonable amount of time, but the vestigial CountDownLatches would accumulate in the queue. The vestigial CountDownLatches would (FIFO) get notified despite the fact that their initiating request may have long since given up, allowing any actual waiting BACKGROUND requests (at the end of the queue) to languish, and could pile up requests in a way that the queue would become effectively a sink (and heap memory leak).

I'm pretty sure this is not just an edge case -- leaking memory is basically guaranteed if requests come in at a rate faster than the throughput at which they can actually execute (a common case for a rate limiter).

Thanks @magibney Nice catch. I have updated the probable fix

Copy link
Collaborator

@magibney magibney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm comfortable with this. There's still a race condition that should probably be addressed, but I think it's basically benign.

@hiteshk25 hiteshk25 merged commit 71c8121 into fs/branch_9_7 Oct 30, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants