| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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 <bthread/condition_variable.h> |
| #include <bthread/mutex.h> |
| |
| #include <climits> |
| #include <mutex> |
| |
| namespace doris { |
| |
| // A reader-writer lock for bthread contexts. It is a port of libc++'s |
| // std::shared_mutex (the two-gate condition-variable algorithm) onto |
| // bthread::Mutex/bthread::ConditionVariable. Unlike std::shared_mutex |
| // (pthread_rwlock_t), ownership carries no OS-thread identity, so it is safe to |
| // lock on one bthread worker and unlock on another after a bthread migrates. |
| // Satisfies the C++ SharedMutex requirements (usable with std::unique_lock / |
| // std::shared_lock). Writer-preferring. |
| class BthreadSharedMutex { |
| public: |
| BthreadSharedMutex() = default; |
| ~BthreadSharedMutex() = default; |
| |
| BthreadSharedMutex(const BthreadSharedMutex&) = delete; |
| BthreadSharedMutex& operator=(const BthreadSharedMutex&) = delete; |
| |
| void lock() { |
| std::unique_lock<bthread::Mutex> lk(_mutex); |
| while (_state & _write_entered) { |
| _gate1.wait(lk); |
| } |
| _state |= _write_entered; |
| while (_state & _n_readers) { |
| _gate2.wait(lk); |
| } |
| } |
| |
| bool try_lock() { |
| std::unique_lock<bthread::Mutex> lk(_mutex); |
| if (_state == 0) { |
| _state = _write_entered; |
| return true; |
| } |
| return false; |
| } |
| |
| void unlock() { |
| std::lock_guard<bthread::Mutex> lk(_mutex); |
| _state = 0; |
| // Notify while still holding `_mutex`. Releasing the mutex before |
| // notifying can lose a wakeup with bthread::ConditionVariable when the |
| // waiter is a pthread and the notifier is a bthread (or vice versa). |
| _gate1.notify_all(); |
| } |
| |
| void lock_shared() { |
| std::unique_lock<bthread::Mutex> lk(_mutex); |
| while ((_state & _write_entered) || (_state & _n_readers) == _n_readers) { |
| _gate1.wait(lk); |
| } |
| unsigned num_readers = (_state & _n_readers) + 1; |
| _state &= ~_n_readers; |
| _state |= num_readers; |
| } |
| |
| bool try_lock_shared() { |
| std::unique_lock<bthread::Mutex> lk(_mutex); |
| unsigned num_readers = _state & _n_readers; |
| if (!(_state & _write_entered) && num_readers != _n_readers) { |
| ++num_readers; |
| _state &= ~_n_readers; |
| _state |= num_readers; |
| return true; |
| } |
| return false; |
| } |
| |
| void unlock_shared() { |
| std::unique_lock<bthread::Mutex> lk(_mutex); |
| unsigned num_readers = (_state & _n_readers) - 1; |
| _state &= ~_n_readers; |
| _state |= num_readers; |
| // Notify while still holding `_mutex` (do not unlock first): with |
| // bthread::ConditionVariable a notify issued after releasing the mutex |
| // can be lost across pthread/bthread contexts, leaving a writer parked |
| // forever on a reader count that is already zero. |
| if (_state & _write_entered) { |
| if (num_readers == 0) { |
| _gate2.notify_one(); |
| } |
| } else { |
| if (num_readers == _n_readers - 1) { |
| _gate1.notify_one(); |
| } |
| } |
| } |
| |
| private: |
| bthread::Mutex _mutex; |
| bthread::ConditionVariable _gate1; |
| bthread::ConditionVariable _gate2; |
| unsigned _state {0}; |
| |
| // High bit: a writer has entered. Remaining bits: active reader count. |
| static constexpr unsigned _write_entered = 1U << (sizeof(unsigned) * CHAR_BIT - 1); |
| static constexpr unsigned _n_readers = ~_write_entered; |
| }; |
| |
| } // namespace doris |