blob: a993b5b78670614bef7a10bb44ef31d3c006037c [file]
// 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