Move fibers out of experimental
[folly.git] / folly / fibers / TimedMutex-inl.h
diff --git a/folly/fibers/TimedMutex-inl.h b/folly/fibers/TimedMutex-inl.h
new file mode 100644 (file)
index 0000000..96ee606
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2016 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_);
+}
+}
+}