// Copyright 2023 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/synchronization/internal/futex_waiter.h" #ifdef ABSL_INTERNAL_HAVE_FUTEX_WAITER #include #include #include #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/thread_identity.h" #include "absl/base/optimization.h" #include "absl/synchronization/internal/kernel_timeout.h" #include "absl/synchronization/internal/futex.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace synchronization_internal { #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr char FutexWaiter::kName[]; #endif int FutexWaiter::WaitUntil(std::atomic* v, int32_t val, KernelTimeout t) { #ifdef CLOCK_MONOTONIC constexpr bool kHasClockMonotonic = true; #else constexpr bool kHasClockMonotonic = false; #endif // We can't call Futex::WaitUntil() here because the prodkernel implementation // does not know about KernelTimeout::SupportsSteadyClock(). if (!t.has_timeout()) { return Futex::Wait(v, val); } else if (kHasClockMonotonic && KernelTimeout::SupportsSteadyClock() && t.is_relative_timeout()) { auto rel_timespec = t.MakeRelativeTimespec(); return Futex::WaitRelativeTimeout(v, val, &rel_timespec); } else { auto abs_timespec = t.MakeAbsTimespec(); return Futex::WaitAbsoluteTimeout(v, val, &abs_timespec); } } bool FutexWaiter::Wait(KernelTimeout t) { // Loop until we can atomically decrement futex from a positive // value, waiting on a futex while we believe it is zero. // Note that, since the thread ticker is just reset, we don't need to check // whether the thread is idle on the very first pass of the loop. bool first_pass = true; while (true) { int32_t x = futex_.load(std::memory_order_relaxed); while (x != 0) { if (!futex_.compare_exchange_weak(x, x - 1, std::memory_order_acquire, std::memory_order_relaxed)) { continue; // Raced with someone, retry. } return true; // Consumed a wakeup, we are done. } if (!first_pass) MaybeBecomeIdle(); const int err = WaitUntil(&futex_, 0, t); if (err != 0) { if (err == -EINTR || err == -EWOULDBLOCK) { // Do nothing, the loop will retry. } else if (err == -ETIMEDOUT) { return false; } else { ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); } } first_pass = false; } } void FutexWaiter::Post() { if (futex_.fetch_add(1, std::memory_order_release) == 0) { // We incremented from 0, need to wake a potential waiter. Poke(); } } void FutexWaiter::Poke() { // Wake one thread waiting on the futex. const int err = Futex::Wake(&futex_, 1); if (ABSL_PREDICT_FALSE(err < 0)) { ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); } } } // namespace synchronization_internal ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_INTERNAL_HAVE_FUTEX_WAITER