/* * Copyright (c) Facebook, Inc. and its 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 #include #include #include #include namespace folly { namespace fibers { class Fiber; class FiberManager; /** * @class Baton * * Primitive which allows one to put current Fiber to sleep and wake it from * another Fiber/thread. */ class Baton { public: class TimeoutHandler; class Waiter { public: virtual void post() = 0; virtual ~Waiter() {} }; Baton() noexcept; ~Baton() noexcept = default; bool ready() const { auto state = waiter_.load(); return state == POSTED; } /** * Registers a waiter for the baton. The waiter will be notified when * the baton is posted. */ void setWaiter(Waiter& waiter); /** * Puts active fiber to sleep. Returns when post is called. */ void wait(); /** * Put active fiber to sleep indefinitely. However, timeoutHandler may * be used elsewhere on the same thread in order to schedule a wakeup * for the active fiber. Users of timeoutHandler must be on the same thread * as the active fiber and may only schedule one timeout, which must occur * after the active fiber calls wait. */ void wait(TimeoutHandler& timeoutHandler); /** * Puts active fiber to sleep. Returns when post is called. * * @param mainContextFunc this function is immediately executed on the main * context. */ template void wait(F&& mainContextFunc); /** * Checks if the baton has been posted without blocking. * * @return true iff the baton has been posted. */ bool try_wait(); /** * Puts active fiber to sleep. Returns when post is called or the timeout * expires. * * @param timeout Baton will be automatically awaken if timeout expires * * @return true if was posted, false if timeout expired */ template bool try_wait_for(const std::chrono::duration& timeout) { return try_wait_for(timeout, [] {}); } /** * Puts active fiber to sleep. Returns when post is called or the timeout * expires. * * @param timeout Baton will be automatically awaken if timeout expires * @param mainContextFunc this function is immediately executed on the main * context. * * @return true if was posted, false if timeout expired */ template bool try_wait_for( const std::chrono::duration& timeout, F&& mainContextFunc); /** * Puts active fiber to sleep. Returns when post is called or the deadline * expires. * * @param deadline Baton will be automatically awaken if deadline expires * * @return true if was posted, false if timeout expired */ template bool try_wait_until( const std::chrono::time_point& deadline) { return try_wait_until(deadline, [] {}); } /** * Puts active fiber to sleep. Returns when post is called or the deadline * expires. * * @param deadline Baton will be automatically awaken if deadline expires * @param mainContextFunc this function is immediately executed on the main * context. * * @return true if was posted, false if timeout expired */ template bool try_wait_until( const std::chrono::time_point& deadline, F&& mainContextFunc); /** * Puts active fiber to sleep. Returns when post is called or the deadline * expires. * * @param deadline Baton will be automatically awaken if deadline expires * @param mainContextFunc this function is immediately executed on the main * context. * * @return true if was posted, false if timeout expired */ template bool try_wait_for( const std::chrono::time_point& deadline, F&& mainContextFunc); /// Alias to try_wait_for. Deprecated. template bool timed_wait(const std::chrono::duration& timeout) { return try_wait_for(timeout); } /// Alias to try_wait_for. Deprecated. template bool timed_wait( const std::chrono::duration& timeout, F&& mainContextFunc) { return try_wait_for(timeout, static_cast(mainContextFunc)); } /// Alias to try_wait_until. Deprecated. template bool timed_wait(const std::chrono::time_point& deadline) { return try_wait_until(deadline); } /// Alias to try_wait_until. Deprecated. template bool timed_wait( const std::chrono::time_point& deadline, F&& mainContextFunc) { return try_wait_until(deadline, static_cast(mainContextFunc)); } /** * Wakes up Fiber which was waiting on this Baton (or if no Fiber is waiting, * next wait() call will return immediately). */ void post(); /** * Reset's the baton (equivalent to destroying the object and constructing * another one in place). * Caller is responsible for making sure no one is waiting on/posting the * baton when reset() is called. */ void reset(); /** * Provides a way to schedule a wakeup for a wait()ing fiber. * A TimeoutHandler must be passed to Baton::wait(TimeoutHandler&) * before a timeout is scheduled. It is only safe to use the * TimeoutHandler on the same thread as the wait()ing fiber. * scheduleTimeout() may only be called once prior to the end of the * associated Baton's life. */ class TimeoutHandler final : public HHWheelTimer::Callback { public: void scheduleTimeout(std::chrono::milliseconds timeout); private: friend class Baton; std::function timeoutFunc_{nullptr}; FiberManager* fiberManager_{nullptr}; void timeoutExpired() noexcept override { assert(timeoutFunc_ != nullptr); timeoutFunc_(); } void callbackCanceled() noexcept override {} }; private: class FiberWaiter; explicit Baton(intptr_t state) : waiter_(state) {} void postHelper(intptr_t new_value); void postThread(); void waitThread(); template inline void waitFiber(FiberManager& fm, F&& mainContextFunc); template bool timedWaitThread( const std::chrono::time_point& deadline); static constexpr intptr_t NO_WAITER = 0; static constexpr intptr_t POSTED = -1; static constexpr intptr_t TIMEOUT = -2; static constexpr intptr_t THREAD_WAITING = -3; union { std::atomic waiter_; struct { folly::detail::Futex<> futex{}; int32_t _unused_packing; } futex_; }; }; #if FOLLY_HAS_COROUTINES namespace detail { class BatonAwaitableWaiter : public Baton::Waiter { public: explicit BatonAwaitableWaiter(Baton& baton) : baton_(baton) {} void post() override { assert(h_); h_(); } bool await_ready() const { return baton_.ready(); } void await_resume() {} void await_suspend(coro::coroutine_handle<> h) { assert(!h_); h_ = std::move(h); baton_.setWaiter(*this); } private: coro::coroutine_handle<> h_; Baton& baton_; }; } // namespace detail inline detail::BatonAwaitableWaiter /* implicit */ operator co_await( Baton& baton) { return detail::BatonAwaitableWaiter(baton); } #endif } // namespace fibers } // namespace folly #include