Skip to content
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

overload: create scaled timers via the dispatcher #14679

Merged
12 changes: 12 additions & 0 deletions include/envoy/api/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ class Api {
*/
virtual Event::DispatcherPtr allocateDispatcher(const std::string& name) PURE;

/**
* Allocate a dispatcher.
* @param name the identity name for a dispatcher, e.g. "worker_2" or "main_thread".
* This name will appear in per-handler/worker statistics, such as
* "server.worker_2.watchdog_miss".
* @param scaled_timer_factory the factory to use when creating the scaled timer manager.
* @return Event::DispatcherPtr which is owned by the caller.
*/
virtual Event::DispatcherPtr
allocateDispatcher(const std::string& name,
const Event::ScaledRangeTimerManagerFactory& scaled_timer_factory) PURE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is passing in a factory the right thing, or should we prefer passing in an std::unique_ptr ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(assuming you mean std::unique_ptr) we can't pass in a pointer because the manager is backed by the dispatcher so we can't construct one without constructing the Dispatcher first or at at the same time.


/**
* Allocate a dispatcher.
* @param name the identity name for a dispatcher, e.g. "worker_2" or "main_thread".
Expand Down
3 changes: 2 additions & 1 deletion include/envoy/event/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ envoy_cc_library(
deps = [
":deferred_deletable",
":file_event_interface",
":scaled_range_timer_manager_interface",
":schedulable_cb_interface",
":signal_interface",
":timer_interface",
"//include/envoy/common:scope_tracker_interface",
"//include/envoy/common:time_interface",
"//include/envoy/event:timer_interface",
"//include/envoy/filesystem:watcher_interface",
"//include/envoy/network:connection_handler_interface",
"//include/envoy/network:connection_interface",
Expand Down
16 changes: 16 additions & 0 deletions include/envoy/event/dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "envoy/common/scope_tracker.h"
#include "envoy/common/time.h"
#include "envoy/event/file_event.h"
#include "envoy/event/scaled_range_timer_manager.h"
#include "envoy/event/schedulable_cb.h"
#include "envoy/event/signal.h"
#include "envoy/event/timer.h"
Expand Down Expand Up @@ -77,6 +78,21 @@ class DispatcherBase {
*/
virtual Event::TimerPtr createTimer(TimerCb cb) PURE;

/**
* Allocates a scaled timer. @see Timer for docs on how to use the timer.
* @param timer_type the type of timer to create.
* @param cb supplies the callback to invoke when the timer fires.
*/
virtual Event::TimerPtr createScaledTimer(Event::ScaledRangeTimerManager::TimerType timer_type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move TimerType somewhere else so that Dispatcher doesn't need to depend on ScaledRangeTimerManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not attached to the placement, and I don't like how verbose the references are to it (Event::ScaledRangeTimerManager::TimerType). What about ::Envoy::Event::ScaledTimerType?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that callers are expected to use this on the Dispatcher function at the top-level, I'd say move the enum into Dispatcher, or just into the Envoy::Event namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put it in Envoy::Event.

TimerCb cb) PURE;

/**
* Allocates a scaled timer. @see Timer for docs on how to use the timer.
* @param minimum the rule for computing the minimum value of the timer.
* @param cb supplies the callback to invoke when the timer fires.
*/
virtual Event::TimerPtr createScaledTimer(ScaledTimerMinimum minimum, TimerCb cb) PURE;

/**
* Allocates a schedulable callback. @see SchedulableCallback for docs on how to use the wrapped
* callback.
Expand Down
20 changes: 20 additions & 0 deletions include/envoy/event/scaled_range_timer_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ namespace Event {
*/
class ScaledRangeTimerManager {
public:
enum class TimerType {
// Timers created with this type will never be scaled. This should only be used for testing.
UnscaledRealTimerForTest,
// The amount of time an HTTP connection to a downstream client can remain idle (no streams).
// This corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto.
HttpDownstreamIdleConnectionTimeout,
// The amount of time an HTTP stream from a downstream client can remain idle. This corresponds
// to the HTTP_DOWNSTREAM_STREAM_IDLE TimerType in overload.proto.
HttpDownstreamIdleStreamTimeout,
};

virtual ~ScaledRangeTimerManager() = default;

/**
Expand All @@ -28,6 +39,12 @@ class ScaledRangeTimerManager {
*/
virtual TimerPtr createTimer(ScaledTimerMinimum minimum, TimerCb callback) PURE;

/**
* Creates a new timer backed by the manager using the provided timer type to
* determine the minimum.
*/
virtual TimerPtr createTimer(TimerType timer_type, TimerCb callback) PURE;

/**
* Sets the scale factor for all timers created through this manager. The value should be between
* 0 and 1, inclusive. The scale factor affects the amount of time timers spend in their target
Expand All @@ -41,5 +58,8 @@ class ScaledRangeTimerManager {

using ScaledRangeTimerManagerPtr = std::unique_ptr<ScaledRangeTimerManager>;

class Dispatcher;
using ScaledRangeTimerManagerFactory = std::function<ScaledRangeTimerManagerPtr(Dispatcher&)>;

} // namespace Event
} // namespace Envoy
23 changes: 16 additions & 7 deletions include/envoy/event/scaled_timer_minimum.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,31 @@ namespace Event {
* Describes a minimum timer value that is equal to a scale factor applied to the maximum.
*/
struct ScaledMinimum {
explicit ScaledMinimum(UnitFloat scale_factor) : scale_factor_(scale_factor) {}
explicit constexpr ScaledMinimum(UnitFloat scale_factor) : scale_factor_(scale_factor) {}
inline bool operator==(const ScaledMinimum& other) const {
return other.scale_factor_.value() == scale_factor_.value();
}
const UnitFloat scale_factor_;
};

/**
* Describes a minimum timer value that is an absolute duration.
*/
struct AbsoluteMinimum {
explicit AbsoluteMinimum(std::chrono::milliseconds value) : value_(value) {}
explicit constexpr AbsoluteMinimum(std::chrono::milliseconds value) : value_(value) {}
inline bool operator==(const AbsoluteMinimum& other) const { return other.value_ == value_; }
const std::chrono::milliseconds value_;
};

/**
* Class that describes how to compute a minimum timeout given a maximum timeout value. It wraps
* ScaledMinimum and AbsoluteMinimum and provides a single computeMinimum() method.
*/
class ScaledTimerMinimum : private absl::variant<ScaledMinimum, AbsoluteMinimum> {
class ScaledTimerMinimum {
public:
// Use base class constructor.
using absl::variant<ScaledMinimum, AbsoluteMinimum>::variant;
// Forward arguments to impl_'s constructor.
constexpr ScaledTimerMinimum(AbsoluteMinimum arg) : impl_(arg) {}
constexpr ScaledTimerMinimum(ScaledMinimum arg) : impl_(arg) {}

// Computes the minimum value for a given maximum timeout. If this object was constructed with a
// - ScaledMinimum value:
Expand All @@ -51,9 +56,13 @@ class ScaledTimerMinimum : private absl::variant<ScaledMinimum, AbsoluteMinimum>
}
const std::chrono::milliseconds value_;
};
return absl::visit<Visitor, const absl::variant<ScaledMinimum, AbsoluteMinimum>&>(
Visitor(maximum), *this);
return absl::visit(Visitor(maximum), impl_);
}

inline bool operator==(const ScaledTimerMinimum& other) const { return impl_ == other.impl_; }

private:
absl::variant<ScaledMinimum, AbsoluteMinimum> impl_;
};

} // namespace Event
Expand Down
1 change: 1 addition & 0 deletions include/envoy/server/overload/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ envoy_cc_library(
name = "thread_local_overload_state",
hdrs = ["thread_local_overload_state.h"],
deps = [
"//include/envoy/event:scaled_range_timer_manager_interface",
"//include/envoy/event:scaled_timer_minimum",
"//include/envoy/event:timer_interface",
"//include/envoy/thread_local:thread_local_object",
Expand Down
6 changes: 6 additions & 0 deletions include/envoy/server/overload/overload_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "envoy/common/pure.h"
#include "envoy/event/dispatcher.h"
#include "envoy/event/scaled_range_timer_manager.h"
#include "envoy/server/overload/thread_local_overload_state.h"

#include "common/singleton/const_singleton.h"
Expand Down Expand Up @@ -69,6 +70,11 @@ class OverloadManager {
* an alternative to registering a callback for overload action state changes.
*/
virtual ThreadLocalOverloadState& getThreadLocalOverloadState() PURE;

/**
* Get a factory for constructing scaled timer managers that respond to overload state.
*/
virtual Event::ScaledRangeTimerManagerFactory scaledTimerFactory() PURE;
};

} // namespace Server
Expand Down
20 changes: 1 addition & 19 deletions include/envoy/server/overload/thread_local_overload_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <string>

#include "envoy/common/pure.h"
#include "envoy/event/scaled_range_timer_manager.h"
#include "envoy/event/scaled_timer_minimum.h"
#include "envoy/event/timer.h"
#include "envoy/thread_local/thread_local_object.h"
Expand Down Expand Up @@ -40,32 +41,13 @@ class OverloadActionState {
*/
using OverloadActionCb = std::function<void(OverloadActionState)>;

enum class OverloadTimerType {
// Timers created with this type will never be scaled. This should only be used for testing.
UnscaledRealTimerForTest,
// The amount of time an HTTP connection to a downstream client can remain idle (no streams). This
// corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto.
HttpDownstreamIdleConnectionTimeout,
// The amount of time an HTTP stream from a downstream client can remain idle. This corresponds to
// the HTTP_DOWNSTREAM_STREAM_IDLE TimerType in overload.proto.
HttpDownstreamIdleStreamTimeout,
};

/**
* Thread-local copy of the state of each configured overload action.
*/
class ThreadLocalOverloadState : public ThreadLocal::ThreadLocalObject {
public:
// Get a thread-local reference to the value for the given action key.
virtual const OverloadActionState& getState(const std::string& action) PURE;

// Get a scaled timer whose minimum corresponds to the configured value for the given timer type.
virtual Event::TimerPtr createScaledTimer(OverloadTimerType timer_type,
Event::TimerCb callback) PURE;

// Get a scaled timer whose minimum is determined by the given scaling rule.
virtual Event::TimerPtr createScaledTimer(Event::ScaledTimerMinimum minimum,
Event::TimerCb callback) PURE;
};

} // namespace Server
Expand Down
12 changes: 10 additions & 2 deletions source/common/api/api_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ Impl::Impl(Thread::ThreadFactory& thread_factory, Stats::Store& store,
process_context_(process_context), watermark_factory_(std::move(watermark_factory)) {}

Event::DispatcherPtr Impl::allocateDispatcher(const std::string& name) {
return std::make_unique<Event::DispatcherImpl>(name, *this, time_system_, watermark_factory_);
return std::make_unique<Event::DispatcherImpl>(name, *this, time_system_, nullptr,
watermark_factory_);
}
Event::DispatcherPtr
Impl::allocateDispatcher(const std::string& name,
const Event::ScaledRangeTimerManagerFactory& scaled_timer_factory) {
return std::make_unique<Event::DispatcherImpl>(name, *this, time_system_, scaled_timer_factory,
watermark_factory_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think about what could be done to avoid this separate API method, although I think this new method is fine.

I see that Server::InstanceImpl owns both the api::Impl and the OverloadManager. OverloadManager depends on a dispatcher so it must be created well after api::Impl which is used to allocate the dispatcher. It would be useful to have Api::Impl provide the access to OverloadManager needed by the dispatcher allocation function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would violate the expectation on API, which is that it is (roughtly) for providing platform-level functionality. Overload management doesn't feel like something on that level.

}

Event::DispatcherPtr Impl::allocateDispatcher(const std::string& name,
Buffer::WatermarkFactoryPtr&& factory) {
return std::make_unique<Event::DispatcherImpl>(name, *this, time_system_, std::move(factory));
return std::make_unique<Event::DispatcherImpl>(name, *this, time_system_, nullptr,
std::move(factory));
}

} // namespace Api
Expand Down
3 changes: 3 additions & 0 deletions source/common/api/api_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Impl : public Api {

// Api::Api
Event::DispatcherPtr allocateDispatcher(const std::string& name) override;
Event::DispatcherPtr
allocateDispatcher(const std::string& name,
const Event::ScaledRangeTimerManagerFactory& scaled_timer_factory) override;
Event::DispatcherPtr allocateDispatcher(const std::string& name,
Buffer::WatermarkFactoryPtr&& watermark_factory) override;
Thread::ThreadFactory& threadFactory() override { return thread_factory_; }
Expand Down
1 change: 1 addition & 0 deletions source/common/event/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ envoy_cc_library(
":libevent_scheduler_lib",
":real_time_system_lib",
":signal_lib",
":scaled_range_timer_manager_lib",
"//include/envoy/common:scope_tracker_interface",
"//include/envoy/common:time_interface",
"//include/envoy/event:signal_interface",
Expand Down
29 changes: 23 additions & 6 deletions source/common/event/dispatcher_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "common/common/thread.h"
#include "common/event/file_event_impl.h"
#include "common/event/libevent_scheduler.h"
#include "common/event/scaled_range_timer_manager_impl.h"
#include "common/event/signal_impl.h"
#include "common/event/timer_impl.h"
#include "common/filesystem/watcher_impl.h"
Expand All @@ -37,17 +38,22 @@
namespace Envoy {
namespace Event {

DispatcherImpl::DispatcherImpl(const std::string& name, Api::Api& api,
Event::TimeSystem& time_system,
const Buffer::WatermarkFactorySharedPtr& factory)
DispatcherImpl::DispatcherImpl(
const std::string& name, Api::Api& api, Event::TimeSystem& time_system,
const std::function<ScaledRangeTimerManagerPtr(Dispatcher&)>& scaled_timer_factory,
const Buffer::WatermarkFactorySharedPtr& watermark_factory)
: name_(name), api_(api),
buffer_factory_(factory != nullptr ? factory
: std::make_shared<Buffer::WatermarkBufferFactory>()),
buffer_factory_(watermark_factory != nullptr
? watermark_factory
: std::make_shared<Buffer::WatermarkBufferFactory>()),
scheduler_(time_system.createScheduler(base_scheduler_, base_scheduler_)),
deferred_delete_cb_(base_scheduler_.createSchedulableCallback(
[this]() -> void { clearDeferredDeleteList(); })),
post_cb_(base_scheduler_.createSchedulableCallback([this]() -> void { runPostCallbacks(); })),
current_to_delete_(&to_delete_1_) {
current_to_delete_(&to_delete_1_),
scaled_timer_manager_(scaled_timer_factory != nullptr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing a function reference to nullptr seems a bit strange. I would suggest removing the " != nullptr"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, we can assume callers won't pass in a null function reference.

? scaled_timer_factory(*this)
: std::make_unique<ScaledRangeTimerManagerImpl>(*this)) {
ASSERT(!name_.empty());
FatalErrorHandler::registerFatalErrorHandler(*this);
updateApproximateMonotonicTimeInternal();
Expand Down Expand Up @@ -190,6 +196,17 @@ TimerPtr DispatcherImpl::createTimer(TimerCb cb) {
return createTimerInternal(cb);
}

TimerPtr DispatcherImpl::createScaledTimer(ScaledRangeTimerManager::TimerType timer_type,
TimerCb cb) {
ASSERT(isThreadSafe());
return scaled_timer_manager_->createTimer(timer_type, std::move(cb));
}

TimerPtr DispatcherImpl::createScaledTimer(ScaledTimerMinimum minimum, TimerCb cb) {
ASSERT(isThreadSafe());
return scaled_timer_manager_->createTimer(minimum, std::move(cb));
}

Event::SchedulableCallbackPtr DispatcherImpl::createSchedulableCallback(std::function<void()> cb) {
ASSERT(isThreadSafe());
return base_scheduler_.createSchedulableCallback([this, cb]() {
Expand Down
7 changes: 6 additions & 1 deletion source/common/event/dispatcher_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>,
public FatalErrorHandlerInterface {
public:
DispatcherImpl(const std::string& name, Api::Api& api, Event::TimeSystem& time_system,
const Buffer::WatermarkFactorySharedPtr& factory = nullptr);
const ScaledRangeTimerManagerFactory& scaled_timer_factory = nullptr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer default constructor over nullptr when specifying default function argument

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const Buffer::WatermarkFactorySharedPtr& watermark_factory = nullptr);
~DispatcherImpl() override;

/**
Expand Down Expand Up @@ -67,6 +68,9 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>,
Network::UdpListenerPtr createUdpListener(Network::SocketSharedPtr socket,
Network::UdpListenerCallbacks& cb) override;
TimerPtr createTimer(TimerCb cb) override;
TimerPtr createScaledTimer(ScaledRangeTimerManager::TimerType timer_type, TimerCb cb) override;
TimerPtr createScaledTimer(ScaledTimerMinimum minimum, TimerCb cb) override;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have some basic tests for these methods in dispatcher_impl_test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


Event::SchedulableCallbackPtr createSchedulableCallback(std::function<void()> cb) override;
void deferredDelete(DeferredDeletablePtr&& to_delete) override;
void exit() override;
Expand Down Expand Up @@ -154,6 +158,7 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>,
bool deferred_deleting_{};
MonotonicTime approximate_monotonic_time_;
WatchdogRegistrationPtr watchdog_registration_;
const ScaledRangeTimerManagerPtr scaled_timer_manager_;
};

} // namespace Event
Expand Down
17 changes: 15 additions & 2 deletions source/common/event/scaled_range_timer_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,28 @@ class ScaledRangeTimerManagerImpl::RangeTimerImpl final : public Timer {
const ScopeTrackedObject* scope_;
};

ScaledRangeTimerManagerImpl::ScaledRangeTimerManagerImpl(Dispatcher& dispatcher)
: dispatcher_(dispatcher), scale_factor_(1.0) {}
ScaledRangeTimerManagerImpl::ScaledRangeTimerManagerImpl(
Dispatcher& dispatcher, const TimerTypeMapConstSharedPtr& timer_minimums)
: dispatcher_(dispatcher),
timer_minimums_(timer_minimums != nullptr ? timer_minimums
: std::make_shared<TimerTypeMap>()),
scale_factor_(1.0) {}

ScaledRangeTimerManagerImpl::~ScaledRangeTimerManagerImpl() {
// Scaled timers created by the manager shouldn't outlive it. This is
// necessary but not sufficient to guarantee that.
ASSERT(queues_.empty());
}

TimerPtr ScaledRangeTimerManagerImpl::createTimer(TimerType timer_type, TimerCb callback) {
const auto minimum_it = timer_minimums_->find(timer_type);
const Event::ScaledTimerMinimum minimum =
minimum_it != timer_minimums_->end()
? minimum_it->second
: Event::ScaledTimerMinimum(Event::ScaledMinimum(UnitFloat::max()));
return createTimer(minimum, std::move(callback));
}

TimerPtr ScaledRangeTimerManagerImpl::createTimer(ScaledTimerMinimum minimum, TimerCb callback) {
return std::make_unique<RangeTimerImpl>(minimum, callback, *this);
}
Expand Down
Loading