-
Notifications
You must be signed in to change notification settings - Fork 5.3k
common: introduce new "safe" init manager #6296
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
28ccab4
common: introduce new "safe" init manager
86cc5cf
fix gmock compilation issue in ci
c85df15
no doc comments for overridden methods
d863ffa
Merge branch 'master' into safe_init
ec99a04
address comments from matt & harvey
afef8b8
change SafeInit::Target to be a data member rather than a mix-in
98e90c1
Merge branch 'master' into safe_init
162044e
address matt's comments
8da0732
renaming mock watcher and target
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| load( | ||
| "//bazel:envoy_build_system.bzl", | ||
| "envoy_cc_library", | ||
| "envoy_package", | ||
| ) | ||
|
|
||
| envoy_package() | ||
|
|
||
| envoy_cc_library( | ||
| name = "watcher_interface", | ||
| hdrs = ["watcher.h"], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "target_interface", | ||
| hdrs = ["target.h"], | ||
| deps = [ | ||
| ":watcher_interface", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "manager_interface", | ||
| hdrs = ["manager.h"], | ||
| deps = [ | ||
| ":target_interface", | ||
| ":watcher_interface", | ||
| ], | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| #pragma once | ||
|
|
||
| #include "envoy/common/pure.h" | ||
| #include "envoy/safe_init/target.h" | ||
| #include "envoy/safe_init/watcher.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace SafeInit { | ||
|
|
||
| /** | ||
| * SafeInit::Manager coordinates initialization of one or more "targets." A typical flow would be: | ||
| * | ||
| * - One or more initialization targets are registered with a manager using `add`. | ||
| * - The manager is told to `initialize` all its targets, given a Watcher to notify when all | ||
| * registered targets are initialized. | ||
| * - Each target will initialize, either immediately or asynchronously, and will signal | ||
| * `ready` to the manager when initialized. | ||
| * - When all targets are initialized, the manager signals `ready` to the watcher it was given | ||
| * previously. | ||
| * | ||
| * Since there are several entities involved in this flow -- the owner of the manager, the targets | ||
| * registered with the manager, and the manager itself -- it may be difficult or impossible in some | ||
| * cases to guarantee that their lifetimes line up correctly to avoid use-after-free errors. The | ||
| * interface design here in SafeInit allows implementations to avoid the issue: | ||
| * | ||
| * - A Target can only be initialized via a TargetHandle, which acts as a weak reference. | ||
| * Attempting to initialize a destroyed Target via its handle has no ill effects. | ||
| * - Likewise, a Watcher can only be notified that initialization was complete via a | ||
| * WatcherHandle, which acts as a weak reference as well. | ||
| * | ||
| * See target.h and watcher.h, as well as implementation in source/common/safe_init for details. | ||
| */ | ||
| struct Manager { | ||
| virtual ~Manager() = default; | ||
|
|
||
| /** | ||
| * The manager's state, used e.g. for reporting in the admin server. | ||
| */ | ||
| enum class State { | ||
| /** | ||
| * Targets have not been initialized. | ||
| */ | ||
| Uninitialized, | ||
| /** | ||
| * Targets are currently being initialized. | ||
| */ | ||
| Initializing, | ||
| /** | ||
| * All targets have been initialized. | ||
| */ | ||
| Initialized | ||
| }; | ||
|
|
||
| /** | ||
| * @return the current state of the manager. | ||
| */ | ||
| virtual State state() const PURE; | ||
|
|
||
| /** | ||
| * Register an initialization target. If the manager's current state is uninitialized, the target | ||
| * will be saved for invocation later, when `initialize` is called. If the current state is | ||
| * initializing, the target will be invoked immediately. It is an error to register a target with | ||
| * a manager that is already in initialized state. | ||
| * @param target the target to be invoked when initialization begins. | ||
| */ | ||
| virtual void add(const Target& target) PURE; | ||
|
|
||
| /** | ||
| * Start initialization of all previously registered targets, and notify the given Watcher when | ||
| * initialization is complete. It is an error to call initialize on a manager that is already in | ||
| * initializing or initialized state. If the manager contains no targets, initialization completes | ||
| * immediately. | ||
mergeconflict marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @param watcher the watcher to notify when initialization is complete. | ||
| */ | ||
| virtual void initialize(const Watcher& watcher) PURE; | ||
| }; | ||
|
|
||
| } // namespace SafeInit | ||
| } // namespace Envoy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "envoy/common/pure.h" | ||
| #include "envoy/safe_init/watcher.h" | ||
|
|
||
| #include "absl/strings/string_view.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace SafeInit { | ||
|
|
||
| /** | ||
| * A TargetHandle functions as a weak reference to a Target. It is how an implementation of | ||
| * SafeInit::Manager would safely tell a target to `initialize` with no guarantees about the | ||
| * target's lifetime. Typical usage (outside of SafeInit::ManagerImpl) does not require touching | ||
| * TargetHandles at all. | ||
| */ | ||
| struct TargetHandle { | ||
| virtual ~TargetHandle() = default; | ||
|
|
||
| /** | ||
| * Tell the target to begin initialization, if it is still available. | ||
| * @param watcher A Watcher for the target to notify when it has initialized. | ||
| * @return true if the target received this call, false if the target was already destroyed. | ||
| */ | ||
| virtual bool initialize(const Watcher& watcher) const PURE; | ||
| }; | ||
| using TargetHandlePtr = std::unique_ptr<TargetHandle>; | ||
|
|
||
| /** | ||
| * An initialization Target is an entity that can be registered with a Manager for initialization. | ||
| * It can only be invoked through a TargetHandle. | ||
| */ | ||
| struct Target { | ||
| virtual ~Target() = default; | ||
|
|
||
| /** | ||
| * @return a human-readable target name, for logging / debugging. | ||
| */ | ||
| virtual absl::string_view name() const PURE; | ||
|
|
||
| /** | ||
| * Create a new handle that can initialize this target. | ||
| * @param name a human readable handle name, for logging / debugging. | ||
| * @return a new handle that can initialize this target. | ||
| */ | ||
| virtual TargetHandlePtr createHandle(absl::string_view name) const PURE; | ||
| }; | ||
|
|
||
| } // namespace SafeInit | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "envoy/common/pure.h" | ||
|
|
||
| #include "absl/strings/string_view.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace SafeInit { | ||
|
|
||
| /** | ||
| * A WatcherHandle functions as a weak reference to a Watcher. It is how an implementation of | ||
| * SafeInit::Target would safely notify a Manager that it has initialized, and likewise it's how | ||
| * an implementation of SafeInit::Manager would safely tell its client that all registered targets | ||
| * have initialized, with no guarantees about the lifetimes of the manager or client. Typical usage | ||
| * (outside of SafeInit::TargetImpl and ManagerImpl) does not require touching WatcherHandles at | ||
| * all. | ||
| */ | ||
| struct WatcherHandle { | ||
| virtual ~WatcherHandle() = default; | ||
|
|
||
| /** | ||
| * Tell the watcher that initialization has completed, if it is still available. | ||
| * @return true if the watcher received this call, false if the watcher was already destroyed. | ||
| */ | ||
| virtual bool ready() const PURE; | ||
| }; | ||
| using WatcherHandlePtr = std::unique_ptr<WatcherHandle>; | ||
|
|
||
| /** | ||
| * A Watcher is an entity that listens for notifications that either an initialization target or | ||
| * all targets registered with a manager have initialized. It can only be invoked through a | ||
| * WatcherHandle. | ||
| */ | ||
| struct Watcher { | ||
| virtual ~Watcher() = default; | ||
|
|
||
| /** | ||
| * @return a human-readable target name, for logging / debugging. | ||
| */ | ||
| virtual absl::string_view name() const PURE; | ||
|
|
||
| /** | ||
| * Create a new handle that can notify this watcher. | ||
| * @param name a human readable handle name, for logging / debugging. | ||
| * @return a new handle that can notify this watcher. | ||
| */ | ||
| virtual WatcherHandlePtr createHandle(absl::string_view name) const PURE; | ||
| }; | ||
|
|
||
| } // namespace SafeInit | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| load( | ||
| "//bazel:envoy_build_system.bzl", | ||
| "envoy_cc_library", | ||
| "envoy_package", | ||
| ) | ||
|
|
||
| envoy_package() | ||
|
|
||
| envoy_cc_library( | ||
| name = "watcher_lib", | ||
| srcs = ["watcher_impl.cc"], | ||
| hdrs = ["watcher_impl.h"], | ||
| deps = [ | ||
| "//include/envoy/safe_init:watcher_interface", | ||
| "//source/common/common:logger_lib", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "target_lib", | ||
| srcs = ["target_impl.cc"], | ||
| hdrs = ["target_impl.h"], | ||
| deps = [ | ||
| "//include/envoy/safe_init:target_interface", | ||
| "//source/common/common:logger_lib", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "manager_lib", | ||
| srcs = ["manager_impl.cc"], | ||
| hdrs = ["manager_impl.h"], | ||
| deps = [ | ||
| ":watcher_lib", | ||
| "//include/envoy/safe_init:manager_interface", | ||
| "//source/common/common:logger_lib", | ||
| ], | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| #include "common/safe_init/manager_impl.h" | ||
|
|
||
| #include "common/common/assert.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace SafeInit { | ||
|
|
||
| ManagerImpl::ManagerImpl(absl::string_view name) | ||
| : name_(fmt::format("init manager {}", name)), state_(State::Uninitialized), count_(0), | ||
| watcher_(name_, [this]() { onTargetReady(); }) {} | ||
|
|
||
| Manager::State ManagerImpl::state() const { return state_; } | ||
|
|
||
| void ManagerImpl::add(const Target& target) { | ||
| ++count_; | ||
| TargetHandlePtr target_handle(target.createHandle(name_)); | ||
| switch (state_) { | ||
| case State::Uninitialized: | ||
| // If the manager isn't initialized yet, save the target handle to be initialized later. | ||
| ENVOY_LOG(debug, "added {} to {}", target.name(), name_); | ||
mergeconflict marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| target_handles_.push_back(std::move(target_handle)); | ||
| return; | ||
| case State::Initializing: | ||
| // If the manager is already initializing, initialize the new target immediately. Note that | ||
| // it's important in this case that count_ was incremented above before calling the target, | ||
| // because if the target calls the init manager back immediately, count_ will be decremented | ||
| // here (see the definition of watcher_ above). | ||
| target_handle->initialize(watcher_); | ||
| return; | ||
| case State::Initialized: | ||
| // If the manager has already completed initialization, consider this a programming error. | ||
| ASSERT(false, fmt::format("attempted to add {} to initialized {}", target.name(), name_)); | ||
| } | ||
| } | ||
|
|
||
| void ManagerImpl::initialize(const Watcher& watcher) { | ||
| // If the manager is already initializing or initialized, consider this a programming error. | ||
| ASSERT(state_ == State::Uninitialized, fmt::format("attempted to initialize {} twice", name_)); | ||
|
|
||
| // Create a handle to notify when initialization is complete. | ||
| watcher_handle_ = watcher.createHandle(name_); | ||
|
|
||
| if (count_ == 0) { | ||
| // If we have no targets, initialization trivially completes. This can happen, and is fine. | ||
| ENVOY_LOG(debug, "{} contains no targets", name_); | ||
| ready(); | ||
| } else { | ||
| // If we have some targets, start initialization... | ||
| ENVOY_LOG(debug, "{} initializing", name_); | ||
| state_ = State::Initializing; | ||
|
|
||
| // Attempt to initialize each target. If a target is unavailable, treat it as though it | ||
| // completed immediately. | ||
| for (const auto& target_handle : target_handles_) { | ||
| if (!target_handle->initialize(watcher_)) { | ||
| onTargetReady(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void ManagerImpl::onTargetReady() { | ||
| // If there are no remaining targets and one mysteriously calls us back, this manager is haunted. | ||
| ASSERT(count_ != 0, fmt::format("{} called back by target after initialization complete")); | ||
|
|
||
| // If there are no uninitialized targets remaining when called back by a target, that means it was | ||
| // the last. Signal `ready` to the handle we saved in `initialize`. | ||
| if (--count_ == 0) { | ||
| ready(); | ||
| } | ||
| } | ||
|
|
||
| void ManagerImpl::ready() { | ||
| state_ = State::Initialized; | ||
| watcher_handle_->ready(); | ||
| } | ||
|
|
||
| } // namespace SafeInit | ||
| } // namespace Envoy | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.