-
Notifications
You must be signed in to change notification settings - Fork 5.1k
admin: improve infrastructure #3172
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
admin: improve infrastructure #3172
Conversation
Signed-off-by: trabetti <talis@il.ibm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for splitting this out. Looks great. No action called for on my one comment: just pointing that out in general :)
@@ -150,6 +150,16 @@ Http::FilterTrailersStatus AdminFilter::decodeTrailers(Http::HeaderMap&) { | |||
return Http::FilterTrailersStatus::StopIteration; | |||
} | |||
|
|||
void AdminFilter::onDestroy() { | |||
for (const auto& callback : on_destroy_callbacks_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the record, generally we shouldn't use 'auto' as the type of a range variable when looping over a simple (non-map) container. See
https://google.github.io/styleguide/cppguide.html#auto
However in this case it's OK because the usage makes it pretty obvious what you've got.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for splitting this out! Could we also add the Server::Instance
interface change that was in the Hystrix PR:
virtual std::chrono::milliseconds statsFlushInterval() PURE;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM, small interface comment/nit.
include/envoy/server/admin.h
Outdated
@@ -15,6 +15,7 @@ | |||
namespace Envoy { | |||
namespace Server { | |||
|
|||
class AdminFilter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'm not in favor of exposing out the AdminFilter object in this manner into the abstract includes. Can we add an AdminFilter
or AdminStream
interface here and then fill it in with relevant callbacks? Then the existing admin filter can implement this interface as needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm. @trabetti I'm glad we're iterating on this in this small PR. Can you make Matt's suggested changes, and I'll take another look.
@mattklein123 in a past life I might have called this new interface the RequestContext, except for the callback-array, which feels more specialized to the admin domain, but I'm not sure. But AdminStream is probably fine too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either is fine with me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattklein123 @jmarantz - I pushed a fix. Not sure if what I did is what you meant here. Please review.
Signed-off-by: trabetti <talis@il.ibm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like the tests are broken with a compile error.
include/envoy/server/admin.h
Outdated
virtual void setEndStreamOnComplete(const bool& end_stream) PURE; | ||
virtual void addOnDestroyCallback(std::function<void()> cb) PURE; | ||
virtual Http::StreamDecoderFilterCallbacks* getDecoderFilterCallbacks() PURE; | ||
virtual const Http::HeaderMap* getRequestHeaders() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
virtual const Http::HeaderMap& getRequestHeaders() const PURE;
include/envoy/server/admin.h
Outdated
virtual ~AdminStream() {} | ||
virtual void setEndStreamOnComplete(const bool& end_stream) PURE; | ||
virtual void addOnDestroyCallback(std::function<void()> cb) PURE; | ||
virtual Http::StreamDecoderFilterCallbacks* getDecoderFilterCallbacks() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
virtual const Http::StreamDecoderFilterCallbacks& getDecoderFilterCallbacks() const PURE;
return callbacks by const ref, and make the method const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes in general this is great. Some small comments and please check the CI errors, but otherwise is on the right track. Thank you!
include/envoy/server/admin.h
Outdated
class AdminStream { | ||
public: | ||
virtual ~AdminStream() {} | ||
virtual void setEndStreamOnComplete(const bool& end_stream) PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you doc comment all these methods?
include/envoy/server/admin.h
Outdated
Buffer::Instance& data) -> Http::Code { \ | ||
return X(path_and_query, response_headers, data); \ | ||
Buffer::Instance& data, AdminStream& admin_filter) -> Http::Code { \ | ||
return X(path_and_query, response_headers, data, admin_filter); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/admin_filter/admin_stream here and elsewhere
source/server/http/admin.h
Outdated
@@ -228,12 +235,14 @@ class AdminImpl : public Admin, | |||
/** | |||
* A terminal HTTP filter that implements server admin functionality. | |||
*/ | |||
class AdminFilter : public Http::StreamDecoderFilter, Logger::Loggable<Logger::Id::admin> { | |||
class AdminFilter : public Http::StreamDecoderFilter, | |||
AdminStream, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public AdminStream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after addressing @mattklein123 and @jmarantz's comments.
Signed-off-by: trabetti <talis@il.ibm.com>
include/envoy/server/admin.h
Outdated
|
||
/** | ||
* @param end_stream set to false for streaming response. Default is true, which will close | ||
* connection on socket |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
End sentences with period, here & below.
include/envoy/server/admin.h
Outdated
* @return Http::StreamDecoderFilterCallbacks* to be used by the handler to get socket for data | ||
* streaming | ||
*/ | ||
virtual const Http::StreamDecoderFilterCallbacks* getDecoderFilterCallbacks() const PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you either make a guarantee that the return-value is non-null by returning a ref, or documenting that nullptr might be an actual return-value?
I think that in the actual usage model, there's no way for null to be returned in the context of an admin callback to which the stream object is passed. However, I can see that AdminFilter does have these null on construction.
So what's the correct way to use this, and getRequestHeaders()?
One idea is you can make this type-safe by having an StreamImpl object which contains a pointer to the AdminFilter as a delegate, and implements these methods with guarantees implied by returning const ref for the callbacks and requestHeaders, know that at the time is is called, those are guaranteed non-null.
In that case you wouldn't multiply inherit from AdminFilter to AdminStream.
This is a slightly more complex implementation, but a much simpler-to-describe behavior. With what you have, I think you can't make the guarantee without explaining when it's safe to assume non-null, or forcing the caller to check for nullptr, creating hard-to-test paths through the code.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmarantz I do not feel I am familiar enough with the behavior to decide whether we can guarantee a non-null. Maybe someone else can comment on this. Can you be more explicit about the suggested solution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RE guarantees: look at the call-site for runCallback in AdminFilter::onComplete, and you'll see that there has to be non-null request-headers there (there's a RELEASE_ASSERT). At that point you could create the AdminStream object on the stack with the non-null request-headers ref and pass it to runCallback, rather than 'this' (having cut the multiple-inheritance relationship).
It looks to me also that callbacks_ has to be non-null at that point as well, or the call to encodeHeaders will segv.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re code-coverage, ideally each level of abstraction should have its own covering unit tests, and not rely on some other complex system to test it. Is there some reason why this would be hard to test in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the idea of changing the pointer to const ref.
Just to be sure: the variables callbacks_ and request_headers_ are not initialized in the constructor (like parent_ variable). Therefore, they were defined as pointers and couldn’t be defined as const ref.
Do you suggest holding them in the class as pointers, and return them as const ref, after a null checking, in the getter functions (getDecoderFilterCallbacks and getRequestHeaders)?
Thanks in advance!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I suggest:
- have AdminStream be a pure base class, not part of a multi-inheritance mesh.
- have the callbacks & req-headers be const refs member vars of AdminStreamImpl
- construct AdminStreamImpl in AdminFilters::onComplete where it's clear you have those
available non-null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re code-coverage: it will be hard to test the onDestroy() code with added callbacks. I could use addHandler() to add a new handler that adds a callback. The two relevant tests are admin_test and integration_admin_test. In admin_test I have an admin object but no requests, so onDestroy() is not called. In integration_admin_test onDestroy() is called but I don't have an admin object.
I may be missing a simple solution.. it looks to me that it is complicated to build such test, but hystrix_test should cover it, that's why I asked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand there are not already existing tests, but I'm still not clear why it's difficult to make one.
I definitely think it's better to cover each level of abstraction on its own and not wait for its application with a higher level integration to be able to test it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay -- I wanted to give you an exact answer and needed to poke around on a larger screen to point you to the call-site.
include/envoy/server/admin.h
Outdated
* @return Http::StreamDecoderFilterCallbacks* to be used by the handler to get socket for data | ||
* streaming | ||
*/ | ||
virtual const Http::StreamDecoderFilterCallbacks* getDecoderFilterCallbacks() const PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RE guarantees: look at the call-site for runCallback in AdminFilter::onComplete, and you'll see that there has to be non-null request-headers there (there's a RELEASE_ASSERT). At that point you could create the AdminStream object on the stack with the non-null request-headers ref and pass it to runCallback, rather than 'this' (having cut the multiple-inheritance relationship).
It looks to me also that callbacks_ has to be non-null at that point as well, or the call to encodeHeaders will segv.
include/envoy/server/admin.h
Outdated
* @return Http::StreamDecoderFilterCallbacks* to be used by the handler to get socket for data | ||
* streaming | ||
*/ | ||
virtual const Http::StreamDecoderFilterCallbacks* getDecoderFilterCallbacks() const PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re code-coverage, ideally each level of abstraction should have its own covering unit tests, and not rely on some other complex system to test it. Is there some reason why this would be hard to test in this case?
Signed-off-by: trabetti <talis@il.ibm.com>
source/server/http/admin.h
Outdated
std::list<std::function<void()>>& on_destroy_callbacks) | ||
: callbacks_(callbacks), request_headers_(request_headers), | ||
on_destroy_callbacks_(on_destroy_callbacks){}; | ||
void setEndStreamOnComplete(const bool& end_stream) override { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for a const reference here, just take the bool by value.
auto callback = [](absl::string_view, Http::HeaderMap&, Buffer::Instance&, | ||
Server::AdminStream& admin_stream) -> Http::Code { | ||
auto on_destroy_callback = []() { | ||
// TODO (@trabetti) : any good idea what to do in this callback that can be checked? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set a bool declared in the test-method.
test/server/http/admin_test.cc
Outdated
admin_filter_.decodeHeaders(request_headers_, false); | ||
return admin_.runCallback(path_and_query, response_headers, response, admin_filter_); | ||
AdminStreamImpl admin_stream(callbacks, request_headers_, on_destroy_callbacks); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm...I'm wondering, when you integrate Hystrix, if this will be the right model. Will your callback to initiate the hystrix callback never return until the client closes the connection, so that the AdminStreamImpl has the right lifetime?
include/envoy/server/instance.h
Outdated
/** | ||
* @return the flush interval of stats sinks. | ||
*/ | ||
virtual std::chrono::milliseconds statsFlushInterval() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const method, here & the override
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation calls statsFlushInterval() of config, which is not a const ref, I should change there too, would that be ok?
virtual std::chrono::milliseconds statsFlushInterval() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely an oversight, looking at the only impl in source/server/configuration_impl.h and the similar functions declared above & below. Let's fix there and in the interface, rather than propagating the issue.
// EXPECT_STREQ("200", response->headers().Status()->value().c_str()); | ||
// EXPECT_EQ(spdlog::level::trace, Logger::Registry::getLog(Logger::Id::assert).level()); | ||
// // TODO (@trabetti) : what can be checked on response headers? | ||
//} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the story here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried to see if there's a way to test end_stream = false. but then the response never returns.
Signed-off-by: trabetti <talis@il.ibm.com>
Signed-off-by: trabetti <talis@il.ibm.com>
include/envoy/server/admin.h
Outdated
@@ -36,14 +70,18 @@ class Admin { | |||
|
|||
/** | |||
* Callback for admin URL handlers. | |||
* @param url supplies the URL prefix to install the handler for. | |||
* @param path_and_query supplies the URL prefix to install the handler for. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this was the existing condition, but I think this param description is not right. Something like "the path and query of the request URL"
include/envoy/server/instance.h
Outdated
/** | ||
* @return the flush interval of stats sinks. | ||
*/ | ||
virtual std::chrono::milliseconds statsFlushInterval() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely an oversight, looking at the only impl in source/server/configuration_impl.h and the similar functions declared above & below. Let's fix there and in the interface, rather than propagating the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job getting the coverage. Almost there. Just some nits and one optional suggestion around clarifying ownership.
include/envoy/server/configuration.h
Outdated
@@ -73,7 +73,7 @@ class Main { | |||
* @return std::chrono::milliseconds the time interval between flushing to configured stat sinks. | |||
* The server latches counters. | |||
*/ | |||
virtual std::chrono::milliseconds statsFlushInterval() PURE; | |||
virtual const std::chrono::milliseconds& statsFlushInterval() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why return a const ref here? Most of the other time-returning things I see in Envoy are returning by value.
@@ -89,6 +89,10 @@ class ValidationInstance : Logger::Loggable<Logger::Id::main>, | |||
ThreadLocal::Instance& threadLocal() override { return thread_local_; } | |||
const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } | |||
|
|||
const std::chrono::milliseconds& statsFlushInterval() override { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const method per discussion in outdated conversation. You will have to fix the underlying interface and impl.
not const ref return though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I should have paid attention. will correct all these.
include/envoy/server/instance.h
Outdated
/** | ||
* @return the flush interval of stats sinks. | ||
*/ | ||
virtual const std::chrono::milliseconds& statsFlushInterval() PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const method; not const ref return.
source/server/configuration_impl.h
Outdated
@@ -127,7 +127,7 @@ class MainImpl : Logger::Loggable<Logger::Id::config>, public Main { | |||
Tracing::HttpTracer& httpTracer() override { return *http_tracer_; } | |||
RateLimit::ClientFactory& rateLimitClientFactory() override { return *ratelimit_client_factory_; } | |||
std::list<Stats::SinkPtr>& statsSinks() override { return stats_sinks_; } | |||
std::chrono::milliseconds statsFlushInterval() override { return stats_flush_interval_; } | |||
const std::chrono::milliseconds& statsFlushInterval() override { return stats_flush_interval_; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const ref, not const ref return.
source/server/http/admin.h
Outdated
*/ | ||
class AdminStreamImpl : public AdminStream { | ||
public: | ||
AdminStreamImpl(Http::StreamDecoderFilterCallbacks& callbacks, Http::HeaderMap& request_headers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth commenting here that the ctor args are held by reference and must be held in a data structure that outlasts the stream. Another thought I had was to have the AdminFilter own the AdminStreamImpl as a std::unique_ptr, which would be the only location for these objects. That might be easier to reason about in terms of correctness. It would populate the AdminStreamImpl in the same place it does now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, will look into it, I just need to check with the Hystrix implementation that it doesn't break anything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super opinionated about who needs to own the admin stream since either way it will be possible to be left with a dangling reference without explicitly destroying something IIUC (in once case it will be the stream, in the other it will be the callbacks). However, as @jmarantz suggests, in either case, please document how one would know that the reference is invalidated (using onDestryCallabacks()
).
test/server/http/admin_test.cc
Outdated
}; | ||
|
||
// Add non removable handler. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nicer to make separate testcases for these. You could generate actually make the callback be a static method of AdminInstanceTest to share it between the methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it is better to cover all these setters/getters in one test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best practice is to make each individual test just test one thing. The tests will each be small if you factor out the callback generator into a static helper method.
Signed-off-by: trabetti <talis@il.ibm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nits & personal prefs. But I'll leave it up to you.
test/server/http/admin_test.cc
Outdated
}; | ||
|
||
// Add non removable handler. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best practice is to make each individual test just test one thing. The tests will each be small if you factor out the callback generator into a static helper method.
test/server/http/admin_test.cc
Outdated
} | ||
|
||
TEST_P(AdminInstanceTest, RejectHandlerWithXss) { | ||
auto callback = [](absl::string_view, Http::HeaderMap&, Buffer::Instance&, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could factor this helper callback generator into a simple static test method, since it is used 3x (previously 2x)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM but some comments on comments.
source/server/http/admin.h
Outdated
@@ -252,6 +283,8 @@ class AdminFilter : public Http::StreamDecoderFilter, Logger::Loggable<Logger::I | |||
AdminImpl& parent_; | |||
Http::StreamDecoderFilterCallbacks* callbacks_{}; | |||
Http::HeaderMap* request_headers_{}; | |||
std::unique_ptr<std::list<std::function<void()>>> on_destroy_callbacks_ = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I don't think it's really worth optimizing this to create the list when needed, as this is admin code. I would just remove the unique_ptr here and always have the list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure? Not sure it will have any use beside in the Hystrix handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel very strongly about it, but this makes the code harder to read and I don't think the slightly smaller CPU/memory use matters here.
include/envoy/server/admin.h
Outdated
virtual ~AdminStream() {} | ||
|
||
/** | ||
* @param end_stream set to false for streaming response. Default is true, which will close |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"which will close connection on socket" is misleading and not necessarily true if the admin connection were kept alive, which is possible with the existing code. I would rephrase to make it clear that this is really about ending the response when the initial handler completes or something like that.
include/envoy/server/admin.h
Outdated
|
||
/** | ||
* @param cb callback to be added to the list of callbacks invoked by onDestroy() when connection | ||
* is dropped. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment. Connection is not necessarily dropped. When the stream is closed is more accurate.
include/envoy/server/admin.h
Outdated
virtual void addOnDestroyCallback(std::function<void()> cb) PURE; | ||
|
||
/** | ||
* @return Http::StreamDecoderFilterCallbacks* to be used by the handler to get socket for data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not really getting socket data here. We are getting HTTP request data, etc. I would rephrase.
Signed-off-by: trabetti <talis@il.ibm.com>
include/envoy/server/admin.h
Outdated
* @param cb callback to be added to the list of callbacks invoked by onDestroy() when connection | ||
* is closed. | ||
*/ | ||
virtual void addOnDestroyCallback(std::function<void()> cb) PURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/taste: I feel like the 'On' here and the 'Callback' both add the same info, and it would be slightly nicer to call this 'addDestroyCallback', but I'm fine as it is too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the comment. I prefer to keep the name since it references the onDestroy() method, where the callbacks are being used.
source/server/http/admin.h
Outdated
bool endStreamOnComplete() const override { return end_stream_on_complete_; } | ||
|
||
private: | ||
const Http::StreamDecoderFilterCallbacks& callbacks_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment here too about who owns this, if you prefer not to try to have AdminStream be the sole owner (with AdminFilter owning the AdminStream as a std::unique_ptr) as I suggested above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verified this against the intended hystrix implementation. working. pushing a fix.
Signed-off-by: trabetti <talis@il.ibm.com>
source/server/http/admin.cc
Outdated
Http::Code code = parent_.runCallback(path, *header_map, response, admin_stream); | ||
std::unique_ptr<AdminStreamImpl> admin_stream = | ||
std::make_unique<AdminStreamImpl>(*callbacks_, *request_headers_, on_destroy_callbacks_); | ||
Http::Code code = parent_.runCallback(path, *header_map, response, *admin_stream); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my suggestion was not clear. I was suggesting you make this unique_ptr be a member variable of AdminFilter, and should own the headers, callbacks, and destroy-callbacks exclusively.
The existing of this as an independent class referencing member variables of AdminStream seemed perilous and I think that change makes the ownership semantics much clearer and easy to reason about, without affecting the actual flow of the logic.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind this change. But need to check if callbacks can be on a unique pointer, since it points to outside admin.
In general I feel that holding onto refs to member vars of other classes is hard to reason about. But it we were already doing that and your PR doesn't make it worse, then for callbacks it makes sense to leave then as a ref, but still doing as I suggested for the objects created in runCallback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything LGTM except I think the reference invalidation semantics need to be documented a bit more.
source/server/http/admin.h
Outdated
*/ | ||
class AdminStreamImpl : public AdminStream { | ||
public: | ||
AdminStreamImpl(Http::StreamDecoderFilterCallbacks& callbacks, Http::HeaderMap& request_headers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super opinionated about who needs to own the admin stream since either way it will be possible to be left with a dangling reference without explicitly destroying something IIUC (in once case it will be the stream, in the other it will be the callbacks). However, as @jmarantz suggests, in either case, please document how one would know that the reference is invalidated (using onDestryCallabacks()
).
@jmarantz : I still think of the AdminStream as a compact way to pass parameters to and from the handlers. Making it a class member and moving members used by AdminFilter to be owned by the AdminStream seems to me more confusing. |
Any time you have a data structure that contains references or pointers to member variables owned by other data structures, it casts suspicion and doubt and forces the reader to look very carefully. That is why I believe clean ownership semantics are better, and less confusing. I also suggested in this PR to call this 'RequestContext' rather than 'AdminStream', and it might grow into a useful abstraction beyond this over time. In many other server architectures I've used in the past, a request-context is where this sort of data would live. But I don't have the breadth of knowledge of Envoy's structures yet to know how that would fit in more broadly. However as I said initially when I made this suggestion, I'm also OK with commenting the lifetime of what each member variable is referring to is, particularly because as @mrice32 points out, at least one of the member variables would need lifetime beyond the AdminStream. |
source/server/http/admin.cc
Outdated
@@ -653,7 +663,11 @@ void AdminFilter::onComplete() { | |||
Buffer::OwnedImpl response; | |||
Http::HeaderMapPtr header_map{new Http::HeaderMapImpl}; | |||
RELEASE_ASSERT(request_headers_); | |||
Http::Code code = parent_.runCallback(path, *request_headers_, *header_map, response); | |||
|
|||
std::unique_ptr<AdminStreamImpl> admin_stream = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion to make this a unique_ptr rather than instantiate it on-stack only makes sense if you are going to hold the unique_ptr in the AdminStream, moving the ownership of header_map and response along with it.
If you are going to stay with the structure as is, you might as well just instantiate the AdminStream on the stack here. You are not releasing it or std::move()ing it either, so unique_ptr locally here doesn't have much impact.
Signed-off-by: trabetti <talis@il.ibm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like on the right track, thanks for the detailed review. A couple of comments.
include/envoy/server/admin.h
Outdated
virtual void setEndStreamOnComplete(bool end_stream) PURE; | ||
|
||
/** | ||
* @param cb callback to be added to the list of callbacks invoked by onDestroy() when connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember if I commented on this before, but this is not getting called with the connection is closed, it's getting called when the stream is closed. Can you update the comment?
source/server/http/admin.cc
Outdated
@@ -653,7 +663,10 @@ void AdminFilter::onComplete() { | |||
Buffer::OwnedImpl response; | |||
Http::HeaderMapPtr header_map{new Http::HeaderMapImpl}; | |||
RELEASE_ASSERT(request_headers_); | |||
Http::Code code = parent_.runCallback(path, *request_headers_, *header_map, response); | |||
|
|||
AdminStreamImpl admin_stream(*callbacks_, *request_headers_, on_destroy_callbacks_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, I haven't been tracking this lengthy conversation, but why doesn't AdminFilter implement AdminStream and then you just pass *this
? That is the correct ownership model anyway IMO and avoids all of the other concerns? cc @jmarantz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started with that model and then changed it in response to feedback. If it makes more sense we can go back to that. @mattklein123 @jmarantz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had one issue with that model which is that there are some lazy-initialized member vars, including request_headers_. AdminStreamImpl (in the current code) is only live when request_headers_ is non-null, but AdminFilter would satisfy an AdminStream interface all the time, even though some of the data it provides access to is sometimes null.
So I think ownership is clear with that design, but not lifetime.
I think the clearest model for ownership and lifetime is to have AdminStreamImpl own the request headers (and maybe call it "RequestContext" instead of "AdminStream"), and it can be instantiated in AdminFilter::onComplete and stored in a unique_ptr in the AdminFilter.
However, I think I'm done bikeshedding this now :) I'm also OK with having the AdminFilter inherit from AdminStream and comment in AdminStream return objects whose lifetime is bounded more tightly than the AdminStream object, or having AdminStreamImpl capture references to member variables in other structures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I don't mean to rehash a huge conversation. Personally, I think AdminFilter implementing AdminStream is the clearest model, as the filter/stream is alive for the lifetime of the admin request, so that is what I would do, but if we want to start with this we can see how it goes. Though I don't see how this can be the final solution as being stack allocated, as for handlers that continue processing they will do so outside the context of onComplete, so something will need to change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it answers your question, but - In the Hystrix example, only the StreamDecoderFilterCallbacks is needed after onComplete is done with. It references to outside the AdminFilter. In Hystrix we keep pointer to it in the sink, so the pointer stay valid after the lifetime of onComplete(). The idea we agreed here is to handle this by adding methods to on_destroy_callbacks_ that will stop using the StreamDecoderFilterCallbacks pointer when onDestroy() is called. Adding this function should be done in the handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this is all needless complexity. Each filter lives for the live of the stream, along with associated request headers, stream decoder filter callbacks, etc. The filter is exactly the lifetime of what you want, and it should implement the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The StreamDecoderFilterCallbacks points to an object on ConnectionManagerImpl, and it is removed when the connection is closed (e.g. the hystrix dashboard is closed). Since for Hystrix we are doing the streaming using the sink timer, and we hold another pointer to the StreamDecoderFilterCallbacks , we must invalidate that pointer on the sink when onDestory() is called in the filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've already commented on this several times, but there is nothing happening here at the granularity of the connection. Only streams, and the filter object has the lifetime of the stream. Trust me, you want the filter to implement the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. Having the filter implement the interface should work perfectly for the Hystrix case. It will make the lifetime easier to understand, and it will incur the smallest code footprint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source/server/http/admin.h
Outdated
@@ -49,6 +49,9 @@ class AdminStreamImpl : public AdminStream { | |||
bool endStreamOnComplete() const override { return end_stream_on_complete_; } | |||
|
|||
private: | |||
// the callbacks member is a reference to a member of a ConnectionManagerImpl object. It is | |||
// destroyed when connection is terminated. handlers using it should invalidate the reference by | |||
// adding a method to be run when onDestroy() is invoked, using addOnDestroyCallback(). | |||
const Http::StreamDecoderFilterCallbacks& callbacks_; | |||
const Http::HeaderMap& request_headers_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar comments are also needed for request_headers_ and on_destroy_callbacks_. Basically any time you have a reference or pointer as a member variable you need to comment on ownership & lifetime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm assuming you add comments about lifetime on member reference vars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! A few minor nits on the reference comment.
source/server/http/admin.h
Outdated
bool endStreamOnComplete() const override { return end_stream_on_complete_; } | ||
|
||
private: | ||
// the callbacks member is a reference to a member of a ConnectionManagerImpl object. It is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: The callbacks
. Please capitalize the first letter of each sentence here and later in this comment.
source/server/http/admin.h
Outdated
|
||
private: | ||
// the callbacks member is a reference to a member of a ConnectionManagerImpl object. It is | ||
// destroyed when connection is terminated. handlers using it should invalidate the reference by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe reword the second sentence to to:
Handlers relying on the reference should use addOnDestroyCallback()
to add a callback that will notify them when the reference is no
longer valid.
Signed-off-by: trabetti <talis@il.ibm.com>
Just pushed a version with AdminFilter implementing AdminStream, @mattklein123 is this what you meant? |
@trabetti yes. Can you merge master and look at the remaining comments? Then I think we can get this merged. |
that would be great |
Confirmed; I'm fine with going back to the multiple-inheritance model; but returning references to the objects that must not be nullptr, using the pattern
|
Signed-off-by: trabetti <talis@il.ibm.com>
Signed-off-by: trabetti <talis@il.ibm.com>
…ure_merge_master Signed-off-by: trabetti <talis@il.ibm.com>
…ure_merge_master Signed-off-by: trabetti <talis@il.ibm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
include/envoy/server/admin.h
Outdated
virtual void addOnDestroyCallback(std::function<void()> cb) PURE; | ||
|
||
/** | ||
* @return Http::StreamDecoderFilterCallbacks* to be used by the handler to get HTTP request data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Http::StreamDecoderFilterCallbacks&
include/envoy/server/admin.h
Outdated
virtual const Http::StreamDecoderFilterCallbacks& getDecoderFilterCallbacks() const PURE; | ||
|
||
/** | ||
* @return Http::HeaderMap* to be used by handler to parse header information sent with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Http::HeaderMap&
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
any idea what is this tsan failure? |
Signed-off-by: trabetti <talis@il.ibm.com>
@trabetti unrelated. Fixed in master. |
Signed-off-by: trabetti talis@il.ibm.com
title: admin: add infrastructure, which will later be used for hystrix dashboard support
Description: split from PR #2736
AdminFilter&
to admin handlers, so that all its parameters can be used by the handlers, without the need to pass them as arguments. Also enables passing more data to future handlers without adding interface arguments.Risk Level: Medium
Testing:
Will need to add coverage tests