Skip to content

common: add a thread safe token bucket#14827

Merged
ggreenway merged 14 commits intoenvoyproxy:mainfrom
nitgoy:tokenbucket
Feb 12, 2021
Merged

common: add a thread safe token bucket#14827
ggreenway merged 14 commits intoenvoyproxy:mainfrom
nitgoy:tokenbucket

Conversation

@nitgoy
Copy link
Copy Markdown
Contributor

@nitgoy nitgoy commented Jan 26, 2021

Commit Message: Add a thread-safe token bucket wrapper class to be shared across threads. The other token bucket implementation was not thread safe which made it unusable for certain cases. This wrapper class calls the thread-unsafe class for the core token bucket functionality.
Additional Description: New class, not used anywhere else yet except UTs.
Risk Level: Low
Testing: Ran existing and newly added UTs.
Docs Changes: None
Release Notes:
Platform Specific Features:

nitgoy added 3 commits January 7, 2021 12:03
Signed-off-by: Nitin <nigoyal@microsoft.com>
Signed-off-by: Nitin <nigoyal@microsoft.com>
Signed-off-by: Nitin <nigoyal@microsoft.com>
@repokitteh-read-only
Copy link
Copy Markdown

Hi @nitgoy, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #14827 was opened by nitgoy.

see: more, trace.

Signed-off-by: Nitin <nigoyal@microsoft.com>
@ggreenway
Copy link
Copy Markdown
Member

I think it would be cleaner to add a wrapper class around the existing token bucket class to add locking, instead of inserting optional locking into it.

@yanavlasov
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Signed-off-by: Nitin <nigoyal@microsoft.com>
@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Jan 26, 2021

I think it would be cleaner to add a wrapper class around the existing token bucket class to add locking, instead of inserting optional locking into it.

Done.

It looks like ABSL_EXCLUSIVE_LOCKS_REQUIRED is not acquiring lock for me locally but it does in the CodeQL-build. That indicates the exclusive_locks_required ABSL attribute is not set. Shouldn't it be automatically set for the bazel workspace?

Signed-off-by: Nitin <nigoyal@microsoft.com>
@nitgoy nitgoy changed the title common: make token bucket optionally thread safe common: add a thread safe token bucket Jan 26, 2021
@ggreenway
Copy link
Copy Markdown
Member

It looks like ABSL_EXCLUSIVE_LOCKS_REQUIRED is not acquiring lock for me locally but it does in the CodeQL-build. That indicates the exclusive_locks_required ABSL attribute is not set. Shouldn't it be automatically set for the bazel workspace?

That annotation allows the compiler to verify that the correct lock is held everytime you access a field. It does not automatically acquire the lock for you. It's only used for static code analysis.

@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Jan 26, 2021

It looks like ABSL_EXCLUSIVE_LOCKS_REQUIRED is not acquiring lock for me locally but it does in the CodeQL-build. That indicates the exclusive_locks_required ABSL attribute is not set. Shouldn't it be automatically set for the bazel workspace?

That annotation allows the compiler to verify that the correct lock is held everytime you access a field. It does not automatically acquire the lock for you. It's only used for static code analysis.

I thought so too but I see below CodeQL-build errors if I keep it along with the actual LockGuard (see previous iteration). The build passes if I remove the macro. Is this the right macro?

source/common/common/shared_token_bucket_impl.cc:18:21: error: acquiring mutex 'mutex_' that is already held [-Werror,-Wthread-safety-analysis]
Thread::LockGuard lock(mutex_);
^
source/common/common/shared_token_bucket_impl.cc:17:5: note: mutex acquired here
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
^
external/com_google_absl/absl/base/thread_annotations.h:143:18: note: expanded from macro 'ABSL_EXCLUSIVE_LOCKS_REQUIRED'
attribute((exclusive_locks_required(VA_ARGS)))

@ggreenway
Copy link
Copy Markdown
Member

Before we go further with this review, can you explain what this code will be used for? Oftentimes it's easier to make changes like this in the context that it will be used, so the requirements/use-case are clearer.

@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Jan 27, 2021

Before we go further with this review, can you explain what this code will be used for? Oftentimes it's easier to make changes like this in the context that it will be used, so the requirements/use-case are clearer.

This is the PR for that will use it: #14096. Essentially, I need it to represent token bucket for per filter-chain/listener limit for bandwidth limit filter that will span across multiple http streams.

Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

High-level question: is this wrapper needed, or would it be better to just have a mutex and a TokenBucketImpl in the class that needs it? Will the owning class ever need to manipulate the token bucket and some other state atomically under the same lock?

/wait

Signed-off-by: Nitin <nigoyal@microsoft.com>
@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Jan 28, 2021

High-level question: is this wrapper needed, or would it be better to just have a mutex and a TokenBucketImpl in the class that needs it? Will the owning class ever need to manipulate the token bucket and some other state atomically under the same lock?

/wait

The interface methods will be called by the common class StreamRateLimiter. Depending upon the use case callers can pass the TokenBucketImpl or the SharedTokenBucketImpl. That way the StreamRateLimiter class can be agnostic whether the token bucket's shared or not. Also, a caller that doesn't need the thread-safety can avoid paying that cost.

@nitgoy nitgoy requested a review from ggreenway January 28, 2021 00:50
@ggreenway
Copy link
Copy Markdown
Member

Looking at this, and how it's used in the stream limit filter, I think you need to change the API to make consume() and nextTokenAvailable() be atomic. With what you have here, consume() could return no tokens, but before nextTokenAvailable() is called some tokens could be added, and thus nextTokenAvailable() will return 0, the timer will not be scheduled, and no data will be written.

I'm not sure that just adding a mutex around each call on the TokenBucket interface is sufficient for what you're trying to accomplish.

/wait-any

@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Feb 3, 2021

With what you have here, consume() could return no tokens, but before nextTokenAvailable() is called some tokens could be added, and thus nextTokenAvailable() will return 0, the timer will not be scheduled, and no data will be written.

Thanks for catching this issue. I'll try adding a test around it. Does it make sense to change the check right after nextTokenAvailable() call to if (ms.count() >= 0)? We could use 1ms as minimum for next timer schedule and move the write_data_cb_ call before it, to allow the current thread to finish that operation before next timer wakeup.

The other approach would be to have the mutex in streamratelimiter class as you suggested but has other downsides as I stated before.

@ggreenway
Copy link
Copy Markdown
Member

I think it makes most sense to add a method to the interface to get the two values atomically.

Signed-off-by: Nitin <nigoyal@microsoft.com>
Signed-off-by: Nitin <nigoyal@microsoft.com>
Signed-off-by: Nitin <nigoyal@microsoft.com>
@nitgoy
Copy link
Copy Markdown
Contributor Author

nitgoy commented Feb 5, 2021

I think it makes most sense to add a method to the interface to get the two values atomically.

Made the suggested changes. Can you please take a look? Working on fixing these other issues.

Signed-off-by: Nitin <nigoyal@microsoft.com>
@nitgoy nitgoy requested a review from alyssawilk as a code owner February 11, 2021 03:07
Signed-off-by: Nitin Goyal <nigoyal@microsoft.com>
@azure-pipelines
Copy link
Copy Markdown

Commenter does not have sufficient privileges for PR 14827 in repo envoyproxy/envoy

ggreenway
ggreenway previously approved these changes Feb 11, 2021
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #14827 (review) was submitted by @ggreenway.

see: more, trace.

Signed-off-by: Nitin Goyal <nigoyal@microsoft.com>
@ggreenway ggreenway merged commit 97c722f into envoyproxy:main Feb 12, 2021
@nitgoy nitgoy deleted the tokenbucket branch February 12, 2021 23:41
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.

3 participants