Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:54:51

0001 ///////////////////////////////////////////////////////////////////////////////
0002 // Copyright (c) Lewis Baker
0003 // Licenced under MIT license. See LICENSE.txt for details.
0004 ///////////////////////////////////////////////////////////////////////////////
0005 #ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
0006 #define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
0007 
0008 #include <cppcoro/coroutine.hpp>
0009 #include <atomic>
0010 #include <cstdint>
0011 #include <mutex> // for std::adopt_lock_t
0012 
0013 namespace cppcoro
0014 {
0015     class async_mutex_lock;
0016     class async_mutex_lock_operation;
0017     class async_mutex_scoped_lock_operation;
0018 
0019     /// \brief
0020     /// A mutex that can be locked asynchronously using 'co_await'.
0021     ///
0022     /// Ownership of the mutex is not tied to any particular thread.
0023     /// This allows the coroutine owning the lock to transition from
0024     /// one thread to another while holding a lock.
0025     ///
0026     /// Implementation is lock-free, using only std::atomic values for
0027     /// synchronisation. Awaiting coroutines are suspended without blocking
0028     /// the current thread if the lock could not be acquired synchronously.
0029     class async_mutex
0030     {
0031     public:
0032 
0033         /// \brief
0034         /// Construct to a mutex that is not currently locked.
0035         async_mutex() noexcept;
0036 
0037         /// Destroys the mutex.
0038         ///
0039         /// Behaviour is undefined if there are any outstanding coroutines
0040         /// still waiting to acquire the lock.
0041         ~async_mutex();
0042 
0043         /// \brief
0044         /// Attempt to acquire a lock on the mutex without blocking.
0045         ///
0046         /// \return
0047         /// true if the lock was acquired, false if the mutex was already locked.
0048         /// The caller is responsible for ensuring unlock() is called on the mutex
0049         /// to release the lock if the lock was acquired by this call.
0050         bool try_lock() noexcept;
0051 
0052         /// \brief
0053         /// Acquire a lock on the mutex asynchronously.
0054         ///
0055         /// If the lock could not be acquired synchronously then the awaiting
0056         /// coroutine will be suspended and later resumed when the lock becomes
0057         /// available. If suspended, the coroutine will be resumed inside the
0058         /// call to unlock() from the previous lock owner.
0059         ///
0060         /// \return
0061         /// An operation object that must be 'co_await'ed to wait until the
0062         /// lock is acquired. The result of the 'co_await m.lock_async()'
0063         /// expression has type 'void'.
0064         async_mutex_lock_operation lock_async() noexcept;
0065 
0066         /// \brief
0067         /// Acquire a lock on the mutex asynchronously, returning an object that
0068         /// will call unlock() automatically when it goes out of scope.
0069         ///
0070         /// If the lock could not be acquired synchronously then the awaiting
0071         /// coroutine will be suspended and later resumed when the lock becomes
0072         /// available. If suspended, the coroutine will be resumed inside the
0073         /// call to unlock() from the previous lock owner.
0074         ///
0075         /// \return
0076         /// An operation object that must be 'co_await'ed to wait until the
0077         /// lock is acquired. The result of the 'co_await m.scoped_lock_async()'
0078         /// expression returns an 'async_mutex_lock' object that will call
0079         /// this->mutex() when it destructs.
0080         async_mutex_scoped_lock_operation scoped_lock_async() noexcept;
0081 
0082         /// \brief
0083         /// Unlock the mutex.
0084         ///
0085         /// Must only be called by the current lock-holder.
0086         ///
0087         /// If there are lock operations waiting to acquire the
0088         /// mutex then the next lock operation in the queue will
0089         /// be resumed inside this call.
0090         void unlock();
0091 
0092     private:
0093 
0094         friend class async_mutex_lock_operation;
0095 
0096         static constexpr std::uintptr_t not_locked = 1;
0097 
0098         // assume == reinterpret_cast<std::uintptr_t>(static_cast<void*>(nullptr))
0099         static constexpr std::uintptr_t locked_no_waiters = 0;
0100 
0101         // This field provides synchronisation for the mutex.
0102         //
0103         // It can have three kinds of values:
0104         // - not_locked
0105         // - locked_no_waiters
0106         // - a pointer to the head of a singly linked list of recently
0107         //   queued async_mutex_lock_operation objects. This list is
0108         //   in most-recently-queued order as new items are pushed onto
0109         //   the front of the list.
0110         std::atomic<std::uintptr_t> m_state;
0111 
0112         // Linked list of async lock operations that are waiting to acquire
0113         // the mutex. These operations will acquire the lock in the order
0114         // they appear in this list. Waiters in this list will acquire the
0115         // mutex before waiters added to the m_newWaiters list.
0116         async_mutex_lock_operation* m_waiters;
0117 
0118     };
0119 
0120     /// \brief
0121     /// An object that holds onto a mutex lock for its lifetime and
0122     /// ensures that the mutex is unlocked when it is destructed.
0123     ///
0124     /// It is equivalent to a std::lock_guard object but requires
0125     /// that the result of co_await async_mutex::lock_async() is
0126     /// passed to the constructor rather than passing the async_mutex
0127     /// object itself.
0128     class async_mutex_lock
0129     {
0130     public:
0131 
0132         explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
0133             : m_mutex(&mutex)
0134         {}
0135 
0136         async_mutex_lock(async_mutex_lock&& other) noexcept
0137             : m_mutex(other.m_mutex)
0138         {
0139             other.m_mutex = nullptr;
0140         }
0141 
0142         async_mutex_lock(const async_mutex_lock& other) = delete;
0143         async_mutex_lock& operator=(const async_mutex_lock& other) = delete;
0144 
0145         // Releases the lock.
0146         ~async_mutex_lock()
0147         {
0148             if (m_mutex != nullptr)
0149             {
0150                 m_mutex->unlock();
0151             }
0152         }
0153 
0154     private:
0155 
0156         async_mutex* m_mutex;
0157 
0158     };
0159 
0160     class async_mutex_lock_operation
0161     {
0162     public:
0163 
0164         explicit async_mutex_lock_operation(async_mutex& mutex) noexcept
0165             : m_mutex(mutex)
0166         {}
0167 
0168         bool await_ready() const noexcept { return false; }
0169         bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
0170         void await_resume() const noexcept {}
0171 
0172     protected:
0173 
0174         friend class async_mutex;
0175 
0176         async_mutex& m_mutex;
0177 
0178     private:
0179 
0180         async_mutex_lock_operation* m_next;
0181         cppcoro::coroutine_handle<> m_awaiter;
0182 
0183     };
0184 
0185     class async_mutex_scoped_lock_operation : public async_mutex_lock_operation
0186     {
0187     public:
0188 
0189         using async_mutex_lock_operation::async_mutex_lock_operation;
0190 
0191         [[nodiscard]]
0192         async_mutex_lock await_resume() const noexcept
0193         {
0194             return async_mutex_lock{ m_mutex, std::adopt_lock };
0195         }
0196 
0197     };
0198 }
0199 
0200 #endif