diff --git a/include/sendspin/server/server.h b/include/sendspin/server/server.h index a1459d8..96bd313 100644 --- a/include/sendspin/server/server.h +++ b/include/sendspin/server/server.h @@ -324,8 +324,8 @@ class SendspinServer { /// and after its source's open stream is reported stopped. void stop(); - /// @brief The clock of every timestamp the server sends, in microseconds: monotonic, and - /// counting through system sleep + /// @brief The clock of every timestamp the server sends, in microseconds: monotonic, at the + /// clock's own rate, never slewed by NTP int64_t now_us() const; /// @brief The server_id clients see (spec connection.md "Identities"); empty while stopped diff --git a/src/clock.cpp b/src/clock.cpp index 92f0f53..ce5cbb8 100644 --- a/src/clock.cpp +++ b/src/clock.cpp @@ -49,7 +49,12 @@ int64_t now_us() { int64_t now_us() { constexpr int64_t US_PER_S = 1000000; timespec ts{}; +#if defined(__linux__) + // A slewed clock would change rate under the clients' time filters + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +#else clock_gettime(CLOCK_MONOTONIC, &ts); +#endif return (static_cast(ts.tv_sec) * US_PER_S) + (ts.tv_nsec / NS_PER_US); } diff --git a/src/clock.h b/src/clock.h index fa77850..0ad7f18 100644 --- a/src/clock.h +++ b/src/clock.h @@ -21,8 +21,9 @@ namespace sendspin::server { -/// @brief Monotonic microseconds that keep counting while the system sleeps: `CLOCK_MONOTONIC` -/// on Linux, `mach_continuous_time` on macOS (whose `steady_clock` stops during sleep) +/// @brief Monotonic microseconds at the clock's own rate, never slewed by NTP: +/// `CLOCK_MONOTONIC_RAW` on Linux, `mach_continuous_time` on macOS (which keeps counting while the +/// system sleeps), the esp_timer's `CLOCK_MONOTONIC` on ESP-IDF int64_t now_us(); } // namespace sendspin::server diff --git a/src/transport.cpp b/src/transport.cpp index 201333b..a3180a5 100644 --- a/src/transport.cpp +++ b/src/transport.cpp @@ -1699,17 +1699,9 @@ class Timer::Impl : public std::enable_shared_from_this { explicit Impl(asio::io_context& io) : timer_(io) {} void start(int64_t at_us, std::function on_fire) { - const uint64_t generation = ++this->generation_; + this->at_us_ = at_us; this->on_fire_ = std::move(on_fire); - this->timer_.expires_after(microseconds(std::max(at_us - now_us(), 0))); - // A weak reference: the timer's owner may drop it before the wait completes - this->timer_.async_wait([weak = this->weak_from_this(), generation](const error_code& ec) { - const std::shared_ptr self = weak.lock(); - if (!ec && self && generation == self->generation_) { - std::function on_fire = std::move(self->on_fire_); - on_fire(); - } - }); + this->wait(++this->generation_); } void cancel() { @@ -1719,8 +1711,27 @@ class Timer::Impl : public std::enable_shared_from_this { } private: + void wait(uint64_t generation) { + this->timer_.expires_after(microseconds(std::max(this->at_us_ - now_us(), 0))); + // A weak reference: the timer's owner may drop it before the wait completes + this->timer_.async_wait([weak = this->weak_from_this(), generation](const error_code& ec) { + const std::shared_ptr self = weak.lock(); + if (ec || !self || generation != self->generation_) { + return; + } + // The steady clock can run ahead of now_us(), ending the wait short of at_us + if (now_us() < self->at_us_) { + self->wait(generation); + return; + } + std::function on_fire = std::move(self->on_fire_); + on_fire(); + }); + } + asio::steady_timer timer_; std::function on_fire_; + int64_t at_us_{0}; uint64_t generation_{0}; }; diff --git a/src/transport.h b/src/transport.h index 19ad46f..e652f11 100644 --- a/src/transport.h +++ b/src/transport.h @@ -395,8 +395,8 @@ class Timer { Timer(const Timer&) = delete; Timer& operator=(const Timer&) = delete; - /// @brief Calls on_fire from the loop once now_us() reaches about at_us, replacing any - /// pending call. The wait runs on the steady clock, which can fall behind now_us(). + /// @brief Calls on_fire from the loop once now_us() has reached at_us, replacing any + /// pending one void start(int64_t at_us, std::function on_fire); void cancel(); diff --git a/tests/test_clock.cpp b/tests/test_clock.cpp index 936707b..919ef64 100644 --- a/tests/test_clock.cpp +++ b/tests/test_clock.cpp @@ -13,7 +13,7 @@ // limitations under the License. /// @file test_clock.cpp -/// @brief now_us() counts microseconds on the system's sleep-inclusive monotonic clock. +/// @brief now_us() counts microseconds on the system's raw monotonic clock. #include "clock.h" #include @@ -26,15 +26,10 @@ namespace { using sendspin::server::now_us; // The clock now_us() reads: on macOS CLOCK_MONOTONIC_RAW is mach_continuous_time, while -// CLOCK_MONOTONIC is adjusted and drifts from it. +// CLOCK_MONOTONIC is adjusted and drifts from it, on Linux by NTP's slew. int64_t monotonic_us() { -#if defined(__APPLE__) - constexpr clockid_t REFERENCE = CLOCK_MONOTONIC_RAW; -#else - constexpr clockid_t REFERENCE = CLOCK_MONOTONIC; -#endif timespec ts{}; - clock_gettime(REFERENCE, &ts); + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return (static_cast(ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000); }