Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/sendspin/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/clock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
kahrendt marked this conversation as resolved.
Comment thread
kahrendt marked this conversation as resolved.
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
return (static_cast<int64_t>(ts.tv_sec) * US_PER_S) + (ts.tv_nsec / NS_PER_US);
}

Expand Down
5 changes: 3 additions & 2 deletions src/clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 21 additions & 10 deletions src/transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1699,17 +1699,9 @@ class Timer::Impl : public std::enable_shared_from_this<Impl> {
explicit Impl(asio::io_context& io) : timer_(io) {}

void start(int64_t at_us, std::function<void()> 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<int64_t>(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<Impl> self = weak.lock();
if (!ec && self && generation == self->generation_) {
std::function<void()> on_fire = std::move(self->on_fire_);
on_fire();
}
});
this->wait(++this->generation_);
}

void cancel() {
Expand All @@ -1719,8 +1711,27 @@ class Timer::Impl : public std::enable_shared_from_this<Impl> {
}

private:
void wait(uint64_t generation) {
this->timer_.expires_after(microseconds(std::max<int64_t>(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<Impl> 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<void()> on_fire = std::move(self->on_fire_);
on_fire();
});
}

asio::steady_timer timer_;
std::function<void()> on_fire_;
int64_t at_us_{0};
uint64_t generation_{0};
};

Expand Down
4 changes: 2 additions & 2 deletions src/transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void()> on_fire);

void cancel();
Expand Down
11 changes: 3 additions & 8 deletions tests/test_clock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <gtest/gtest.h>
Expand All @@ -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<int64_t>(ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000);
}

Expand Down
Loading