From b7ae3b944cf1c40d01b891af6ab81ec2e429d2c9 Mon Sep 17 00:00:00 2001 From: Ronald Klop Date: Thu, 9 Jan 2025 11:09:02 +0100 Subject: [PATCH 1/2] Implement waitable_atomic for FreeBSD Based on the Apple implementation rewritten to use _umtx_op syscall. --- src/mongo/platform/waitable_atomic.cpp | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/mongo/platform/waitable_atomic.cpp b/src/mongo/platform/waitable_atomic.cpp index 36e85fe6dfaa5..94045adae5dc2 100644 --- a/src/mongo/platform/waitable_atomic.cpp +++ b/src/mongo/platform/waitable_atomic.cpp @@ -34,6 +34,9 @@ #ifdef __linux__ #include #include +#elif defined(__FreeBSD__) +#include +#include #elif defined(_WIN32) #include #endif @@ -235,6 +238,52 @@ bool waitUntil(const void* uaddr, return timeoutOverflow || errno != ETIMEDOUT; } +#elif defined(__FreeBSD__) + +void notifyOne(const void* uaddr) { + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, 1, NULL, NULL); +} + +void notifyMany(const void* uaddr, int nToWake) { + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, nToWake, NULL, NULL); +} + +void notifyAll(const void* uaddr) { + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, INT_MAX, NULL, NULL); +} + +bool waitUntil(const void* uaddr, + uint32_t old, + boost::optional deadline) { + struct timespec timeout; + bool timeoutOverflow = false; + if (deadline) { + int64_t micros = durationCount(*deadline - system_clock::now()); + if (micros <= 0) { + return false; // Synthesize a timeout. + } + + if (micros > int64_t(std::numeric_limits::max())) { + // 2**32 micros is a little over an hour. If this happens, we wait as long as we can, + // then return as-if a spurious wakeup happened, rather than a timeout. This will cause + // the caller to loop and we will compute a smaller time each pass, eventually reaching + // a representable timeout. + micros = std::numeric_limits::max(); + timeoutOverflow = true; + } + + timeout.tv_sec = micros / 1000; + timeout.tv_nsec = (micros % 1000) * 1000; + } + + if (_umtx_op(const_cast(uaddr), UMTX_OP_WAIT, old, (void*)sizeof(struct timespec), &timeout) != -1) + return true; + + // There isn't a good list of possible errors, so assuming that anything other than a timeout + // error is a possible spurious wakeup. + return timeoutOverflow || errno != ETIMEDOUT; +} + #else #error "Need an implementation of waitUntil(), notifyOne(), notifyMany(), notifyAll() for this OS" #endif From 5daa46ff811d83b885095e7219e4a5072993b74e Mon Sep 17 00:00:00 2001 From: Ronald Klop Date: Tue, 4 Feb 2025 11:27:20 +0100 Subject: [PATCH 2/2] Reimplement waitable_atomic.cpp to use absolute times. Similar to the Linux implementation instead of the MacOS version. --- src/mongo/platform/waitable_atomic.cpp | 45 +++++++++++--------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/mongo/platform/waitable_atomic.cpp b/src/mongo/platform/waitable_atomic.cpp index 94045adae5dc2..b62f52738873f 100644 --- a/src/mongo/platform/waitable_atomic.cpp +++ b/src/mongo/platform/waitable_atomic.cpp @@ -241,47 +241,40 @@ bool waitUntil(const void* uaddr, #elif defined(__FreeBSD__) void notifyOne(const void* uaddr) { - _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, 1, NULL, NULL); + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE_PRIVATE, 1, NULL, NULL); } void notifyMany(const void* uaddr, int nToWake) { - _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, nToWake, NULL, NULL); + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE_PRIVATE, nToWake, NULL, NULL); } void notifyAll(const void* uaddr) { - _umtx_op(const_cast(uaddr), UMTX_OP_WAKE, INT_MAX, NULL, NULL); + _umtx_op(const_cast(uaddr), UMTX_OP_WAKE_PRIVATE, INT_MAX, NULL, NULL); } bool waitUntil(const void* uaddr, uint32_t old, boost::optional deadline) { - struct timespec timeout; - bool timeoutOverflow = false; + struct _umtx_time umtx_deadline; + void* uaddr2 = nullptr; + if (deadline) { - int64_t micros = durationCount(*deadline - system_clock::now()); - if (micros <= 0) { - return false; // Synthesize a timeout. - } + umtx_deadline._timeout.tv_sec = durationCount(deadline->time_since_epoch()); + umtx_deadline._timeout.tv_nsec = durationCount( + deadline->time_since_epoch() - stdx::chrono::seconds(umtx_deadline._timeout.tv_sec)); + umtx_deadline._flags = UMTX_ABSTIME; + umtx_deadline._clockid = CLOCK_REALTIME_FAST; + uaddr2 = &umtx_deadline; + } - if (micros > int64_t(std::numeric_limits::max())) { - // 2**32 micros is a little over an hour. If this happens, we wait as long as we can, - // then return as-if a spurious wakeup happened, rather than a timeout. This will cause - // the caller to loop and we will compute a smaller time each pass, eventually reaching - // a representable timeout. - micros = std::numeric_limits::max(); - timeoutOverflow = true; + int umtxOpRet; + if ((umtxOpRet = _umtx_op(const_cast(uaddr), UMTX_OP_WAIT_UINT_PRIVATE, old, (void*)sizeof(struct _umtx_time), uaddr2)) != 0) { + if (errno == ETIMEDOUT) { + return false; } - - timeout.tv_sec = micros / 1000; - timeout.tv_nsec = (micros % 1000) * 1000; + invariant(umtxOpRet == 0, errorMessage(lastSystemError())); } - - if (_umtx_op(const_cast(uaddr), UMTX_OP_WAIT, old, (void*)sizeof(struct timespec), &timeout) != -1) - return true; - - // There isn't a good list of possible errors, so assuming that anything other than a timeout - // error is a possible spurious wakeup. - return timeoutOverflow || errno != ETIMEDOUT; + return true; } #else