Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new OutstandingRpcs metric to track the instantaneous count of outstanding RPCs on selected channels within dynamic channel pools, updating the channel pool, decorators, and metrics exporter accordingly. The review feedback highlights several critical safety issues, specifically potential null pointer dereferences in bigtable_random_two_least_used_decorator.cc and operation_context.cc that could cause crashes. Additionally, the reviewer recommended optimizing string copies by using std::move in grpc_metrics_exporter.cc and simplifying a complex std::set_difference operation in metrics.cc with a more readable loop, aligning with the repository's style guide.
| Response AsyncHelper(std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool, | ||
| std::shared_ptr<OperationContext> const& operation_context, | ||
| std::function<Response(BigtableStub&)> fn) { | ||
| SelectedChannel<BigtableStub> selection = | ||
| pool->GetChannelRandomTwoLeastUsed(); | ||
| operation_context->StubSelection(StubSelectionParams{ | ||
| selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed, | ||
| pool->transport_type(), RpcType::kUnary}); | ||
| std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub(); | ||
| Response result = fn(*stub); | ||
| selection.channel->ReleaseStub(); | ||
| return result; | ||
| } |
There was a problem hiding this comment.
The operation_context parameter is a std::shared_ptr and can be nullptr (for example, when metrics are disabled or in certain unit tests). Dereferencing it directly without a null check will cause a crash. Please add a null check before calling StubSelection.
template <typename Response>
Response AsyncHelper(std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool,
std::shared_ptr<OperationContext> const& operation_context,
std::function<Response(BigtableStub&)> fn) {
SelectedChannel<BigtableStub> selection =
pool->GetChannelRandomTwoLeastUsed();
if (operation_context) {
operation_context->StubSelection(StubSelectionParams{
selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed,
pool->transport_type(), RpcType::kUnary});
}
std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub();
Response result = fn(*stub);
selection.channel->ReleaseStub();
return result;
}| template <typename Response> | ||
| std::unique_ptr<internal::StreamingReadRpc<Response>> StreamingHelper( | ||
| std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool, | ||
| std::function<std::unique_ptr< | ||
| google::cloud::internal::StreamingReadRpc<Response>>(BigtableStub&)> | ||
| std::shared_ptr<OperationContext> const& operation_context, | ||
| std::function<std::unique_ptr<internal::StreamingReadRpc<Response>>( | ||
| BigtableStub&)> | ||
| fn) { | ||
| auto child = pool->GetChannelRandomTwoLeastUsed(); | ||
| auto stub = child->AcquireStub(); | ||
| auto result = fn(*stub); | ||
| auto release_fn = [weak = child->MakeWeak()] { | ||
| auto child = weak.lock(); | ||
| SelectedChannel<BigtableStub> selection = | ||
| pool->GetChannelRandomTwoLeastUsed(); | ||
| operation_context->StubSelection(StubSelectionParams{ | ||
| selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed, | ||
| pool->transport_type(), RpcType::kStreaming}); | ||
| std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub(); | ||
| std::unique_ptr<internal::StreamingReadRpc<Response>> result = fn(*stub); | ||
| auto release_fn = [weak = selection.channel->MakeWeak()] { | ||
| std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock(); | ||
| if (child) child->ReleaseStub(); | ||
| }; | ||
| return std::make_unique<StreamingReadRpcTracking<Response>>( | ||
| std::move(result), std::move(release_fn)); | ||
| } |
There was a problem hiding this comment.
The operation_context parameter is a std::shared_ptr and can be nullptr. Dereferencing it directly without a null check will cause a crash. Please add a null check before calling StubSelection.
template <typename Response>
std::unique_ptr<internal::StreamingReadRpc<Response>> StreamingHelper(
std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool,
std::shared_ptr<OperationContext> const& operation_context,
std::function<std::unique_ptr<internal::StreamingReadRpc<Response>>(
BigtableStub&)>
fn) {
SelectedChannel<BigtableStub> selection =
pool->GetChannelRandomTwoLeastUsed();
if (operation_context) {
operation_context->StubSelection(StubSelectionParams{
selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed,
pool->transport_type(), RpcType::kStreaming});
}
std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub();
std::unique_ptr<internal::StreamingReadRpc<Response>> result = fn(*stub);
auto release_fn = [weak = selection.channel->MakeWeak()] {
std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock();
if (child) child->ReleaseStub();
};
return std::make_unique<StreamingReadRpcTracking<Response>>(
std::move(result), std::move(release_fn));
}| template <typename Response> | ||
| std::unique_ptr<google::cloud::internal::AsyncStreamingReadRpc<Response>> | ||
| AsyncStreamingHelper( | ||
| std::unique_ptr<internal::AsyncStreamingReadRpc<Response>> AsyncStreamingHelper( | ||
| std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool, | ||
| std::function<std::unique_ptr< | ||
| google::cloud::internal::AsyncStreamingReadRpc<Response>>( | ||
| std::shared_ptr<OperationContext> const& operation_context, | ||
| std::function<std::unique_ptr<internal::AsyncStreamingReadRpc<Response>>( | ||
| BigtableStub&)> | ||
| fn) { | ||
| auto child = pool->GetChannelRandomTwoLeastUsed(); | ||
| auto stub = child->AcquireStub(); | ||
| auto result = fn(*stub); | ||
| auto release_fn = [weak = child->MakeWeak()] { | ||
| auto child = weak.lock(); | ||
| SelectedChannel<BigtableStub> selection = | ||
| pool->GetChannelRandomTwoLeastUsed(); | ||
| operation_context->StubSelection(StubSelectionParams{ | ||
| selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed, | ||
| pool->transport_type(), RpcType::kStreaming}); | ||
| std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub(); | ||
| std::unique_ptr<internal::AsyncStreamingReadRpc<Response>> result = fn(*stub); | ||
| auto release_fn = [weak = selection.channel->MakeWeak()] { | ||
| std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock(); | ||
| if (child) child->ReleaseStub(); | ||
| }; | ||
| return std::make_unique<AsyncStreamingReadRpcTracking<Response>>( | ||
| std::move(result), std::move(release_fn)); | ||
| } |
There was a problem hiding this comment.
The operation_context parameter is a std::shared_ptr and can be nullptr. Dereferencing it directly without a null check will cause a crash. Please add a null check before calling StubSelection.
template <typename Response>
std::unique_ptr<internal::AsyncStreamingReadRpc<Response>> AsyncStreamingHelper(
std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool,
std::shared_ptr<OperationContext> const& operation_context,
std::function<std::unique_ptr<internal::AsyncStreamingReadRpc<Response>>(
BigtableStub&)>
fn) {
SelectedChannel<BigtableStub> selection =
pool->GetChannelRandomTwoLeastUsed();
if (operation_context) {
operation_context->StubSelection(StubSelectionParams{
selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed,
pool->transport_type(), RpcType::kStreaming});
}
std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub();
std::unique_ptr<internal::AsyncStreamingReadRpc<Response>> result = fn(*stub);
auto release_fn = [weak = selection.channel->MakeWeak()] {
std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock();
if (child) child->ReleaseStub();
};
return std::make_unique<AsyncStreamingReadRpcTracking<Response>>(
std::move(result), std::move(release_fn));
}| template <typename Request, typename Response> | ||
| std::unique_ptr<google::cloud::AsyncStreamingReadWriteRpc<Request, Response>> | ||
| std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>> | ||
| AsyncStreamingHelper( | ||
| std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool, | ||
| std::function<std::unique_ptr<google::cloud::AsyncStreamingReadWriteRpc< | ||
| Request, Response>>(BigtableStub&)> | ||
| std::shared_ptr<OperationContext> const& operation_context, | ||
| std::function< | ||
| std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>>( | ||
| BigtableStub&)> | ||
| fn) { | ||
| auto child = pool->GetChannelRandomTwoLeastUsed(); | ||
| auto stub = child->AcquireStub(); | ||
| auto result = fn(*stub); | ||
| auto release_fn = [weak = child->MakeWeak()] { | ||
| auto child = weak.lock(); | ||
| SelectedChannel<BigtableStub> selection = | ||
| pool->GetChannelRandomTwoLeastUsed(); | ||
| operation_context->StubSelection(StubSelectionParams{ | ||
| selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed, | ||
| pool->transport_type(), RpcType::kStreaming}); | ||
| std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub(); | ||
| std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>> result = | ||
| fn(*stub); | ||
| auto release_fn = [weak = selection.channel->MakeWeak()] { | ||
| std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock(); | ||
| if (child) child->ReleaseStub(); | ||
| }; |
There was a problem hiding this comment.
The operation_context parameter is a std::shared_ptr and can be nullptr. Dereferencing it directly without a null check will cause a crash. Please add a null check before calling StubSelection.
template <typename Request, typename Response>
std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>>
AsyncStreamingHelper(
std::shared_ptr<DynamicChannelPool<BigtableStub>>& pool,
std::shared_ptr<OperationContext> const& operation_context,
std::function<
std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>>(
BigtableStub&)>
fn) {
SelectedChannel<BigtableStub> selection =
pool->GetChannelRandomTwoLeastUsed();
if (operation_context) {
operation_context->StubSelection(StubSelectionParams{
selection.outstanding_rpcs, ChannelPoolLbPolicy::kRandomTwoLeastUsed,
pool->transport_type(), RpcType::kStreaming});
}
std::shared_ptr<BigtableStub> stub = selection.channel->AcquireStub();
std::unique_ptr<AsyncStreamingReadWriteRpc<Request, Response>> result =
fn(*stub);
auto release_fn = [weak = selection.channel->MakeWeak()] {
std::shared_ptr<ChannelUsage<BigtableStub>> child = weak.lock();
if (child) child->ReleaseStub();
};| if (filtered_data_labels.empty()) { | ||
| labels.insert(data.begin(), data.end()); | ||
| return labels; | ||
| } | ||
|
|
||
| struct Compare { | ||
| bool operator()(std::pair<std::string const, std::string> const& a, | ||
| std::string const& b) { | ||
| return a.first < b; | ||
| } | ||
|
|
||
| bool operator()(std::string const& a, | ||
| std::pair<std::string const, std::string> const& b) { | ||
| return a < b.first; | ||
| } | ||
| }; | ||
|
|
||
| std::set_difference(data.begin(), data.end(), filtered_data_labels.begin(), | ||
| filtered_data_labels.end(), | ||
| std::inserter(labels, labels.begin()), Compare()); |
There was a problem hiding this comment.
This block uses std::set_difference with a custom local comparator struct to filter out keys from a map that are present in a set. This is overly complex and represents 'Code Poetry'. We can replace this entire block with a simple, readable, and idiomatic loop.
for (auto const& kv : data) {
if (filtered_data_labels.find(kv.first) == filtered_data_labels.end()) {
labels.emplace(kv.first, kv.second);
}
}
return labels;References
- Dismantle complex abstractions used for simple tasks. Prefer simplicity over cleverness. (link)
| } | ||
|
|
||
| return MonitoredResourceResult{std::move(project_id), std::move(resource)}; | ||
| return MonitoredResourceResult{project_id, std::move(resource)}; |
There was a problem hiding this comment.
The local variable project_id is returned as part of MonitoredResourceResult which is constructed inline. Since NRVO does not apply here, not moving project_id results in an unnecessary copy of the string. Please use std::move to avoid this copy.
return MonitoredResourceResult{std::move(project_id), std::move(resource)};References
- Scrutinize copies of non-fundamental C++ types. Is it necessary to copy the data? Can we move the data instead? (link)
| for (auto const& m : metrics) { | ||
| v.emplace_back(m->clone(resource_labels, data_labels)); | ||
| auto clone = m->clone(resource_labels, data_labels); | ||
| if (!clone) { | ||
| clone = m->clone(client_resource_labels); | ||
| } | ||
| if (clone) { | ||
| v.emplace_back(std::move(clone)); | ||
| } | ||
| } |
There was a problem hiding this comment.
If any element in the metrics vector is nullptr, calling m->clone will result in a null pointer dereference and crash. Please add a null check for m before cloning.
for (auto const& m : metrics) {
if (!m) continue;
auto clone = m->clone(resource_labels, data_labels);
if (!clone) {
clone = m->clone(client_resource_labels);
}
if (clone) {
v.emplace_back(std::move(clone));
}
}b612871 to
a6dabcc
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #16350 +/- ##
==========================================
- Coverage 92.24% 92.24% -0.01%
==========================================
Files 2227 2227
Lines 209551 209869 +318
==========================================
+ Hits 193293 193585 +292
- Misses 16258 16284 +26 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a6dabcc to
0b4c0a0
Compare
0b4c0a0 to
6eec3ae
Compare
No description provided.