mirror of https://github.com/grpc/grpc.git
Merge pull request #22101 from markdroth/lb_graceful_switcher
Refactor code for gracefully switching child LB policies.pull/22187/head
commit
5e98ad4077
21 changed files with 520 additions and 700 deletions
@ -0,0 +1,280 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/lb_policy/child_policy_handler.h" |
||||
|
||||
#include "absl/strings/str_cat.h" |
||||
|
||||
#include "src/core/ext/filters/client_channel/lb_policy_registry.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// ChildPolicyHandler::Helper
|
||||
//
|
||||
|
||||
class ChildPolicyHandler::Helper |
||||
: public LoadBalancingPolicy::ChannelControlHelper { |
||||
public: |
||||
explicit Helper(RefCountedPtr<ChildPolicyHandler> parent) |
||||
: parent_(std::move(parent)) {} |
||||
|
||||
~Helper() { parent_.reset(DEBUG_LOCATION, "Helper"); } |
||||
|
||||
RefCountedPtr<SubchannelInterface> CreateSubchannel( |
||||
const grpc_channel_args& args) override { |
||||
if (parent_->shutting_down_) return nullptr; |
||||
if (!CalledByCurrentChild() && !CalledByPendingChild()) return nullptr; |
||||
return parent_->channel_control_helper()->CreateSubchannel(args); |
||||
} |
||||
|
||||
void UpdateState(grpc_connectivity_state state, |
||||
std::unique_ptr<SubchannelPicker> picker) override { |
||||
if (parent_->shutting_down_) return; |
||||
// If this request is from the pending child policy, ignore it until
|
||||
// it reports READY, at which point we swap it into place.
|
||||
if (CalledByPendingChild()) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*(parent_->tracer_))) { |
||||
gpr_log(GPR_INFO, |
||||
"[child_policy_handler %p] helper %p: pending child policy %p " |
||||
"reports state=%s", |
||||
parent_.get(), this, child_, ConnectivityStateName(state)); |
||||
} |
||||
if (state != GRPC_CHANNEL_READY) return; |
||||
grpc_pollset_set_del_pollset_set( |
||||
parent_->child_policy_->interested_parties(), |
||||
parent_->interested_parties()); |
||||
parent_->child_policy_ = std::move(parent_->pending_child_policy_); |
||||
} else if (!CalledByCurrentChild()) { |
||||
// This request is from an outdated child, so ignore it.
|
||||
return; |
||||
} |
||||
parent_->channel_control_helper()->UpdateState(state, std::move(picker)); |
||||
} |
||||
|
||||
void RequestReresolution() override { |
||||
if (parent_->shutting_down_) return; |
||||
// Only forward re-resolution requests from the most recent child,
|
||||
// since that's the one that will be receiving any update we receive
|
||||
// from the resolver.
|
||||
const LoadBalancingPolicy* latest_child_policy = |
||||
parent_->pending_child_policy_ != nullptr |
||||
? parent_->pending_child_policy_.get() |
||||
: parent_->child_policy_.get(); |
||||
if (child_ != latest_child_policy) return; |
||||
if (GRPC_TRACE_FLAG_ENABLED(*(parent_->tracer_))) { |
||||
gpr_log(GPR_INFO, "[child_policy_handler %p] started name re-resolving", |
||||
parent_.get()); |
||||
} |
||||
parent_->channel_control_helper()->RequestReresolution(); |
||||
} |
||||
|
||||
void AddTraceEvent(TraceSeverity severity, StringView message) override { |
||||
if (parent_->shutting_down_) return; |
||||
if (!CalledByPendingChild() && !CalledByCurrentChild()) return; |
||||
parent_->channel_control_helper()->AddTraceEvent(severity, message); |
||||
} |
||||
|
||||
void set_child(LoadBalancingPolicy* child) { child_ = child; } |
||||
|
||||
private: |
||||
bool CalledByPendingChild() const { |
||||
GPR_ASSERT(child_ != nullptr); |
||||
return child_ == parent_->pending_child_policy_.get(); |
||||
} |
||||
|
||||
bool CalledByCurrentChild() const { |
||||
GPR_ASSERT(child_ != nullptr); |
||||
return child_ == parent_->child_policy_.get(); |
||||
}; |
||||
|
||||
RefCountedPtr<ChildPolicyHandler> parent_; |
||||
LoadBalancingPolicy* child_ = nullptr; |
||||
}; |
||||
|
||||
//
|
||||
// ChildPolicyHandler
|
||||
//
|
||||
|
||||
void ChildPolicyHandler::ShutdownLocked() { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, "[child_policy_handler %p] shutting down", this); |
||||
} |
||||
shutting_down_ = true; |
||||
if (child_policy_ != nullptr) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, "[child_policy_handler %p] shutting down lb_policy %p", |
||||
this, child_policy_.get()); |
||||
} |
||||
grpc_pollset_set_del_pollset_set(child_policy_->interested_parties(), |
||||
interested_parties()); |
||||
child_policy_.reset(); |
||||
} |
||||
if (pending_child_policy_ != nullptr) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, |
||||
"[child_policy_handler %p] shutting down pending lb_policy %p", |
||||
this, pending_child_policy_.get()); |
||||
} |
||||
grpc_pollset_set_del_pollset_set( |
||||
pending_child_policy_->interested_parties(), interested_parties()); |
||||
pending_child_policy_.reset(); |
||||
} |
||||
} |
||||
|
||||
void ChildPolicyHandler::UpdateLocked(UpdateArgs args) { |
||||
// The name of the policy that this update wants us to use.
|
||||
const char* child_policy_name = args.config->name(); |
||||
// If the child policy name changes, we need to create a new child
|
||||
// policy. When this happens, we leave child_policy_ as-is and store
|
||||
// the new child policy in pending_child_policy_. Once the new child
|
||||
// policy transitions into state READY, we swap it into child_policy_,
|
||||
// replacing the original child policy. So pending_child_policy_ is
|
||||
// non-null only between when we apply an update that changes the child
|
||||
// policy name and when the new child reports state READY.
|
||||
//
|
||||
// Updates can arrive at any point during this transition. We always
|
||||
// apply updates relative to the most recently created child policy,
|
||||
// even if the most recent one is still in pending_child_policy_. This
|
||||
// is true both when applying the updates to an existing child policy
|
||||
// and when determining whether we need to create a new policy.
|
||||
//
|
||||
// As a result of this, there are several cases to consider here:
|
||||
//
|
||||
// 1. We have no existing child policy (i.e., this is the first update
|
||||
// we receive after being created; in this case, both child_policy_
|
||||
// and pending_child_policy_ are null). In this case, we create a
|
||||
// new child policy and store it in child_policy_.
|
||||
//
|
||||
// 2. We have an existing child policy and have no pending child policy
|
||||
// from a previous update (i.e., either there has not been a
|
||||
// previous update that changed the policy name, or we have already
|
||||
// finished swapping in the new policy; in this case, child_policy_
|
||||
// is non-null but pending_child_policy_ is null). In this case:
|
||||
// a. If child_policy_->name() equals child_policy_name, then we
|
||||
// update the existing child policy.
|
||||
// b. If child_policy_->name() does not equal child_policy_name,
|
||||
// we create a new policy. The policy will be stored in
|
||||
// pending_child_policy_ and will later be swapped into
|
||||
// child_policy_ by the helper when the new child transitions
|
||||
// into state READY.
|
||||
//
|
||||
// 3. We have an existing child policy and have a pending child policy
|
||||
// from a previous update (i.e., a previous update set
|
||||
// pending_child_policy_ as per case 2b above and that policy has
|
||||
// not yet transitioned into state READY and been swapped into
|
||||
// child_policy_; in this case, both child_policy_ and
|
||||
// pending_child_policy_ are non-null). In this case:
|
||||
// a. If pending_child_policy_->name() equals child_policy_name,
|
||||
// then we update the existing pending child policy.
|
||||
// b. If pending_child_policy->name() does not equal
|
||||
// child_policy_name, then we create a new policy. The new
|
||||
// policy is stored in pending_child_policy_ (replacing the one
|
||||
// that was there before, which will be immediately shut down)
|
||||
// and will later be swapped into child_policy_ by the helper
|
||||
// when the new child transitions into state READY.
|
||||
const bool create_policy = |
||||
// case 1
|
||||
child_policy_ == nullptr || |
||||
// case 2b
|
||||
(pending_child_policy_ == nullptr && |
||||
strcmp(child_policy_->name(), child_policy_name) != 0) || |
||||
// case 3b
|
||||
(pending_child_policy_ != nullptr && |
||||
strcmp(pending_child_policy_->name(), child_policy_name) != 0); |
||||
LoadBalancingPolicy* policy_to_update = nullptr; |
||||
if (create_policy) { |
||||
// Cases 1, 2b, and 3b: create a new child policy.
|
||||
// If child_policy_ is null, we set it (case 1), else we set
|
||||
// pending_child_policy_ (cases 2b and 3b).
|
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, |
||||
"[child_policy_handler %p] creating new %schild policy %s", this, |
||||
child_policy_ == nullptr ? "" : "pending ", child_policy_name); |
||||
} |
||||
auto& lb_policy = |
||||
child_policy_ == nullptr ? child_policy_ : pending_child_policy_; |
||||
lb_policy = CreateChildPolicy(child_policy_name, *args.args); |
||||
policy_to_update = lb_policy.get(); |
||||
} else { |
||||
// Cases 2a and 3a: update an existing policy.
|
||||
// If we have a pending child policy, send the update to the pending
|
||||
// policy (case 3a), else send it to the current policy (case 2a).
|
||||
policy_to_update = pending_child_policy_ != nullptr |
||||
? pending_child_policy_.get() |
||||
: child_policy_.get(); |
||||
} |
||||
GPR_ASSERT(policy_to_update != nullptr); |
||||
// Update the policy.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, "[child_policy_handler %p] updating %schild policy %p", |
||||
this, |
||||
policy_to_update == pending_child_policy_.get() ? "pending " : "", |
||||
policy_to_update); |
||||
} |
||||
policy_to_update->UpdateLocked(std::move(args)); |
||||
} |
||||
|
||||
void ChildPolicyHandler::ExitIdleLocked() { |
||||
if (child_policy_ != nullptr) { |
||||
child_policy_->ExitIdleLocked(); |
||||
if (pending_child_policy_ != nullptr) { |
||||
pending_child_policy_->ExitIdleLocked(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ChildPolicyHandler::ResetBackoffLocked() { |
||||
if (child_policy_ != nullptr) { |
||||
child_policy_->ResetBackoffLocked(); |
||||
if (pending_child_policy_ != nullptr) { |
||||
pending_child_policy_->ResetBackoffLocked(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
OrphanablePtr<LoadBalancingPolicy> ChildPolicyHandler::CreateChildPolicy( |
||||
const char* child_policy_name, const grpc_channel_args& args) { |
||||
Helper* helper = new Helper(Ref(DEBUG_LOCATION, "Helper")); |
||||
LoadBalancingPolicy::Args lb_policy_args; |
||||
lb_policy_args.combiner = combiner(); |
||||
lb_policy_args.channel_control_helper = |
||||
std::unique_ptr<ChannelControlHelper>(helper); |
||||
lb_policy_args.args = &args; |
||||
OrphanablePtr<LoadBalancingPolicy> lb_policy = |
||||
LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy( |
||||
child_policy_name, std::move(lb_policy_args)); |
||||
if (GPR_UNLIKELY(lb_policy == nullptr)) { |
||||
gpr_log(GPR_ERROR, "could not create LB policy \"%s\"", child_policy_name); |
||||
return nullptr; |
||||
} |
||||
helper->set_child(lb_policy.get()); |
||||
if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) { |
||||
gpr_log(GPR_INFO, |
||||
"[child_policy_handler %p] created new LB policy \"%s\" (%p)", this, |
||||
child_policy_name, lb_policy.get()); |
||||
} |
||||
channel_control_helper()->AddTraceEvent( |
||||
ChannelControlHelper::TRACE_INFO, |
||||
absl::StrCat("Created new LB policy \"", child_policy_name, "\"")); |
||||
grpc_pollset_set_add_pollset_set(lb_policy->interested_parties(), |
||||
interested_parties()); |
||||
return lb_policy; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,66 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_CHILD_POLICY_HANDLER_H |
||||
#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_CHILD_POLICY_HANDLER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/lb_policy.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/orphanable.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// A class that makes it easy to gracefully switch child policies.
|
||||
//
|
||||
// Callers should instantiate this instead of using
|
||||
// LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy(). Once
|
||||
// instantiated, this object will automatically take care of
|
||||
// constructing the child policy as needed upon receiving an update.
|
||||
class ChildPolicyHandler : public LoadBalancingPolicy { |
||||
public: |
||||
ChildPolicyHandler(Args args, TraceFlag* tracer) |
||||
: LoadBalancingPolicy(std::move(args)), tracer_(tracer) {} |
||||
|
||||
virtual const char* name() const override { return "child_policy_handler"; } |
||||
|
||||
void UpdateLocked(UpdateArgs args) override; |
||||
void ExitIdleLocked() override; |
||||
void ResetBackoffLocked() override; |
||||
|
||||
private: |
||||
class Helper; |
||||
|
||||
void ShutdownLocked() override; |
||||
|
||||
OrphanablePtr<LoadBalancingPolicy> CreateChildPolicy( |
||||
const char* child_policy_name, const grpc_channel_args& args); |
||||
|
||||
// Passed in from caller at construction time.
|
||||
TraceFlag* tracer_; |
||||
|
||||
bool shutting_down_ = false; |
||||
|
||||
// Child LB policy.
|
||||
OrphanablePtr<LoadBalancingPolicy> child_policy_; |
||||
OrphanablePtr<LoadBalancingPolicy> pending_child_policy_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_CHILD_POLICY_HANDLER_H \ |
||||
*/ |
Loading…
Reference in new issue