server: add server owned http route manager#1345
Conversation
| std::vector<Router::RouteConfigProviderSharedPtr> ret; | ||
| ret.reserve(route_config_providers_.size()); | ||
| for (const auto& element : route_config_providers_) { | ||
| ret.push_back(element.second.lock()); |
There was a problem hiding this comment.
@mattklein123 is it possible here that between the time that the last shared_ptr for the provider gets reset, and the destructor for the provider is called (thus cleaning the entry in the manager), this iteration happens?
In other words, should I lock, check for nullptr, and then push into the vector only is the shared_ptr is not null?
There was a problem hiding this comment.
The whole implementation will need to change a bit so let's touch on this later.
mattklein123
left a comment
There was a problem hiding this comment.
nice start, but I would suggest some fundamental changes before working on tests.
| std::vector<Router::RouteConfigProviderSharedPtr> ret; | ||
| ret.reserve(route_config_providers_.size()); | ||
| for (const auto& element : route_config_providers_) { | ||
| ret.push_back(element.second.lock()); |
There was a problem hiding this comment.
The whole implementation will need to change a bit so let's touch on this later.
include/envoy/server/filter_config.h
Outdated
|
|
||
| /** | ||
| * @return HttpRouteManager& singleton for use by the entire server. | ||
| */ |
There was a problem hiding this comment.
At the factory context level, we should not expose the entire manager. Only the ability to get a route provider by name. See below for interface changes.
There was a problem hiding this comment.
there is no reason to expose list() at the context level. Just have two interfaces, with one deriving from the other, and expose the first from the context.
There was a problem hiding this comment.
updated. I am not thrilled by the naming here. I'll think a bit more.
| * @param routeConfigProvider is a weak_ptr to the RouteConfigProvider to add. | ||
| */ | ||
| virtual void | ||
| addRouteConfigProvider(const std::string& name, |
There was a problem hiding this comment.
add/get/remove should be merged into a single API so that RAII can be used when the returned provider is destroyed. Basically, I would recommend doing the following:
- Return a shared_ptr to a route provider (or interface handle to the route provider).
- Internally inside the manager, store both a weak_ptr and a raw pointer. The raw pointer can be used to lookup the manager entry to remove when the last shared_ptr reference goes away. The weak_ptr can be used to create a new shared_ptr to return if needed (AFAIK there is no way to get the raw pointer out of a weak_ptr making this necessary).
- This will allow you to have a single get() method exposed.
source/common/router/rds_impl.h
Outdated
There was a problem hiding this comment.
@junr03 to make reviewing this easier, can you please split out the file move into a separate PR with no code changes. Then do the PR with the changes? It's really hard to tell what changed. Or just don't move things in this PR and clean it up in another PR. Thanks.
7643549 to
d53f2bb
Compare
d714609 to
946964d
Compare
|
@mattklein123 updated with the manager in the router namespace. Right now the manager is in the rds_impl file because I was getting tangled in the imports and forward declarations necessary to make the manager reside in a http_route_manager_impl file. |
| * @param init_manager supplies the init manager. | ||
| */ | ||
| virtual RouteConfigProviderSharedPtr | ||
| getRouteConfigProvider(const Json::Object& config, Runtime::Loader& runtime, |
There was a problem hiding this comment.
Quick comment to work on (sorry really busy this week so I'm giving you drive by reviews :( ): From an interface perspective, I think almost all of these things don't need to be passed. They can be known internally to the route manager. I wouldn't pass any json either, and just unpack into exactly what you need which I think is just route table name and cluster name. (This will likely get converted to v2 but the conversion will be easy cc @htuch ).
So I think at quick glance you need to pass cluster name, route table name, init manager, scope, and maybe stat_prefix.
There was a problem hiding this comment.
No worries, I should have seen that myself. After I update I will ping @htuch to see if he can give a review to load balance you a bit.
There was a problem hiding this comment.
This is close enough. Please just add tests and get the PR ready to ship with full coverage and then we can do a full review.
|
@mattklein123 @htuch tests are in. This is ready for review |
fb846b6 to
2c925f2
Compare
|
|
||
| /** | ||
| * The HttpRouteManager exposes the ability to get a RouteConfigProvider. This interface is exposed | ||
| * to the Server's FactoryContext in order to allow HttpConnMans to get RouteConfigProviders. |
There was a problem hiding this comment.
Nit: HttpConnectionManagers, prefer not to abbreviate type names.
| namespace Router { | ||
|
|
||
| /** | ||
| * The HttpRouteManager exposes the ability to get a RouteConfigProvider. This interface is exposed |
There was a problem hiding this comment.
Nit: Prefer the more verbose RouteConfigProviderManager.
| virtual ~HttpRouteManager() {} | ||
|
|
||
| /** | ||
| * get a Router::RouteConfigProviderSharedPtr. Ownership of the RouteConfigProvider is shared by |
| * get a Router::RouteConfigProviderSharedPtr. Ownership of the RouteConfigProvider is shared by | ||
| * all the ConnectionManagers who own a Router::RouteConfigProviderSharedPtr. The HttpRouteManager | ||
| * holds weak_ptrs to the RouteConfigProviders. Clean up of the weak ptrs happen from the | ||
| * destructor of the RouteConfigProvider. This function creates a RouteConfigProvider is there |
| * @return std::vector<Router::RouteConfigProviderSharedPtr> a list of all the | ||
| * RouteConfigProviders currently loaded. | ||
| */ | ||
| virtual std::vector<RouteConfigProviderSharedPtr> routeConfigProviders() PURE; |
There was a problem hiding this comment.
Is this only for test? Maybe add a ForTest suffix. Do we need to return a proper shared ptr here, or is a reference good enough?
There was a problem hiding this comment.
this function will be used in a subsequent PR by a new admin handler. The handler will iterate
over all the configure RouteConfigProviders, and pretty print all the configured routes.
source/common/router/rds_impl.cc
Outdated
|
|
||
| // RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>. | ||
| std::string map_identifier = config.getString("route_config_name") + "_"; | ||
| map_identifier += config.getString("cluster"); |
There was a problem hiding this comment.
- There's some duplicated logic in creating the map key with the erase in the destructor, probably best to refactor.
- Is it possible that as a result of
_appearing in either cluster or route config name, we could get unintended clashes here? e.g. cluster "A_B" and route config name "C" would hit the same slot as cluster "A" and route config name "B_C". - Nit: prefer creating
map_identifierin one expression and making it `const.
There was a problem hiding this comment.
- Good point. I guess that the correct way to do this is to have the map key be the tuple of the route_config_name and cluster. And it solves 1, and 3 as well.
There was a problem hiding this comment.
Sure, or some other distinguished identifier (e.g. : can't appear in the cluster name.
There was a problem hiding this comment.
@htuch concatenated with :. If we go this route then the route_config_name field of the RDS config also cannot contain :. I have documented that. But if I understand our breaking change policy I cannot enforce that until the 1.4.0 release. That means that when we release 1.4.0 I would make a PR enforcing the rule?
source/common/router/rds_impl.cc
Outdated
| std::vector<Router::RouteConfigProviderSharedPtr> ret; | ||
| ret.reserve(route_config_providers_.size()); | ||
| for (const auto& element : route_config_providers_) { | ||
| ret.push_back(element.second.lock()); |
There was a problem hiding this comment.
Do you want to include the failed locks here?
There was a problem hiding this comment.
That is what I was discussing with @mattklein123 in my initial comment above. I wanted to understand if the threading model allowed for failed locks or not. I understand that the way to protect here is to lock, check the returned ptr is nullptr, and then push only if it isn't, just wanted to make sure it was necessary.
There was a problem hiding this comment.
threading has nothing to do with it here. All of this is single threaded. I haven't looked at the code yet but assuming it's correct lock() should never return nullptr.
There was a problem hiding this comment.
Right, that's what I thought.
There was a problem hiding this comment.
Well, yeah, threading isn't relevant, lock is a misnomer. It's a way to turn weak into a real shared pointer. Assuming this is weak for a reason, it seems it should be possible for the lock to fail... or did I miss something here?
There was a problem hiding this comment.
I think I wasn't clear with my comment. I understand what lock does, and that it has nothing to do with threading. I was originally concerned that in the envoy threading model it would be possible to get to a state where the RouteConfigObject were destroyed but the weak ptr was still present in the Manager. I now understand that is not possible.
@htuch The pointer is weak because we want don't want the manager to own the RouteConfigProvider, we only want it to be able to dole out share ptrs to the HttpConnectionManagers that create RouteConfigProviders.
source/common/router/rds_impl.cc
Outdated
|
|
||
| // RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>. | ||
| std::string map_identifier = config.getString("route_config_name") + "_"; | ||
| map_identifier += config.getString("cluster"); |
There was a problem hiding this comment.
Sure, or some other distinguished identifier (e.g. : can't appear in the cluster name.
source/common/router/rds_impl.cc
Outdated
| return std::move(new_provider); | ||
| } | ||
|
|
||
| return it->second.lock(); |
There was a problem hiding this comment.
Can this lock() fail? Based on other comment threads and the comment above about only cleaning up in RouteConfigProvider destructors, I'm thinking no. If that's the case, please add an ASSERT/comment to document.
If it can fail here, shouldn't we have similar handling to the "not found" case above and create a new provider?
source/common/router/rds_impl.cc
Outdated
| std::vector<Router::RouteConfigProviderSharedPtr> ret; | ||
| ret.reserve(route_config_providers_.size()); | ||
| for (const auto& element : route_config_providers_) { | ||
| ret.push_back(element.second.lock()); |
There was a problem hiding this comment.
OK, I think I get what's going on now. We're using a weak pointer simply as a means to allow handing out shared pointers later when we lock, otherwise we could be using plain * pointers. Seems legit. As below, I'd add an ASSERT here and comment to explain why this can never fail.
| RouteConfigProviderSharedPtr provider2 = route_config_provider_manager_.getRouteConfigProvider( | ||
| *config, cm_, store_, "foo_prefix", init_manager_); | ||
| // So this means that both shared_ptrs should be the same. | ||
| EXPECT_EQ(provider, provider2); |
There was a problem hiding this comment.
Have you considered using the shared_ptr use_count() method to validate the behaviors around reference counting?
|
Please also merge master which will fix ASAN issue. I will take a look after you have addressed @htuch comments. Thanks. |
|
@mattklein123 updated from comments. Merged master. |
| :ref:`RDS HTTP API <config_http_conn_man_rds_api>`. This allows an Envoy configuration with | ||
| multiple HTTP listeners (and associated HTTP connection manager filters) to use different route | ||
| configurations. | ||
| configurations. The route_config_name must **not** contain ``:``. |
There was a problem hiding this comment.
Yeah, on second thoughts, maybe best to avoid baking in these dependencies. I know that we've been discussing trying to relax the : constraint on clusters at some point (but not now), so if there is a cleaner way, e.g. using tuples and hashing on that, then maybe do that.
There was a problem hiding this comment.
Agreed. Don't think there is any explicit reason to block ':' ?
There was a problem hiding this comment.
Thinking about this some more, I was being near sighted, and thought that it was possible to fall into the same collision situation that I was trying to escape. But that is not the case if one of the strings has the blocked character.
@htuch how serious are we thinking about relaxing the : constraint? I could leave a TODO to move the to pair, or struct key when that happens, but for now I'll leave it this way to avoid having to deal with hashing the key.
There was a problem hiding this comment.
I don't really understand how ':' matters here at all. The key is internal. If you just add config name + cluster, what difference does it make what characters it contains?
There was a problem hiding this comment.
Because if for example someone specifies route_config_name foo and cluster bar and someone else does route_config_name foob and cluster ar the concatenation would make the two providers collide when it shouldn't.
There was a problem hiding this comment.
You probably see that. So I am guessing I am the one that doesn't see why this wouldn't matter? I guess it is pretty contrived that such a collision would happen, but I think protecting against it is better?
There was a problem hiding this comment.
Then just do something like "hash_separator_you_need_to_be_really_insane_to_use_this" or whatever. (I haven't thought through it enough but seems like something like this should work fine.
The other option is to just do a custom hasher/comparer which operates on a struct or tuple of config/cluster.
|
Meta comment. I think this should probably use the singleton manager that I am building in #1410. This should merge today. This will avoid a lot of boilerplate addition for something that not everyone needs (for people that don't use HTTP). Even for admin endpoint, the singleton can register a handler into admin. Not a huge deal if we are eager for this to merge, but I might at least put in a TODO to this effect. I will review the rest of this change as is later today. |
mattklein123
left a comment
There was a problem hiding this comment.
look good other than some small nits and fixing test compile errors. Up to you if you want to do the singleton thing here or not.
| :ref:`RDS HTTP API <config_http_conn_man_rds_api>`. This allows an Envoy configuration with | ||
| multiple HTTP listeners (and associated HTTP connection manager filters) to use different route | ||
| configurations. | ||
| configurations. The route_config_name must **not** contain ``:``. |
There was a problem hiding this comment.
Agreed. Don't think there is any explicit reason to block ':' ?
source/common/router/rds_impl.cc
Outdated
| // of this code, locking the weak_ptr will not fail. | ||
| Router::RouteConfigProviderSharedPtr provider = element.second.lock(); | ||
| ASSERT(provider) | ||
| ret.push_back(std::move(provider)); |
There was a problem hiding this comment.
del std::move here as this is a shared_ptr
source/common/router/rds_impl.cc
Outdated
|
|
||
| // RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>. | ||
| const std::string manager_identifier = | ||
| config.getString("route_config_name") + ":" + config.getString("cluster"); |
There was a problem hiding this comment.
The key is opaque. If you just remove ':' and then you can allow ':' in the name (you probably could allow ':' in the name anyway and it would work).
source/common/router/rds_impl.cc
Outdated
| new_provider->registerInitTarget(init_manager); | ||
|
|
||
| route_config_providers_.insert( | ||
| {manager_identifier, std::weak_ptr<RdsRouteConfigProviderImpl>(new_provider)}); |
There was a problem hiding this comment.
nit: I don't thinkstd::weak_ptr<RdsRouteConfigProviderImpl> is needed here as it should auto convert.
source/common/router/rds_impl.cc
Outdated
| // of this code, locking the weak_ptr will not fail. | ||
| Router::RouteConfigProviderSharedPtr new_provider = it->second.lock(); | ||
| ASSERT(new_provider); | ||
| return std::move(new_provider); |
There was a problem hiding this comment.
@mattklein123 for my information. Here and above, doesn't moving the pointer save the atomic operations of copying the pointer on return (here) and insertion into the vector?
There was a problem hiding this comment.
The compiler will optimize usually. It's not worth playing games like this IMO with shared pointers.
|
@mattklein123 for sake of finishing this PR, I am leaning into merging in the current state and doing a subsequent PR to use #1410. |
| auto it = route_config_providers_.find(manager_identifier); | ||
| if (it == route_config_providers_.end()) { | ||
| std::shared_ptr<RdsRouteConfigProviderImpl> new_provider{ | ||
| new RdsRouteConfigProviderImpl(config, manager_identifier, runtime_, cm, dispatcher_, |
mattklein123
left a comment
There was a problem hiding this comment.
Looks great. Few small nits remaining.
test/common/router/rds_impl_test.cc
Outdated
|
|
||
| class RouteConfigProviderManagerImplTest : public testing::Test { | ||
| public: | ||
| RouteConfigProviderManagerImplTest() {} |
There was a problem hiding this comment.
nit: del empty constructor/destructor
test/common/router/rds_impl_test.cc
Outdated
| configured_providers.clear(); | ||
|
|
||
| // All shared_ptrs to the provider pointed at by provider1, and provider2 have been deleted, so | ||
| // now we should only have the provider porinted at by provider3. |
|
@mattklein123 updated. |
Description: Splits iOS CI workflows into test and build, and updates build job to use cached library artifact when run on the same SHA. Risk Level: Low Testing: CI Signed-off-by: Mike Schore <[email protected]> Signed-off-by: JP Simard <[email protected]>
Description: Splits iOS CI workflows into test and build, and updates build job to use cached library artifact when run on the same SHA. Risk Level: Low Testing: CI Signed-off-by: Mike Schore <[email protected]> Signed-off-by: JP Simard <[email protected]>
**Description** This fixes the broken table in #1257. Signed-off-by: Takeshi Yoneda <[email protected]>
This PR adds a server owned HttpRouteManager, which observes the server's RDSRouteConfigProviders. This allows for deduplication of RDSRouteConfigProviders of the same name via shared ownership, and allows the server to iterate over all RDSRouteConfigProviders.