#pragma once #include "envoy/common/time.h" #include "envoy/event/dispatcher.h" #include "envoy/event/timer.h" #include "common/common/assert.h" #include "common/common/thread.h" #include "test/test_common/global.h" #include "test/test_common/only_one_thread.h" namespace Envoy { namespace Event { // Adds sleep() and waitFor() interfaces to Event::TimeSystem. class TestTimeSystem : public Event::TimeSystem { public: ~TestTimeSystem() override = default; /** * This class will use the real monotonic time regardless of the time system in use (real * or simulated). This should only be used when time is needed for real timeouts that govern * networking, etc. It should never be used for time that only advances explicitly for alarms. */ class RealTimeBound { public: template RealTimeBound(const D& duration) : end_time_(std::chrono::steady_clock::now() + duration) // NO_CHECK_FORMAT(real_time) {} std::chrono::milliseconds timeLeft() { const auto current_time = std::chrono::steady_clock::now(); // NO_CHECK_FORMAT(real_time) if (current_time > end_time_) { return std::chrono::milliseconds(0); } return std::chrono::duration_cast(end_time_ - current_time); } bool withinBound() { return std::chrono::steady_clock::now() < end_time_; // NO_CHECK_FORMAT(real_time) } private: const MonotonicTime end_time_; }; /** * Advances time forward by the specified duration, running any timers * scheduled to fire, and blocking until the timer callbacks are complete. * See also advanceTimeAndRun(), which provides the option to run a specific * dispatcher or scheduler after advancing the time. * * This function should be used in multi-threaded tests, where other * threads are running dispatcher loops. Integration tests should usually * use this variant. * * @param duration The amount of time to advance. */ virtual void advanceTimeWaitImpl(const Duration& duration) PURE; template void advanceTimeWait(const D& duration = false) { advanceTimeWaitImpl(std::chrono::duration_cast(duration)); } /** * Advances time forward by the specified duration. Timers on event loops outside the current * thread may trigger, but unlike advanceTimeWait(), this method does not block waiting for them * to complete. This method also takes in a parameter the dispatcher or scheduler for the current * thread, which will be run in the requested mode after advancing the time forward. * * This function should be used in single-threaded tests that want to advance time and then run * the test thread event loop. Unit tests will often use this variant. * * @param duration The amount of time to advance. * @param dispatcher_or_scheduler The event loop to run after advancing time forward. * @param mode The mode to use when running the event loop. */ template void advanceTimeAndRun(const D& duration, DispatcherOrScheduler& dispatcher_or_scheduler, Dispatcher::RunType mode) { advanceTimeAsyncImpl(std::chrono::duration_cast(duration)); dispatcher_or_scheduler.run(mode); } /** * Helper function used by the implementation of advanceTimeAndRun which just advances time * forward by the specified amount. * * @param duration The amount of time to advance. */ virtual void advanceTimeAsyncImpl(const Duration& duration) PURE; /** * Waits for the specified duration to expire, or for the condition to be satisfied, whichever * comes first. * * NOTE: This function takes a duration parameter which is the timeout of the wait. This is *real* * time in all time systems. This is to avoid test hangs and provide a useful error message. * When using simulated time this does not advance monotonic time. Thus, to simulated time * tests all network behavior will appear instantaneous. If time needs to advance to fire * alarms advanceTimeWait() or advanceTimeAsync() should be used. * * @param mutex A mutex which must be held before calling this function. * @param condition The condition to wait on. * @param duration The maximum amount of time to wait. * @return Thread::CondVar::WaitStatus whether the condition timed out or not. */ template bool waitFor(absl::Mutex& mutex, const absl::Condition& condition, const D& duration) noexcept ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { only_one_thread_.checkOneThread(); return mutex.AwaitWithTimeout(condition, absl::FromChrono(std::chrono::duration_cast(duration))); } /** * This function will perform a real sleep in all time systems (real or simulated). This function * should NOT be used without a good reason. It is either for supporting old code that needs to * be converted to an event based approach, simulated time, or some other solution. Be ready * to explain why you are using this in code review. */ template void realSleepDoNotUseWithoutScrutiny(const D& duration) { std::this_thread::sleep_for(duration); // NO_CHECK_FORMAT(real_time) } protected: Thread::OnlyOneThread only_one_thread_; }; // There should only be one instance of any time-system resident in a test // process at once. This helper class is used with Test::Global to help enforce // that with an ASSERT. Each time-system derivation should have a helper // implementation which is referenced from a delegate (see // DelegatingTestTimeSystemBase). In each delegate, a SingletonTimeSystemHelper // should be instantiated via Test::Global. Only one // instance of SingletonTimeSystemHelper per process, at a time. When all // references to the delegates are destructed, the singleton will be destroyed // as well, so each test-method will get a fresh start. class SingletonTimeSystemHelper { public: SingletonTimeSystemHelper() : time_system_(nullptr) {} using MakeTimeSystemFn = std::function()>; /** * Returns a singleton time-system, creating a default one of there's not * one already. This method is thread-safe. * * @return the time system. */ TestTimeSystem& timeSystem(const MakeTimeSystemFn& make_time_system); private: std::unique_ptr time_system_ ABSL_GUARDED_BY(mutex_); Thread::MutexBasicLockable mutex_; }; // Implements the TestTimeSystem interface, delegating implementation of all // methods to a TestTimeSystem reference supplied by a timeSystem() method in a // subclass. template class DelegatingTestTimeSystemBase : public TestTimeSystem { public: void advanceTimeAsyncImpl(const Duration& duration) override { timeSystem().advanceTimeAsyncImpl(duration); } void advanceTimeWaitImpl(const Duration& duration) override { timeSystem().advanceTimeWaitImpl(duration); } SchedulerPtr createScheduler(Scheduler& base_scheduler, CallbackScheduler& cb_scheduler) override { return timeSystem().createScheduler(base_scheduler, cb_scheduler); } SystemTime systemTime() override { return timeSystem().systemTime(); } MonotonicTime monotonicTime() override { return timeSystem().monotonicTime(); } TimeSystemVariant& operator*() { return timeSystem(); } virtual TimeSystemVariant& timeSystem() PURE; }; // Wraps a concrete time-system in a delegate that ensures there is only one // time-system of any variant resident in a process at a time. Attempts to // instantiate multiple instances of the same type of time-system will simply // reference the same shared delegate, which will be deleted when the last one // goes out of scope. Attempts to instantiate different types of type-systems // will result in a RELEASE_ASSERT. See the testcases in // test_time_system_test.cc to understand the allowable sequences. template class DelegatingTestTimeSystem : public DelegatingTestTimeSystemBase { public: DelegatingTestTimeSystem() : time_system_(initTimeSystem()) {} TimeSystemVariant& timeSystem() override { return time_system_; } private: TimeSystemVariant& initTimeSystem() { auto make_time_system = []() -> std::unique_ptr { return std::make_unique(); }; auto time_system = dynamic_cast(&singleton_->timeSystem(make_time_system)); RELEASE_ASSERT(time_system, "Two different types of time-systems allocated. If deriving from " "Event::TestUsingSimulatedTime make sure it is the first base class."); return *time_system; } Test::Global singleton_; TimeSystemVariant& time_system_; }; } // namespace Event } // namespace Envoy