summaryrefslogtreecommitdiff
path: root/src/mongo/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/util')
-rw-r--r--src/mongo/util/background_thread_clock_source.cpp74
-rw-r--r--src/mongo/util/background_thread_clock_source.h19
-rw-r--r--src/mongo/util/background_thread_clock_source_test.cpp74
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