/proc/self/cwd/source/server/drain_manager_impl.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/server/drain_manager_impl.h" |
2 | | |
3 | | #include <chrono> |
4 | | #include <cstdint> |
5 | | #include <functional> |
6 | | #include <memory> |
7 | | |
8 | | #include "envoy/config/listener/v3/listener.pb.h" |
9 | | #include "envoy/event/dispatcher.h" |
10 | | #include "envoy/event/timer.h" |
11 | | |
12 | | #include "source/common/common/assert.h" |
13 | | |
14 | | namespace Envoy { |
15 | | namespace Server { |
16 | | |
17 | | DrainManagerImpl::DrainManagerImpl(Instance& server, |
18 | | envoy::config::listener::v3::Listener::DrainType drain_type, |
19 | | Event::Dispatcher& dispatcher) |
20 | | : server_(server), dispatcher_(dispatcher), drain_type_(drain_type), |
21 | 10.9k | children_(Common::ThreadSafeCallbackManager::create()) {} |
22 | | |
23 | | DrainManagerPtr |
24 | | DrainManagerImpl::createChildManager(Event::Dispatcher& dispatcher, |
25 | 0 | envoy::config::listener::v3::Listener::DrainType drain_type) { |
26 | 0 | auto child = std::make_unique<DrainManagerImpl>(server_, drain_type, dispatcher); |
27 | | |
28 | | // Wire up the child so that when the parent starts draining, the child also sees the |
29 | | // state-change |
30 | 0 | auto child_cb = children_->add(dispatcher, [child = child.get()] { |
31 | 0 | if (!child->draining_) { |
32 | 0 | child->startDrainSequence([] {}); |
33 | 0 | } |
34 | 0 | }); |
35 | 0 | child->parent_callback_handle_ = std::move(child_cb); |
36 | 0 | return child; |
37 | 0 | } |
38 | | |
39 | 0 | DrainManagerPtr DrainManagerImpl::createChildManager(Event::Dispatcher& dispatcher) { |
40 | 0 | return createChildManager(dispatcher, drain_type_); |
41 | 0 | } |
42 | | |
43 | 5.92k | bool DrainManagerImpl::drainClose() const { |
44 | | // If we are actively health check failed and the drain type is default, always drain close. |
45 | | // |
46 | | // TODO(mattklein123): In relation to x-envoy-immediate-health-check-fail, it would be better |
47 | | // if even in the case of server health check failure we had some period of drain ramp up. This |
48 | | // would allow the other side to fail health check for the host which will require some thread |
49 | | // jumps versus immediately start GOAWAY/connection thrashing. |
50 | 5.92k | if (drain_type_ == envoy::config::listener::v3::Listener::DEFAULT && |
51 | 5.92k | server_.healthCheckFailed()) { |
52 | 0 | return true; |
53 | 0 | } |
54 | | |
55 | 5.92k | if (!draining_) { |
56 | 5.92k | return false; |
57 | 5.92k | } |
58 | | |
59 | 0 | if (server_.options().drainStrategy() == Server::DrainStrategy::Immediate) { |
60 | 0 | return true; |
61 | 0 | } |
62 | 0 | ASSERT(server_.options().drainStrategy() == Server::DrainStrategy::Gradual); |
63 | | |
64 | | // P(return true) = elapsed time / drain timeout |
65 | | // If the drain deadline is exceeded, skip the probability calculation. |
66 | 0 | const MonotonicTime current_time = dispatcher_.timeSource().monotonicTime(); |
67 | 0 | if (current_time >= drain_deadline_) { |
68 | 0 | return true; |
69 | 0 | } |
70 | | |
71 | 0 | const auto remaining_time = |
72 | 0 | std::chrono::duration_cast<std::chrono::seconds>(drain_deadline_ - current_time); |
73 | 0 | ASSERT(server_.options().drainTime() >= remaining_time); |
74 | 0 | const auto elapsed_time = server_.options().drainTime() - remaining_time; |
75 | 0 | return static_cast<uint64_t>(elapsed_time.count()) > |
76 | 0 | (server_.api().randomGenerator().random() % server_.options().drainTime().count()); |
77 | 0 | } |
78 | | |
79 | 0 | Common::CallbackHandlePtr DrainManagerImpl::addOnDrainCloseCb(DrainCloseCb cb) const { |
80 | 0 | ASSERT(dispatcher_.isThreadSafe()); |
81 | | |
82 | 0 | if (draining_) { |
83 | 0 | const MonotonicTime current_time = dispatcher_.timeSource().monotonicTime(); |
84 | | |
85 | | // Calculate the delay. If using an immediate drain-strategy or past our deadline, use |
86 | | // a zero millisecond delay. Otherwise, pick a random value within the remaining time-span. |
87 | 0 | std::chrono::milliseconds drain_delay = |
88 | 0 | (server_.options().drainStrategy() != Server::DrainStrategy::Immediate && |
89 | 0 | current_time < drain_deadline_) |
90 | 0 | ? std::chrono::milliseconds(server_.api().randomGenerator().random() % |
91 | 0 | std::chrono::duration_cast<std::chrono::milliseconds>( |
92 | 0 | drain_deadline_ - current_time) |
93 | 0 | .count()) |
94 | 0 | : std::chrono::milliseconds{0}; |
95 | 0 | cb(drain_delay); |
96 | 0 | return nullptr; |
97 | 0 | } |
98 | | |
99 | 0 | return cbs_.add(cb); |
100 | 0 | } |
101 | | |
102 | 4 | void DrainManagerImpl::addDrainCompleteCallback(std::function<void()> cb) { |
103 | 4 | ASSERT(draining_); |
104 | | |
105 | | // If the drain-tick-timer is active, add the callback to the queue. If not defined |
106 | | // then it must have already expired, invoke the callback immediately. |
107 | 4 | if (drain_tick_timer_) { |
108 | 4 | drain_complete_cbs_.push_back(cb); |
109 | 4 | } else { |
110 | 0 | cb(); |
111 | 0 | } |
112 | 4 | } |
113 | | |
114 | 4 | void DrainManagerImpl::startDrainSequence(std::function<void()> drain_complete_cb) { |
115 | 4 | ASSERT(drain_complete_cb); |
116 | | |
117 | | // If we've already started draining (either through direct invocation or through |
118 | | // parent-initiated draining), enqueue the drain_complete_cb and return |
119 | 4 | if (draining_) { |
120 | 0 | addDrainCompleteCallback(drain_complete_cb); |
121 | 0 | return; |
122 | 0 | } |
123 | | |
124 | 4 | ASSERT(!drain_tick_timer_); |
125 | 4 | draining_ = true; |
126 | | |
127 | | // Signal to child drain-managers to start their drain sequence |
128 | 4 | children_->runCallbacks(); |
129 | | |
130 | | // Schedule callback to run at end of drain time |
131 | 4 | drain_tick_timer_ = dispatcher_.createTimer([this]() { |
132 | 0 | for (auto& cb : drain_complete_cbs_) { |
133 | 0 | cb(); |
134 | 0 | } |
135 | 0 | drain_complete_cbs_.clear(); |
136 | 0 | drain_tick_timer_.reset(); |
137 | 0 | }); |
138 | 4 | addDrainCompleteCallback(drain_complete_cb); |
139 | 4 | const std::chrono::seconds drain_delay(server_.options().drainTime()); |
140 | 4 | drain_tick_timer_->enableTimer(drain_delay); |
141 | 4 | drain_deadline_ = dispatcher_.timeSource().monotonicTime() + drain_delay; |
142 | | |
143 | | // Call registered on-drain callbacks - with gradual delays |
144 | | // Note: This will distribute drain events in the first 1/4th of the drain window |
145 | | // to ensure that we initiate draining with enough time for graceful shutdowns. |
146 | 4 | const MonotonicTime current_time = dispatcher_.timeSource().monotonicTime(); |
147 | 4 | std::chrono::seconds remaining_time{0}; |
148 | 4 | if (server_.options().drainStrategy() != Server::DrainStrategy::Immediate && |
149 | 4 | current_time < drain_deadline_) { |
150 | 4 | remaining_time = |
151 | 4 | std::chrono::duration_cast<std::chrono::seconds>(drain_deadline_ - current_time); |
152 | 4 | ASSERT(server_.options().drainTime() >= remaining_time); |
153 | 4 | } |
154 | | |
155 | 4 | uint32_t step_count = 0; |
156 | 4 | size_t num_cbs = cbs_.size(); |
157 | 4 | cbs_.runCallbacksWith([&]() { |
158 | | // switch to floating-point math to avoid issues with integer division |
159 | 0 | std::chrono::milliseconds delay{static_cast<int64_t>( |
160 | 0 | static_cast<double>(step_count) / 4 / num_cbs * |
161 | 0 | std::chrono::duration_cast<std::chrono::milliseconds>(remaining_time).count())}; |
162 | 0 | step_count++; |
163 | 0 | return delay; |
164 | 0 | }); |
165 | 4 | } |
166 | | |
167 | 2.64k | void DrainManagerImpl::startParentShutdownSequence() { |
168 | | // Do not initiate parent shutdown sequence when hot restart is disabled. |
169 | 2.64k | if (server_.options().hotRestartDisabled()) { |
170 | 2.64k | return; |
171 | 2.64k | } |
172 | 0 | ASSERT(!parent_shutdown_timer_); |
173 | 0 | parent_shutdown_timer_ = server_.dispatcher().createTimer([this]() -> void { |
174 | | // Shut down the parent now. It should have already been draining. |
175 | 0 | ENVOY_LOG(info, "shutting down parent after drain"); |
176 | 0 | server_.hotRestart().sendParentTerminateRequest(); |
177 | 0 | }); |
178 | |
|
179 | 0 | parent_shutdown_timer_->enableTimer(std::chrono::duration_cast<std::chrono::milliseconds>( |
180 | 0 | server_.options().parentShutdownTime())); |
181 | 0 | } |
182 | | |
183 | | } // namespace Server |
184 | | } // namespace Envoy |