diff options
Diffstat (limited to 'src/mongo/util')
-rw-r--r-- | src/mongo/util/background_thread_clock_source.cpp | 74 | ||||
-rw-r--r-- | src/mongo/util/background_thread_clock_source.h | 19 | ||||
-rw-r--r-- | src/mongo/util/background_thread_clock_source_test.cpp | 74 |
3 files changed, 152 insertions, 15 deletions
diff --git a/src/mongo/util/background_thread_clock_source.cpp b/src/mongo/util/background_thread_clock_source.cpp index 81b4666760b..d55e046a845 100644 --- a/src/mongo/util/background_thread_clock_source.cpp +++ b/src/mongo/util/background_thread_clock_source.cpp @@ -26,6 +26,8 @@ * it in the license file. */ +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl + #include "mongo/platform/basic.h" #include "mongo/util/background_thread_clock_source.h" @@ -35,21 +37,21 @@ #include "mongo/stdx/memory.h" #include "mongo/stdx/thread.h" +#include "mongo/util/log.h" #include "mongo/util/time_support.h" namespace mongo { BackgroundThreadClockSource::BackgroundThreadClockSource(std::unique_ptr<ClockSource> clockSource, Milliseconds granularity) - : _clockSource(std::move(clockSource)), _granularity(granularity), _shutdownTimer(false) { - _updateCurrent(); + : _clockSource(std::move(clockSource)), _granularity(granularity) { _startTimerThread(); } BackgroundThreadClockSource::~BackgroundThreadClockSource() { { stdx::unique_lock<stdx::mutex> lock(_mutex); - _shutdownTimer = true; + _inShutdown = true; _condition.notify_one(); } @@ -61,7 +63,36 @@ Milliseconds BackgroundThreadClockSource::getPrecision() { } Date_t BackgroundThreadClockSource::now() { - return Date_t::fromMillisSinceEpoch(_current.load()); + // Since this is called very frequently by many threads, the common case should not write to + // shared memory. + if (MONGO_unlikely(_timerWillPause.load())) { + return _slowNow(); + } + auto now = _current.load(); + if (MONGO_unlikely(!now)) { + return _slowNow(); + } + return Date_t::fromMillisSinceEpoch(now); +} + +// This will be called at most once per _granularity per thread. In common cases it will only be +// called by a single thread per _granularity. +Date_t BackgroundThreadClockSource::_slowNow() { + _timerWillPause.store(false); + auto now = _current.load(); + if (!now) { + stdx::lock_guard<stdx::mutex> lock(_mutex); + // Reload and check after locking since someone else may have done this for us. + now = _current.load(); + if (!now) { + // Wake up timer but have it pause if no else calls now() for the next _granularity. + _condition.notify_one(); + _timerWillPause.store(true); + + now = _updateCurrent_inlock(); + } + } + return Date_t::fromMillisSinceEpoch(now); } void BackgroundThreadClockSource::_startTimerThread() { @@ -69,17 +100,40 @@ void BackgroundThreadClockSource::_startTimerThread() { // and wakes up to store the current time. _timer = stdx::thread([&]() { stdx::unique_lock<stdx::mutex> lock(_mutex); - while (!_shutdownTimer) { - if (_condition.wait_for(lock, _granularity.toSystemDuration()) == - stdx::cv_status::timeout) { - _updateCurrent(); + _started = true; + _condition.notify_one(); + + while (!_inShutdown) { + if (!_timerWillPause.swap(true)) { + _updateCurrent_inlock(); + } else { + // Stop running if nothing has read the time since we last updated the time. + _current.store(0); + _condition.wait(lock, [this] { return _inShutdown || _current.load() != 0; }); } + + _condition.wait_for( + lock, _granularity.toSystemDuration(), [this] { return _inShutdown; }); } }); + + // Wait for the thread to start. This prevents other threads from calling now() until the timer + // thread is at its first wait() call. While the code would work without this, it makes startup + // more predictable and therefore easier to test. + stdx::unique_lock<stdx::mutex> lock(_mutex); + _condition.wait(lock, [this] { return _started; }); } -void BackgroundThreadClockSource::_updateCurrent() { - _current.store(_clockSource->now().toMillisSinceEpoch()); +int64_t BackgroundThreadClockSource::_updateCurrent_inlock() { + auto now = _clockSource->now().toMillisSinceEpoch(); + if (!now) { + // We use 0 to indicate that the thread isn't running. + severe() << "ClockSource " << demangleName(typeid(*_clockSource)) << " reported time 0." + << " Is it 1970?"; + fassertFailed(40399); + } + _current.store(now); + return now; } } // namespace mongo diff --git a/src/mongo/util/background_thread_clock_source.h b/src/mongo/util/background_thread_clock_source.h index 6ea43ed86de..55d0096f58c 100644 --- a/src/mongo/util/background_thread_clock_source.h +++ b/src/mongo/util/background_thread_clock_source.h @@ -45,7 +45,8 @@ namespace mongo { /** * A clock source that uses a periodic timer to build a low-resolution, fast-to-read clock. * Essentially uses a background thread that repeatedly sleeps for X amount of milliseconds - * and wakes up to store the current time. + * and wakes up to store the current time. If nothing reads the time for a whole granularity, the + * thread will sleep until it is needed again. */ class BackgroundThreadClockSource final : public ClockSource { MONGO_DISALLOW_COPYING(BackgroundThreadClockSource); @@ -56,18 +57,28 @@ public: Milliseconds getPrecision() override; Date_t now() override; + /** + * Doesn't count as a call to now() for determining whether this ClockSource is idle. + */ + int64_t peekNowForTest() const { + return _current.load(); + } + private: + Date_t _slowNow(); void _startTimerThread(); - void _updateCurrent(); + int64_t _updateCurrent_inlock(); const std::unique_ptr<ClockSource> _clockSource; - AtomicInt64 _current; + AtomicInt64 _current{0}; // 0 if _timer is paused due to idleness. + AtomicBool _timerWillPause{true}; // If true when _timer wakes up, it will pause. const Milliseconds _granularity; stdx::mutex _mutex; stdx::condition_variable _condition; - bool _shutdownTimer; + bool _inShutdown = false; + bool _started = false; stdx::thread _timer; }; diff --git a/src/mongo/util/background_thread_clock_source_test.cpp b/src/mongo/util/background_thread_clock_source_test.cpp index 69bb997ea56..fe8b1b22c5e 100644 --- a/src/mongo/util/background_thread_clock_source_test.cpp +++ b/src/mongo/util/background_thread_clock_source_test.cpp @@ -44,11 +44,16 @@ public: void setUpClocks(Milliseconds granularity) { auto csMock = stdx::make_unique<ClockSourceMock>(); _csMock = csMock.get(); + _csMock->advance(granularity); // Make sure the mock doesn't return time 0. _btcs = stdx::make_unique<BackgroundThreadClockSource>(std::move(csMock), granularity); } + void tearDown() override { + _btcs.reset(); + } + protected: - std::unique_ptr<ClockSource> _btcs; + std::unique_ptr<BackgroundThreadClockSource> _btcs; ClockSourceMock* _csMock; }; @@ -74,4 +79,71 @@ TEST_F(BTCSTest, GetPrecision) { ASSERT_EQUALS(_btcs->getPrecision(), Milliseconds(1)); } +TEST_F(BTCSTest, StartsPaused) { + setUpClocks(Milliseconds(1)); + ASSERT_EQUALS(_btcs->peekNowForTest(), 0); +} + +TEST_F(BTCSTest, PausesAfterRead) { + setUpClocks(Milliseconds(100)); + + // Wake it up. + auto now = _btcs->now().toMillisSinceEpoch(); + ASSERT_NE(now, 0); + ASSERT_EQ(_btcs->peekNowForTest(), now); + _csMock->advance(Milliseconds(100)); + ASSERT_EQ(_btcs->now().toMillisSinceEpoch(), now); + + sleepFor(Seconds(1)); // give the _btcs opportunity to detect idleness. + ASSERT_EQ(_btcs->peekNowForTest(), 0); +} + +TEST_F(BTCSTest, DoesntPauseWhenInUse) { + setUpClocks(Milliseconds(100)); + + auto lastTime = _btcs->now().toMillisSinceEpoch(); + ASSERT_NE(lastTime, 0); + ASSERT_EQ(lastTime, _btcs->now().toMillisSinceEpoch()); // Mark the timer as still in use. + auto ticks = 0; // Count of when times change. + while (ticks < 10) { + if (_btcs->peekNowForTest() == lastTime) { + _csMock->advance(Milliseconds(1)); + ASSERT_LT(_csMock->now().toMillisSinceEpoch() - lastTime, 1000); + sleepFor(Milliseconds(1)); + continue; + } + ticks++; + + ASSERT_NE(_btcs->peekNowForTest(), 0); + lastTime = _btcs->now().toMillisSinceEpoch(); + ASSERT_NE(lastTime, 0); + ASSERT_EQ(lastTime, _btcs->peekNowForTest()); + } +} + +TEST_F(BTCSTest, WakesAfterPause) { + setUpClocks(Milliseconds(10)); + + // Wake it up. + auto now = _btcs->now().toMillisSinceEpoch(); + ASSERT_NE(now, 0); + ASSERT_EQ(_btcs->peekNowForTest(), now); + _csMock->advance(Milliseconds(10)); + ASSERT_EQ(_btcs->now().toMillisSinceEpoch(), now); + + sleepFor(Seconds(1)); // give the _btcs opportunity to detect idleness. + ASSERT_EQ(_btcs->peekNowForTest(), 0); + + // Wake it up again and ensure it ticks at least once. + auto lastTime = _btcs->now().toMillisSinceEpoch(); + ASSERT_NE(lastTime, 0); + ASSERT_EQ(lastTime, _btcs->now().toMillisSinceEpoch()); // Mark the timer as still in use. + while (_btcs->peekNowForTest() == lastTime) { + _csMock->advance(Milliseconds(1)); + ASSERT_LT(_csMock->now().toMillisSinceEpoch() - lastTime, 1000); + sleepFor(Milliseconds(1)); + } + ASSERT_NE(_btcs->peekNowForTest(), 0); +} + } // namespace |