-
Notifications
You must be signed in to change notification settings - Fork 378
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
feat(common): add support for call-tree-specific options #7669
Conversation
Provide access to the prevailing `Options` for an operation without having to plumb function parameters through the various and sundry layers, like connection. This also means that helper libraries can determine things like whether a tracing component is enabled, without having to distort their APIs. For example, if all `ServiceClient::Operation()` calls are implemented like ``` class ServiceClient { public ServiceClient(std::shared_ptr<ServiceConnection> connection, Options options = {}) : connection_(std::move(connection)), options_(ServiceDefaultOptions(std::move(options))) {} T Operation(..., Options options = {}) { internal::OptionsSpan span(internal::MergeOptions(options, options_)); ... } private: std::shared_ptr<ServiceConnection> connection_; Options options_; }; ``` then the connection layer, or any other internal library, can retrieve the prevailing options using `internal::CurrentOptions()`. If the operation is asynchronous, the invocation-time `Options` are also installed during its callbacks.
Google Cloud Build Logs
ℹ️ NOTE: Kokoro logs are linked from "Details" below. |
Codecov Report
@@ Coverage Diff @@
## main #7669 +/- ##
==========================================
- Coverage 95.28% 95.28% -0.01%
==========================================
Files 1254 1254
Lines 113312 113401 +89
==========================================
+ Hits 107969 108052 +83
- Misses 5343 5349 +6
Continue to review full report at Codecov.
|
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.
A suggestion for a different implementation which might be (maybe) a more targeted change and easier to maintain as we add asynchronous operations.
@@ -150,10 +151,12 @@ class AsyncReadStreamImpl | |||
private: | |||
void Cancel() override {} // LCOV_EXCL_LINE | |||
bool Notify(bool ok) override { | |||
OptionsSpan span(options_); |
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 wonder if we should set these spans in the completion queue instead. The Start()
function is a good place to capture the current options:
google-cloud-cpp/google/cloud/internal/default_completion_queue_impl.cc
Lines 238 to 241 in b92933b
void DefaultCompletionQueueImpl::StartOperation( | |
std::unique_lock<std::mutex> lk, std::shared_ptr<AsyncGrpcOperation> op, | |
absl::FunctionRef<void(void*)> start) { | |
void* tag = op.get(); |
and the notifications would be a good place to create the span:
google-cloud-cpp/google/cloud/internal/default_completion_queue_impl.cc
Lines 175 to 179 in b92933b
auto op = FindOperation(tag); | |
++notify_counter_; | |
if (op->Notify(ok)) { | |
ForgetOperation(tag); | |
} |
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 considered that, but thought that changing the tag
map (which is really just a collection of AsyncGrpcOperation
s) to a map of AsyncGrpcOperation
/Options
pairs was both a little more complicated, and belied the fact that the Options
logically belonged to the operation.
It is certainly the case that the behavior is now distributed to the AsyncGrpcOperation
sub-classes, but I've been thinking of that as a feature. For example, the existence of the internal::CompletionQueueImpl
interface implies that this behavior should not belong in a particular instantiation (the DefaultCompletionQueueImpl
) of that interface.
Perhaps experience with this will lead to a better understanding, but for now I'd vote for leaving it as 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.
For example, the existence of the
internal::CompletionQueueImpl
interface implies that this behavior should not belong in a particular instantiation (theDefaultCompletionQueueImpl
) of that interface.
FWIW, the only reason for the interface is mocking 🤷
Perhaps experience with this will lead to a better understanding, but for now I'd vote for leaving it as is.
SGTM.
google/cloud/options.cc
Outdated
// The prevailing options for the current operation. Thread local, so | ||
// additional propagation must be done whenever work for the operation | ||
// is done in another thread. | ||
thread_local Options current_options; |
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.
This obviously has a non-trivial destructor. I am certain you know better than I do whether this is accepted by the Google Style Guide. Just asking to maybe learn something.
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 non-trivial destructor for the namespace-scoped object is not a problem because the object is only accessible to its thread, and is not destroyed until the thread terminates. So, there is no issue with destruction order, and indeed the Style Guide says as much.
What the Style Guide still does require for namespace-scoped objects, thread local or not, is a compile-time-constant initializer. In the thread-local case I guess this is to guard against threads being created before global initialization is complete. The workaround is the same in both cases ... to convert the namespace-scoped object to a function local, so that it is initialized on the first call, no matter when that occurs. I've done that. PTAL.
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.
Ack, thanks for looking into 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.
Looks like a nice solution that's hidden from our users but will make our implementation plumbing much easier. Thanks! Just a couple small questions. The PR LGTM, but I'll let Carlos do the real review.
~OptionsSpan(); | ||
|
||
private: | ||
Options opts_; |
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: If I understand this correctly, opts_
is actually the previous options that will be restored after the span goes out of scope. That is, opts_
is not the same as opts
, which was passed to the c'tor. If you think this distinction is worth capturing, consider renaming this data member to something like restore_
or old_opts_
or something.
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 considered that, but concluded that the meaning of the member changes with time. For the majority of its life it is indeed the "options to restore", but after the initializer list is complete it is the "options to install" (i.e., it is the opts
passed to the constructor), and after restoration it is a "moved from options". All in all, it seemed better to give it a generic name, and let the code specify its meaning at any point.
* | ||
* @param opts the `Options` to install. | ||
*/ | ||
class ABSL_MUST_USE_RESULT OptionsSpan { |
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.
Q: Is ABSL_MUST_USE_RESULT
needed here, or nice to have, or... what's this here 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.
It is there to try to call out usages like OptionsSpan(opts);
where you forget the variable name, and the temporary span dies at the semicolon. For example, see https://github.com/abseil/abseil-cpp/blob/master/absl/base/attributes.h#L410-L414. (Previously we might have used a macro trick to do something similar.)
That said, when I tried to test that, it didn't seem to trigger a warning. But I'm not sure that wasn't just an artifact of which compiler+version I used, so I decided to leave it anyway. But, happy to remove also.
Google Cloud Build Logs
ℹ️ NOTE: Kokoro logs are linked from "Details" below. |
Provide access to the prevailing
Options
for an operation without havingto plumb function parameters through the various and sundry layers, like
connection.
This also means that helper libraries can determine things like whether a
tracing component is enabled, without having to distort their APIs.
For example, if all
ServiceClient::Operation()
calls are implemented likethen the connection layer, or any other internal library, can retrieve the
prevailing options using
internal::CurrentOptions()
. If the operation isasynchronous, the invocation-time
Options
are also installed during itscallbacks.
This change is