Skip to content

server: add server owned http route manager#1345

Merged
mattklein123 merged 15 commits intomasterfrom
http-route-manager
Aug 9, 2017
Merged

server: add server owned http route manager#1345
mattklein123 merged 15 commits intomasterfrom
http-route-manager

Conversation

@junr03
Copy link
Copy Markdown
Member

@junr03 junr03 commented Jul 27, 2017

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.

std::vector<Router::RouteConfigProviderSharedPtr> ret;
ret.reserve(route_config_providers_.size());
for (const auto& element : route_config_providers_) {
ret.push_back(element.second.lock());
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The whole implementation will need to change a bit so let's touch on this later.

Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

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());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The whole implementation will need to change a bit so let's touch on this later.


/**
* @return HttpRouteManager& singleton for use by the entire server.
*/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

makes sense

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@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.

@junr03 junr03 force-pushed the http-route-manager branch from 7643549 to d53f2bb Compare August 2, 2017 00:54
@junr03 junr03 changed the base branch from master to pre-http-route-manager August 2, 2017 00:55
@junr03 junr03 force-pushed the http-route-manager branch from d714609 to 946964d Compare August 2, 2017 08:13
@junr03 junr03 changed the base branch from pre-http-route-manager to master August 2, 2017 08:14
@junr03
Copy link
Copy Markdown
Member Author

junr03 commented Aug 2, 2017

@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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@junr03
Copy link
Copy Markdown
Member Author

junr03 commented Aug 3, 2017

@mattklein123 @htuch tests are in. This is ready for review

@junr03 junr03 force-pushed the http-route-manager branch from fb846b6 to 2c925f2 Compare August 3, 2017 20:19
Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Nice, this makes sense.


/**
* 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: HttpConnectionManagers, prefer not to abbreviate type names.

namespace Router {

/**
* The HttpRouteManager exposes the ability to get a RouteConfigProvider. This interface is exposed
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Prefer the more verbose RouteConfigProviderManager.

virtual ~HttpRouteManager() {}

/**
* get a Router::RouteConfigProviderSharedPtr. Ownership of the RouteConfigProvider is shared by
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: s/get/Get.

* 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: s/is/if

* @return std::vector<Router::RouteConfigProviderSharedPtr> a list of all the
* RouteConfigProviders currently loaded.
*/
virtual std::vector<RouteConfigProviderSharedPtr> routeConfigProviders() PURE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.


// RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>.
std::string map_identifier = config.getString("route_config_name") + "_";
map_identifier += config.getString("cluster");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  1. There's some duplicated logic in creating the map key with the erase in the destructor, probably best to refactor.
  2. 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".
  3. Nit: prefer creating map_identifier in one expression and making it `const.

Copy link
Copy Markdown
Member Author

@junr03 junr03 Aug 3, 2017

Choose a reason for hiding this comment

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

  1. 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sure, or some other distinguished identifier (e.g. : can't appear in the cluster name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@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?

std::vector<Router::RouteConfigProviderSharedPtr> ret;
ret.reserve(route_config_providers_.size());
for (const auto& element : route_config_providers_) {
ret.push_back(element.second.lock());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to include the failed locks here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, that's what I thought.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

LGTM modulo comments.


// RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>.
std::string map_identifier = config.getString("route_config_name") + "_";
map_identifier += config.getString("cluster");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sure, or some other distinguished identifier (e.g. : can't appear in the cluster name.

return std::move(new_provider);
}

return it->second.lock();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

std::vector<Router::RouteConfigProviderSharedPtr> ret;
ret.reserve(route_config_providers_.size());
for (const auto& element : route_config_providers_) {
ret.push_back(element.second.lock());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you considered using the shared_ptr use_count() method to validate the behaviors around reference counting?

@mattklein123
Copy link
Copy Markdown
Member

Please also merge master which will fix ASAN issue. I will take a look after you have addressed @htuch comments. Thanks.

@junr03
Copy link
Copy Markdown
Member Author

junr03 commented Aug 8, 2017

@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 ``:``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed. Don't think there is any explicit reason to block ':' ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@mattklein123
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

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 ``:``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed. Don't think there is any explicit reason to block ':' ?

// of this code, locking the weak_ptr will not fail.
Router::RouteConfigProviderSharedPtr provider = element.second.lock();
ASSERT(provider)
ret.push_back(std::move(provider));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

del std::move here as this is a shared_ptr


// RdsRouteConfigProviders are unique based on their <route_config_name>_<cluster>.
const std::string manager_identifier =
config.getString("route_config_name") + ":" + config.getString("cluster");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

new_provider->registerInitTarget(init_manager);

route_config_providers_.insert(
{manager_identifier, std::weak_ptr<RdsRouteConfigProviderImpl>(new_provider)});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I don't thinkstd::weak_ptr<RdsRouteConfigProviderImpl> is needed here as it should auto convert.

// 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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

del std::move

Copy link
Copy Markdown
Member Author

@junr03 junr03 Aug 8, 2017

Choose a reason for hiding this comment

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

@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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The compiler will optimize usually. It's not worth playing games like this IMO with shared pointers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok, got it.

@junr03
Copy link
Copy Markdown
Member Author

junr03 commented Aug 9, 2017

@mattklein123 for sake of finishing this PR, I am leaning into merging in the current state and doing a subsequent PR to use #1410.

htuch
htuch previously approved these changes Aug 9, 2017
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_,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: make_shared

Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Looks great. Few small nits remaining.


class RouteConfigProviderManagerImplTest : public testing::Test {
public:
RouteConfigProviderManagerImplTest() {}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: del empty constructor/destructor

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

typo "porinted"

@junr03
Copy link
Copy Markdown
Member Author

junr03 commented Aug 9, 2017

@mattklein123 updated.

@mattklein123
Copy link
Copy Markdown
Member

@junr03 still need to fix @htuch nit.

Jose Nino added 2 commits August 9, 2017 14:52
@mattklein123 mattklein123 merged commit ef8f0e0 into master Aug 9, 2017
@mattklein123 mattklein123 deleted the http-route-manager branch August 9, 2017 21:40
jpsim pushed a commit that referenced this pull request Nov 28, 2022
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]>
jpsim pushed a commit that referenced this pull request Nov 29, 2022
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]>
mathetake added a commit that referenced this pull request Mar 3, 2026
**Description**

This fixes the broken table in #1257.

Signed-off-by: Takeshi Yoneda <[email protected]>
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