Coverage Report

Created: 2023-11-12 09:30

/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