+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * 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
+
+
+namespace folly { namespace fibers {
+
+//
+// TimedMutex implementation
+//
+
+template <typename BatonType>
+void TimedMutex<BatonType>::lock() {
+ pthread_spin_lock(&lock_);
+ if (!locked_) {
+ locked_ = true;
+ pthread_spin_unlock(&lock_);
+ return;
+ }
+
+ // Delay constructing the waiter until it is actually required.
+ // This makes a huge difference, at least in the benchmarks,
+ // when the mutex isn't locked.
+ MutexWaiter waiter;
+ waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+ waiter.baton.wait();
+}
+
+template <typename BatonType>
+template <typename Rep, typename Period>
+bool TimedMutex<BatonType>::timed_lock(
+ const std::chrono::duration<Rep, Period>& duration) {
+ pthread_spin_lock(&lock_);
+ if (!locked_) {
+ locked_ = true;
+ pthread_spin_unlock(&lock_);
+ return true;
+ }
+
+ MutexWaiter waiter;
+ waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+
+ if (!waiter.baton.timed_wait(duration)) {
+ // We timed out. Two cases:
+ // 1. We're still in the waiter list and we truly timed out
+ // 2. We're not in the waiter list anymore. This could happen if the baton
+ // times out but the mutex is unlocked before we reach this code. In this
+ // case we'll pretend we got the lock on time.
+ pthread_spin_lock(&lock_);
+ if (waiter.hook.is_linked()) {
+ waiters_.erase(waiters_.iterator_to(waiter));
+ pthread_spin_unlock(&lock_);
+ return false;
+ }
+ pthread_spin_unlock(&lock_);
+ }
+ return true;
+}
+
+template <typename BatonType>
+bool TimedMutex<BatonType>::try_lock() {
+ pthread_spin_lock(&lock_);
+ if (locked_) {
+ pthread_spin_unlock(&lock_);
+ return false;
+ }
+ locked_ = true;
+ pthread_spin_unlock(&lock_);
+ return true;
+}
+
+template <typename BatonType>
+void TimedMutex<BatonType>::unlock() {
+ pthread_spin_lock(&lock_);
+ if (waiters_.empty()) {
+ locked_ = false;
+ pthread_spin_unlock(&lock_);
+ return;
+ }
+ MutexWaiter& to_wake = waiters_.front();
+ waiters_.pop_front();
+ to_wake.baton.post();
+ pthread_spin_unlock(&lock_);
+}
+
+//
+// TimedRWMutex implementation
+//
+
+template <typename BatonType>
+void TimedRWMutex<BatonType>::read_lock() {
+ pthread_spin_lock(&lock_);
+ if (state_ == State::WRITE_LOCKED) {
+ MutexWaiter waiter;
+ read_waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+ waiter.baton.wait();
+ assert(state_ == State::READ_LOCKED);
+ return;
+ }
+ assert((state_ == State::UNLOCKED && readers_ == 0) ||
+ (state_ == State::READ_LOCKED && readers_ > 0));
+ assert(read_waiters_.empty());
+ state_ = State::READ_LOCKED;
+ readers_ += 1;
+ pthread_spin_unlock(&lock_);
+}
+
+template <typename BatonType>
+template <typename Rep, typename Period>
+bool TimedRWMutex<BatonType>::timed_read_lock(
+ const std::chrono::duration<Rep, Period>& duration) {
+ pthread_spin_lock(&lock_);
+ if (state_ == State::WRITE_LOCKED) {
+ MutexWaiter waiter;
+ read_waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+
+ if (!waiter.baton.timed_wait(duration)) {
+ // We timed out. Two cases:
+ // 1. We're still in the waiter list and we truly timed out
+ // 2. We're not in the waiter list anymore. This could happen if the baton
+ // times out but the mutex is unlocked before we reach this code. In
+ // this case we'll pretend we got the lock on time.
+ pthread_spin_lock(&lock_);
+ if (waiter.hook.is_linked()) {
+ read_waiters_.erase(read_waiters_.iterator_to(waiter));
+ pthread_spin_unlock(&lock_);
+ return false;
+ }
+ pthread_spin_unlock(&lock_);
+ }
+ return true;
+ }
+ assert((state_ == State::UNLOCKED && readers_ == 0) ||
+ (state_ == State::READ_LOCKED && readers_ > 0));
+ assert(read_waiters_.empty());
+ state_ = State::READ_LOCKED;
+ readers_ += 1;
+ pthread_spin_unlock(&lock_);
+ return true;
+}
+
+template <typename BatonType>
+bool TimedRWMutex<BatonType>::try_read_lock() {
+ pthread_spin_lock(&lock_);
+ if (state_ != State::WRITE_LOCKED) {
+ assert((state_ == State::UNLOCKED && readers_ == 0) ||
+ (state_ == State::READ_LOCKED && readers_ > 0));
+ assert(read_waiters_.empty());
+ state_ = State::READ_LOCKED;
+ readers_ += 1;
+ pthread_spin_unlock(&lock_);
+ return true;
+ }
+ pthread_spin_unlock(&lock_);
+ return false;
+}
+
+template <typename BatonType>
+void TimedRWMutex<BatonType>::write_lock() {
+ pthread_spin_lock(&lock_);
+ if (state_ == State::UNLOCKED) {
+ verify_unlocked_properties();
+ state_ = State::WRITE_LOCKED;
+ pthread_spin_unlock(&lock_);
+ return;
+ }
+ MutexWaiter waiter;
+ write_waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+ waiter.baton.wait();
+}
+
+template <typename BatonType>
+template <typename Rep, typename Period>
+bool TimedRWMutex<BatonType>::timed_write_lock(
+ const std::chrono::duration<Rep, Period>& duration) {
+ pthread_spin_lock(&lock_);
+ if (state_ == State::UNLOCKED) {
+ verify_unlocked_properties();
+ state_ = State::WRITE_LOCKED;
+ pthread_spin_unlock(&lock_);
+ return true;
+ }
+ MutexWaiter waiter;
+ write_waiters_.push_back(waiter);
+ pthread_spin_unlock(&lock_);
+
+ if (!waiter.baton.timed_wait(duration)) {
+ // We timed out. Two cases:
+ // 1. We're still in the waiter list and we truly timed out
+ // 2. We're not in the waiter list anymore. This could happen if the baton
+ // times out but the mutex is unlocked before we reach this code. In
+ // this case we'll pretend we got the lock on time.
+ pthread_spin_lock(&lock_);
+ if (waiter.hook.is_linked()) {
+ write_waiters_.erase(write_waiters_.iterator_to(waiter));
+ pthread_spin_unlock(&lock_);
+ return false;
+ }
+ pthread_spin_unlock(&lock_);
+ }
+ assert(state_ == State::WRITE_LOCKED);
+ return true;
+}
+
+template <typename BatonType>
+bool TimedRWMutex<BatonType>::try_write_lock() {
+ pthread_spin_lock(&lock_);
+ if (state_ == State::UNLOCKED) {
+ verify_unlocked_properties();
+ state_ = State::WRITE_LOCKED;
+ pthread_spin_unlock(&lock_);
+ return true;
+ }
+ pthread_spin_unlock(&lock_);
+ return false;
+}
+
+template <typename BatonType>
+void TimedRWMutex<BatonType>::unlock() {
+ pthread_spin_lock(&lock_);
+ assert(state_ != State::UNLOCKED);
+ assert((state_ == State::READ_LOCKED && readers_ > 0) ||
+ (state_ == State::WRITE_LOCKED && readers_ == 0));
+ if (state_ == State::READ_LOCKED) {
+ readers_ -= 1;
+ }
+
+ if (!read_waiters_.empty()) {
+ assert(state_ == State::WRITE_LOCKED && readers_ == 0 &&
+ "read waiters can only accumulate while write locked");
+ state_ = State::READ_LOCKED;
+ readers_ = read_waiters_.size();
+
+ while (!read_waiters_.empty()) {
+ MutexWaiter& to_wake = read_waiters_.front();
+ read_waiters_.pop_front();
+ to_wake.baton.post();
+ }
+ } else if (readers_ == 0) {
+ if (!write_waiters_.empty()) {
+ assert(read_waiters_.empty());
+ state_ = State::WRITE_LOCKED;
+
+ // Wake a single writer (after releasing the spin lock)
+ MutexWaiter& to_wake = write_waiters_.front();
+ write_waiters_.pop_front();
+ to_wake.baton.post();
+ } else {
+ verify_unlocked_properties();
+ state_ = State::UNLOCKED;
+ }
+ } else {
+ assert(state_ == State::READ_LOCKED);
+ }
+ pthread_spin_unlock(&lock_);
+}
+
+template <typename BatonType>
+void TimedRWMutex<BatonType>::downgrade() {
+ pthread_spin_lock(&lock_);
+ assert(state_ == State::WRITE_LOCKED && readers_ == 0);
+ state_ = State::READ_LOCKED;
+ readers_ += 1;
+
+ if (!read_waiters_.empty()) {
+ readers_ += read_waiters_.size();
+
+ while (!read_waiters_.empty()) {
+ MutexWaiter& to_wake = read_waiters_.front();
+ read_waiters_.pop_front();
+ to_wake.baton.post();
+ }
+ }
+ pthread_spin_unlock(&lock_);
+}
+
+}}