/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 * * http://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. */ #pragma once #include <atomic> #include <glog/logging.h> #include <folly/Likely.h> #include <folly/detail/Futex.h> #include <folly/detail/MemoryIdler.h> #include <folly/portability/Asm.h> #include <folly/synchronization/AtomicUtil.h> #include <folly/synchronization/WaitOptions.h> #include <folly/synchronization/detail/Spin.h> namespace folly { /// SaturatingSemaphore is a flag that allows concurrent posting by /// multiple posters and concurrent non-destructive waiting by /// multiple waiters. /// /// A SaturatingSemaphore allows one or more waiter threads to check, /// spin, or block, indefinitely or with timeout, for a flag to be set /// by one or more poster threads. By setting the flag, posters /// announce to waiters (that may be already waiting or will check /// the flag in the future) that some condition is true. Posts to an /// already set flag are idempotent. /// /// SaturatingSemaphore is called so because it behaves like a hybrid /// binary/counted _semaphore_ with values zero and infinity, and /// post() and wait() functions. It is called _saturating_ because one /// post() is enough to set it to infinity and to satisfy any number /// of wait()-s. Once set (to infinity) it remains unchanged by /// subsequent post()-s and wait()-s, until it is reset() back to /// zero. /// /// The implementation of SaturatingSemaphore is based on that of /// Baton. It includes no internal padding, and is only 4 bytes in /// size. Any alignment or padding to avoid false sharing is up to /// the user. /// SaturatingSemaphore differs from Baton as follows: /// - Baton allows at most one call to post(); this allows any number /// and concurrently. /// - Baton allows at most one successful call to any wait variant; /// this allows any number and concurrently. /// /// Template parameter: /// - bool MayBlock: If false, waiting operations spin only. If /// true, timed and wait operations may block; adds an atomic /// instruction to the critical path of posters. /// /// Wait options: /// WaitOptions contains optional per call setting for spin-max duration: /// Calls to wait(), try_wait_until(), and try_wait_for() block only after the /// passage of the spin-max period. The default spin-max duration is 10 usec. /// The spin-max option is applicable only if MayBlock is true. /// /// Functions: /// bool ready(): /// Returns true if the flag is set by a call to post, otherwise false. /// Equivalent to try_wait, but available on const receivers. /// void reset(); /// Clears the flag. /// void post(); /// Sets the flag and wakes all current waiters, i.e., causes all /// concurrent calls to wait, try_wait_for, and try_wait_until to /// return. /// void wait( /// WaitOptions opt = wait_options()); /// Waits for the flag to be set by a call to post. /// bool try_wait(); /// Returns true if the flag is set by a call to post, otherwise false. /// bool try_wait_until( /// time_point& deadline, /// WaitOptions& = wait_options()); /// Returns true if the flag is set by a call to post before the /// deadline, otherwise false. /// bool try_wait_for( /// duration&, /// WaitOptions& = wait_options()); /// Returns true if the flag is set by a call to post before the /// expiration of the specified duration, otherwise false. /// /// Usage: /// @code /// SaturatingSemaphore<> f; /// ASSERT_FALSE(f.try_wait()); /// ASSERT_FALSE(f.try_wait_until( /// std::chrono::steady_clock::now() + std::chrono::microseconds(1))); /// ASSERT_FALSE(f.try_wait_until( /// std::chrono::steady_clock::now() + std::chrono::microseconds(1), /// f.wait_options().spin_max(std::chrono::microseconds(1)))); /// f.post(); /// f.post(); /// f.wait(); /// f.wait(f.wait_options().spin_max(std::chrono::nanoseconds(100))); /// ASSERT_TRUE(f.try_wait()); /// ASSERT_TRUE(f.try_wait_until( /// std::chrono::steady_clock::now() + std::chrono::microseconds(1))); /// f.wait(); /// f.reset(); /// ASSERT_FALSE(f.try_wait()); /// @endcode template <bool MayBlock = true, template <typename> class Atom = std::atomic> class SaturatingSemaphore { … }; /// /// Member function definitioons /// /** postSlowWaiterMayBlock */ template <bool MayBlock, template <typename> class Atom> FOLLY_NOINLINE void SaturatingSemaphore<MayBlock, Atom>::postSlowWaiterMayBlock( uint32_t before) noexcept { … } /** tryWaitSlow */ template <bool MayBlock, template <typename> class Atom> template <typename Clock, typename Duration> FOLLY_NOINLINE bool SaturatingSemaphore<MayBlock, Atom>::tryWaitSlow( const std::chrono::time_point<Clock, Duration>& deadline, const WaitOptions& opt) noexcept { … } } // namespace folly