1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 #ifndef ROCKSDB_LITE
7 
8 #include "utilities/transactions/transaction_db_mutex_impl.h"
9 
10 #include <chrono>
11 #include <condition_variable>
12 #include <functional>
13 #include <mutex>
14 
15 #include "rocksdb/utilities/transaction_db_mutex.h"
16 
17 namespace ROCKSDB_NAMESPACE {
18 
19 class TransactionDBMutexImpl : public TransactionDBMutex {
20  public:
TransactionDBMutexImpl()21   TransactionDBMutexImpl() {}
~TransactionDBMutexImpl()22   ~TransactionDBMutexImpl() override {}
23 
24   Status Lock() override;
25 
26   Status TryLockFor(int64_t timeout_time) override;
27 
UnLock()28   void UnLock() override { mutex_.unlock(); }
29 
30   friend class TransactionDBCondVarImpl;
31 
32  private:
33   std::mutex mutex_;
34 };
35 
36 class TransactionDBCondVarImpl : public TransactionDBCondVar {
37  public:
TransactionDBCondVarImpl()38   TransactionDBCondVarImpl() {}
~TransactionDBCondVarImpl()39   ~TransactionDBCondVarImpl() override {}
40 
41   Status Wait(std::shared_ptr<TransactionDBMutex> mutex) override;
42 
43   Status WaitFor(std::shared_ptr<TransactionDBMutex> mutex,
44                  int64_t timeout_time) override;
45 
Notify()46   void Notify() override { cv_.notify_one(); }
47 
NotifyAll()48   void NotifyAll() override { cv_.notify_all(); }
49 
50  private:
51   std::condition_variable cv_;
52 };
53 
54 std::shared_ptr<TransactionDBMutex>
AllocateMutex()55 TransactionDBMutexFactoryImpl::AllocateMutex() {
56   return std::shared_ptr<TransactionDBMutex>(new TransactionDBMutexImpl());
57 }
58 
59 std::shared_ptr<TransactionDBCondVar>
AllocateCondVar()60 TransactionDBMutexFactoryImpl::AllocateCondVar() {
61   return std::shared_ptr<TransactionDBCondVar>(new TransactionDBCondVarImpl());
62 }
63 
Lock()64 Status TransactionDBMutexImpl::Lock() {
65   mutex_.lock();
66   return Status::OK();
67 }
68 
TryLockFor(int64_t timeout_time)69 Status TransactionDBMutexImpl::TryLockFor(int64_t timeout_time) {
70   bool locked = true;
71 
72   if (timeout_time == 0) {
73     locked = mutex_.try_lock();
74   } else {
75     // Previously, this code used a std::timed_mutex.  However, this was changed
76     // due to known bugs in gcc versions < 4.9.
77     // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
78     //
79     // Since this mutex isn't held for long and only a single mutex is ever
80     // held at a time, it is reasonable to ignore the lock timeout_time here
81     // and only check it when waiting on the condition_variable.
82     mutex_.lock();
83   }
84 
85   if (!locked) {
86     // timeout acquiring mutex
87     return Status::TimedOut(Status::SubCode::kMutexTimeout);
88   }
89 
90   return Status::OK();
91 }
92 
Wait(std::shared_ptr<TransactionDBMutex> mutex)93 Status TransactionDBCondVarImpl::Wait(
94     std::shared_ptr<TransactionDBMutex> mutex) {
95   auto mutex_impl = reinterpret_cast<TransactionDBMutexImpl*>(mutex.get());
96 
97   std::unique_lock<std::mutex> lock(mutex_impl->mutex_, std::adopt_lock);
98   cv_.wait(lock);
99 
100   // Make sure unique_lock doesn't unlock mutex when it destructs
101   lock.release();
102 
103   return Status::OK();
104 }
105 
WaitFor(std::shared_ptr<TransactionDBMutex> mutex,int64_t timeout_time)106 Status TransactionDBCondVarImpl::WaitFor(
107     std::shared_ptr<TransactionDBMutex> mutex, int64_t timeout_time) {
108   Status s;
109 
110   auto mutex_impl = reinterpret_cast<TransactionDBMutexImpl*>(mutex.get());
111   std::unique_lock<std::mutex> lock(mutex_impl->mutex_, std::adopt_lock);
112 
113   if (timeout_time < 0) {
114     // If timeout is negative, do not use a timeout
115     cv_.wait(lock);
116   } else {
117     auto duration = std::chrono::microseconds(timeout_time);
118     auto cv_status = cv_.wait_for(lock, duration);
119 
120     // Check if the wait stopped due to timing out.
121     if (cv_status == std::cv_status::timeout) {
122       s = Status::TimedOut(Status::SubCode::kMutexTimeout);
123     }
124   }
125 
126   // Make sure unique_lock doesn't unlock mutex when it destructs
127   lock.release();
128 
129   // CV was signaled, or we spuriously woke up (but didn't time out)
130   return s;
131 }
132 
133 }  // namespace ROCKSDB_NAMESPACE
134 
135 #endif  // ROCKSDB_LITE
136