| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef LIB_CONCURRENT_SEQLOCK_INC_H_ |
| #define LIB_CONCURRENT_SEQLOCK_INC_H_ |
| |
| #include <lib/concurrent/seqlock.h> |
| |
| namespace concurrent { |
| namespace internal { |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| typename SeqLock<Osal, kSyncOpt>::ReadTransactionToken |
| SeqLock<Osal, kSyncOpt>::BeginReadTransaction() { |
| SequenceNumber seq_num; |
| |
| while (((seq_num = seq_num_.load(std::memory_order_acquire)) & 0x1) != 0) { |
| Osal::ArchYield(); |
| } |
| |
| return ReadTransactionToken{seq_num}; |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| bool SeqLock<Osal, kSyncOpt>::TryBeginReadTransaction(ReadTransactionToken& out_token, |
| zx_duration_t timeout) { |
| return TryBeginReadTransactionDeadline(out_token, Osal::GetClockMonotonic() + timeout); |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| bool SeqLock<Osal, kSyncOpt>::TryBeginReadTransactionDeadline(ReadTransactionToken& out_token, |
| zx_time_t deadline) { |
| while (((out_token.seq_num_ = seq_num_.load(std::memory_order_acquire)) & 0x1) != 0) { |
| if (Osal::GetClockMonotonic() >= deadline) { |
| return false; |
| } |
| Osal::ArchYield(); |
| } |
| |
| return true; |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| bool SeqLock<Osal, kSyncOpt>::EndReadTransaction(ReadTransactionToken token) { |
| if ((token.seq_num_ & 0x1) != 0) { |
| return false; |
| } |
| |
| // If we are using fence-to-fence synchronization, this is the place we |
| // need to put our acquire fence. |
| if constexpr (kSyncOpt == SyncOpt::Fence) { |
| std::atomic_thread_fence(std::memory_order_acquire); |
| } |
| |
| return seq_num_.load(std::memory_order_relaxed) == token.seq_num_; |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| void SeqLock<Osal, kSyncOpt>::Acquire() { |
| while (true) { |
| SequenceNumber expected; |
| |
| // Wait until we observe an even sequence number. |
| while (((expected = seq_num_.load(std::memory_order_relaxed)) & 0x1) != 0) { |
| Osal::ArchYield(); |
| } |
| |
| // Attempt to increment the even number we observed to be an odd number, |
| // with Acquire semantics on the RMW if we succeed. |
| if (seq_num_.compare_exchange_strong(expected, expected + 1, std::memory_order_acquire, |
| std::memory_order_relaxed)) { |
| // If we are using fence-to-fence synchronization, this is the place we |
| // need to put our release fence. |
| if constexpr (kSyncOpt == SyncOpt::Fence) { |
| std::atomic_thread_fence(std::memory_order_release); |
| } |
| break; |
| } |
| } |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| bool SeqLock<Osal, kSyncOpt>::TryAcquire(zx_duration_t timeout) { |
| return TryAcquireDeadline(Osal::GetClockMonotonic() + timeout); |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| bool SeqLock<Osal, kSyncOpt>::TryAcquireDeadline(zx_time_t deadline) { |
| while (true) { |
| SequenceNumber expected; |
| |
| // Wait until we observe an even sequence number. Bail out if we exceed our |
| // deadline. |
| while (((expected = seq_num_.load(std::memory_order_relaxed)) & 0x1) != 0) { |
| if (Osal::GetClockMonotonic() >= deadline) { |
| return false; |
| } |
| Osal::ArchYield(); |
| } |
| |
| // Attempt to increment the even number we observed to be an odd number, |
| // with Acquire semantics on the RMW if we succeed. |
| if (seq_num_.compare_exchange_strong(expected, expected + 1, std::memory_order_acquire, |
| std::memory_order_relaxed)) { |
| // If we are using fence-to-fence synchronization, this is the place we |
| // need to put our release fence. |
| if constexpr (kSyncOpt == SyncOpt::Fence) { |
| std::atomic_thread_fence(std::memory_order_release); |
| } |
| return true; |
| } |
| |
| // Make sure we check our deadline again. We don't want to be in a |
| // situation where the relaxed load (at the start of the while) always sees |
| // an even number, but the subsequent CMPX always fails, causing us to never |
| // check our deadline. |
| if (Osal::GetClockMonotonic() >= deadline) { |
| return false; |
| } |
| } |
| } |
| |
| template <typename Osal, SyncOpt kSyncOpt> |
| void SeqLock<Osal, kSyncOpt>::Release() { |
| [[maybe_unused]] SequenceNumber before = seq_num_.fetch_add(1, std::memory_order_release); |
| ZX_DEBUG_ASSERT((before & 0x1) != 0); |
| } |
| |
| } // namespace internal |
| } // namespace concurrent |
| |
| #endif // LIB_CONCURRENT_SEQLOCK_INC_H_ |