quic: refactor QUICHE flag implementation#18485
quic: refactor QUICHE flag implementation#18485lkpdn wants to merge 12 commits intoenvoyproxy:mainfrom
Conversation
|
Hi @lkpdn, 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. |
c47c097 to
375fe66
Compare
|
Forgot to run git pre-push. I've just force-pushed to clear up some formatting issues. |
375fe66 to
8b9408a
Compare
8b9408a to
58b0355
Compare
|
Awesome find! Mind taking a look at the presubmits and seeing if you can sort them out? |
|
I'll look into it. Before that, let me rebase to the latest main branch. |
58b0355 to
87dffe0
Compare
There was a problem hiding this comment.
quick question: what is the contention that this part of the change solves? It's not immediately clear to me whether the contention is on just referencing the shared_ptr object (seems unlikely) or something internal to the snapshot? Can you add more details?
There was a problem hiding this comment.
It's the former that seemed unlikely to me too. Each thread has a local slot in thread_local_data_.data_, though the stored snapshot is shared among them so the contention happens.
There was a problem hiding this comment.
But contention on what? Actually accessing the shared_ptr just to return a reference? I would be curious to see a perf trace about this part specifically, because I would prefer to not change this unless it really provides a lot of benefit and if so we should understand it more.
There was a problem hiding this comment.
I've just added a side note in this PR description. Could you take a look a it?
There was a problem hiding this comment.
Thanks for the perf trace! It looks like InstanceImpl::SlotImpl::getWorker(uint32_t index) has heavy contention. @jmarantz could take a look to see if the contention is expected?
There was a problem hiding this comment.
These are my opinion:
One thing I wonder is whether we could have an access variant that does not incref if we know it's safe
If each thread had a weak_ptr to a new snapshot, when the next createNewSnapshot() invoked and the old one destructor runs, each thread may have a dangling pointer to them until it'll be replaced with a new one.
the runtime system appears to be using the untyped variants of TLS slots. It would be great to change them to use the typed versions and eliminate dynamic casts.
I didn't consider this.
A good pattern is to use them when the code being locked is long-running (ie more than just returning a shared_ptr).
I agree.
But if I'm not mistaken this change creates separate shared pointers for every worker all pointing to the same snapshot instance. And this may cause races.
It creates separate shared pointers for every worker all pointing to the "copied" snapshot instance. (So, essentially It does not need to be shared_ptr now, but if we change the part in this PR, the diff would be too large I guess.)
There was a problem hiding this comment.
It creates separate shared pointers for every worker all pointing to the "copied" snapshot instance. (So, essentially It does not need to be shared_ptr now, but if we change the part in this PR, the diff would be too large I guess.)
I see, I misread make_shared as shared_ptr. Can this part be split into a separate PR?
There was a problem hiding this comment.
I think I'm suggesting something slightly different if this is actually a contention issue:
- Still share the data via shared_ptr.
- Have a variant which effectively returns the reference
*shared_ptr.get(). - We know this is safe because of the RCU like semantics as long as the reference is only used during that function call.
I think this should avoid any atomic interaction.
Either way I agree with @rojkov let's please split this part out and discuss separately?
There was a problem hiding this comment.
Thank you for the comment. I'm not sure I understand your idea correctly so let me ask a few questions:
- Coud you explain "RCU like semantics" in detail?
- Looking at the second point you mentioned, I thought the idea is to avoid
dynamic_pointer_castfromLoaderImpl::threadSnapshot()and change SnapshotConstSharedPtr to a more plain reference. Am I correct?
But anyway,
Either way I agree with @rojkov let's please split this part out and discuss separately?
Alright, I'll split this part out, leaving the "QUIC flags backed by runtime features" part in this PR 18485.
There was a problem hiding this comment.
@mattklein123 @rojkov @jmarantz @danzh2010
Please see #18601 .
There was a problem hiding this comment.
We sometimes use this in tests, can we just implement it somehow?
There was a problem hiding this comment.
56152d2 removed those call sites. And I think it should be done with Runtime::LoaderSingleton::getExisting()->mergeValues when some flags need to be tweaked in tests.
There was a problem hiding this comment.
Sorry I was wrong. I didn't remove SetQuicheReloadableFlagImpl call sites in tests at all.
To be honest, I don't see any merit keeping these set/get testing if the quic flag implementation is to be integrated to Envoy runtime feature infrastructure.
There was a problem hiding this comment.
We do need SetQuicheReloadableFlagImpl() sometimes even in non-test code in case where Envoy needs a feature/fix in QUICHE which is not turned on yet.
There was a problem hiding this comment.
Okay I got it. I'll implement it. Thank you.
87dffe0 to
d4f01ac
Compare
|
I looked into the presubmits failures, then I noticed that the copy constructor implementation of Rather than introducing the copy construction and messing the code base, just calling |
|
It seems that multiple
After thinking carefully, I realized that only |
192a16f to
be7ec15
Compare
|
/assign @RenjieTang who implemented QUICHE flags with Envoy runtime. |
|
Wow an interesting find! Thanks for fixing this. |
|
Before diving in I want to say this is one of the crispest, most detailed, data-driven PR descriptions I have ever seen. Nice. |
be7ec15 to
d0186f0
Compare
The currently used QUICHE for envoy integration already contains the
upstream commit 68b06f23af21 ("Refactor QUICHE platform flag accessors.")
so these accessor implementations are no longer needed.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
0ccb9d9 to
bdbfd1c
Compare
|
side note, please don't force push as it complicates the review process. Please merge from main rather than rebase for updates. thanks! |
- As the flags are now backed by Envoy Runtime Configuration, updating those values online from inside should be prohibited. They should just be updated in the same way as other runtime features. - QUIC_PROTOCOL_FLAG implementation is converted to plain global variables. Signed-off-by: Koichiro Den <den@valinux.co.jp>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
…-access-contention Signed-off-by: Koichiro Den <den@valinux.co.jp>
e22e06e to
3786431
Compare
|
@mattklein123 Alright, done. |
|
Note that the reason I added a commit 850bb91 is not because I added some new code that is not covered by testing. It just seems that deleting some covered lines makes the final result down to 95.9%. |
danzh2010
left a comment
There was a problem hiding this comment.
Thanks for working on this! I left a few nits, but looks good overall!
@RenjieTang, do you mind take a look as well?
| "//envoy/runtime:runtime_interface", | ||
| "//source/common/common:hash_lib", | ||
| "//source/common/singleton:const_singleton", | ||
| "@com_github_google_quiche//:quic_core_flags_list_lib", |
There was a problem hiding this comment.
You will need envoy_select_enable_http3() for this.
There was a problem hiding this comment.
Indeed. I'll push a fix shortly. Thank you!
There was a problem hiding this comment.
Just noticed that the quic platform tests had been executed successfully even when http3=False build.
quiche::FlagRegistry was holding quic flags even if http3=False.
So, in order to make it run even after making it backed by runtime features, in 3a13a02 I reverted this part.
| testNewOverrides(*loader_, store_); | ||
| } | ||
|
|
||
| #ifdef ENVOY_ENABLE_QUIC |
There was a problem hiding this comment.
Why is this test removed?
There was a problem hiding this comment.
I just forgot to revert this dropping after #18485 (comment). Just my mistake. I'll push a fix, thanks!
There was a problem hiding this comment.
In e4d8613, I modified and moved this test under RtdsLoaderImplTest . Now I wonder this PR somewhat would break backward compatibility. Some admin might expect they can override the QUICHE reloadable/restart flags from any layer.
There was a problem hiding this comment.
Is this why you added admin layer and called doOnConfigUpdateVerifyNoThrow(runtime, 0) in the test? I'm not familiar with the control plan, but updating flags from admin layer only seems fine to me. @htuch
There was a problem hiding this comment.
I think if something is a runtime flag, it should ideally be updatable from any runtime layer, otherwise we violate principle-of-least-surprise (every other runtime flag should have this capability). Is there a reason we can't do this for QUIC?
There was a problem hiding this comment.
My comment #18485 (comment) was somewhat perplexing.
It's updatable from any runtime layer. The point is that QUICHE flag could be updated at an arbitrary timing from inside, on admin layer, if such an implementation will be merged. That's why I dropped the SetQuiche(Reloadable|Restart)FlagsImpl capability in my original PR (ref. #18485 (comment)) though.
The current upstream implementation calls SetQuic(Reloadable|Restart)Flag from inside (e.g., on ActiveQuicListener instantiation). With this PR, those places will be eliminated, except tests.
So, basically administrators can update those flags from any runtime layer without worry.
In that sense, in the SetQuiche(Reloadable|Restart)FlagImpl implementation, replacing ASSERT(Envoy::Thread::MainThread::isMainOrTestThread()); with something like ASSERT(Envoy::Thread::MainThread::isTestThread()); might help.
There was a problem hiding this comment.
SG - as long as we are treating the flags uniformly with other runtime flags then no objections.
Ref: #18485 (review) Signed-off-by: Koichiro Den <den@valinux.co.jp>
|
@danzh2010 Thank you for the review! Just pushed e4d8613. |
Signed-off-by: Koichiro Den <den@valinux.co.jp>
…-access-contention Signed-off-by: Koichiro Den <den@valinux.co.jp>
…if http3=False Signed-off-by: Koichiro Den <den@valinux.co.jp>
| # for existing directories with low coverage. | ||
| declare -a KNOWN_LOW_COVERAGE=( | ||
| "source/common:95.9" # Raise when QUIC coverage goes up | ||
| "source/common:96.0" # Raise when QUIC coverage goes up |
There was a problem hiding this comment.
nice! Does source/common/quic/ coverage changed?
There was a problem hiding this comment.
The latest CI coverage run failed in bazel coverage stage so the actual output (artifact, including logs) is not available.
But anyway, #18591 (ref. #17479 (comment)) was merged a few days ago, so I think we better to re-run and look at the result, with the new 95.9% threshold being untouched.
| testNewOverrides(*loader_, store_); | ||
| } | ||
|
|
||
| #ifdef ENVOY_ENABLE_QUIC |
There was a problem hiding this comment.
Is this why you added admin layer and called doOnConfigUpdateVerifyNoThrow(runtime, 0) in the test? I'm not familiar with the control plan, but updating flags from admin layer only seems fine to me. @htuch
Signed-off-by: Koichiro Den <den@valinux.co.jp>
|
We got two new failures even though a9d5b2b is tiny diff.
|
Signed-off-by: Koichiro Den <den@valinux.co.jp>
|
https://dev.azure.com/cncf/envoy/_build/results?buildId=92264&view=logs&j=bbe4b42d-86e6-5e9c-8a0b-fea01d818a24&t=e00c5a13-c6dc-5e9a-6104-69976170e881&l=11105 |
| Network::Address::InstanceConstSharedPtr local_addr, | ||
| QuicStatNames& quic_stat_names, Stats::Scope& scope) { | ||
| // This flag fix a QUICHE issue which may crash Envoy during connection close. | ||
| SetQuicReloadableFlag(quic_single_ack_in_packet2, true); |
There was a problem hiding this comment.
Does Envoy runtime work for client side? @ggreenway
If not, we still need to set this flag here and quiche::FlagRegistry::getInstance() above.
There was a problem hiding this comment.
Answer to myself, yes, RuntimeFeature is a singleton instantiated upon fetching a feature.
|
This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! |
|
This pull request has been automatically closed because it has not had activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! |
QUICHE QUIC_FLAG implementation should be backed by Envoy runtime features not only because it's the canonical way (*1) but also for performance reasons.
It is observed that some quiche::TypedFlag access on the QUICHE datapath becomes a major scaling bottleneck when downstream QUIC traffic is heavy.
Simply replacing absl::MutexLock in TypedFlag implementation with absl::(Reader|Writer)MutexLock where possible is not satisfactory. The bottleneck would just move from the contention on the MutexLock to the atomic operation on the refcount in the new ReaderMutexLock done by each Envoy worker thread. We need a truly scalable flag accessing. Unfortunately, Envoy::Runtime::runtimeFeatureEnabled is also not contention-free since the snapshot created by createNewSnapshot() is shared by every worker thread, which again leads to the refcount contention.
With this patchWith this PR and #18601, Envoy QUIC performance scales quite well. Please see the attached plot of the scalability comparison between Envoy (as a) QUIC server with-and-without this patch.(*1) https://github.com/envoyproxy/envoy/blob/1b9c688997f5/source/common/quic/platform/quiche_flags_impl.h#L22-L24
(As a side note, added on 2021/10/11)
This is the reason why do we not only make QUICHE flag implementation backed by Envoy runtime features, but also "unshare" SnapshotImpl. But the contention really occurs? how to reproduce it? The way to reproduce it and its result are shown below:
bazel.debug.server_onlywith -O2 optimization (my ad-hoc diff: https://gist.github.com/lkpdn/cd416dbcb1a2dba061389bb8eff07f5c)Risk Level: Low
Testing:
Docs Changes:
Release Notes:
Platform Specific Features:
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Fixes commit #PR or SHA]
[Optional Deprecated:]
[Optional API Considerations:]