| // 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 <cstdint> |
| #include <memory> |
| #include <vector> |
| |
| #include "kudu/common/txn_id.h" |
| #include "kudu/gutil/macros.h" |
| #include "kudu/tserver/tserver.pb.h" |
| #include "kudu/util/locks.h" |
| #include "kudu/util/monotime.h" |
| #include "kudu/util/semaphore.h" |
| #include "kudu/util/slice.h" |
| |
| namespace kudu { |
| |
| template <typename T> |
| class ArrayView; |
| |
| namespace tablet { |
| |
| class LockEntry; |
| class LockTable; |
| class OpState; |
| class PartitionLockState; |
| |
| // Lock manager implementation that only supports exclusive locks. It is |
| // composed of two types of lock: 'ScopedRowLock' for single-row lock and |
| // 'ScopedPartitionLock' for partition lock. |
| // |
| // For deadlock prevention of multi-row transactions, a wait-die scheme is |
| // used. When a transaction B attempts to take a lock that's already held by |
| // transaction A: |
| // - B > A: transaction B should be aborted, and applications should retry |
| // B at later time, signified by the returning of TXN_LOCKED_ABORT. |
| // - B < A: transaction B should wait for the lock to be taken by retrying the |
| // transactional op, signified by the returning of TXN_LOCKED_RETRY_OP. |
| class LockManager { |
| public: |
| LockManager(); |
| ~LockManager(); |
| |
| enum LockMode { |
| LOCK_EXCLUSIVE |
| }; |
| |
| enum LockWaitMode { |
| // The attempt to take the lock must wait until the lock is taken. This is |
| // only appropriate if it is guaranteed that the lock can be waited on |
| // without a deadlock. |
| WAIT_FOR_LOCK, |
| |
| // Try to acquire the lock with a time out, if not available return without |
| // acquiring it. |
| TRY_LOCK, |
| }; |
| |
| int64_t partition_lock_refs() const { return partition_lock_refs_; } |
| |
| private: |
| friend class ScopedPartitionLock; |
| friend class ScopedRowLock; |
| friend class LockManagerTest; |
| |
| std::vector<LockEntry*> LockBatch(ArrayView<Slice> keys, const OpState* op); |
| |
| bool TryLock(const Slice& key, const OpState* op, LockEntry** entry); |
| void Release(LockEntry* lock); |
| void ReleaseBatch(ArrayView<LockEntry*> locks); |
| |
| // Tries to acquire the partition lock with the given txn ID with the given |
| // timeout, or tries indefinitely if no timeout is set. A partition lock can |
| // only be held by a single transaction at a time; the same transaction can |
| // acquire the lock multiple times. Both transactional and non-transactional |
| // ops must try to acquire the lock (non-transactional ops are signified with |
| // an invalid 'txn_id'). |
| // |
| // If the attempt to lock fails, an appropriate error code is returned based |
| // on the transaction ID and the deadlock prevention policy described above. |
| PartitionLockState* TryAcquirePartitionLock(const TxnId& txn_id, |
| tserver::TabletServerErrorPB::Code* code, |
| const MonoDelta& timeout = MonoDelta()); |
| |
| // Similar to the above, but waits until the lock is acquired. |
| // |
| // Note that the caller is expected to ensure there is no deadlock. For |
| // example, when running on followers in the prepare phase, or running |
| // serially in an order that has already been successful with |
| // TryAcquirePartitionLock() calls. |
| PartitionLockState* WaitUntilAcquiredPartitionLock(const TxnId& txn_id); |
| void ReleasePartitionLock(); |
| |
| static void AcquireLockOnEntry(LockEntry* e, const OpState* op); |
| |
| // Semaphore used by the LockManager to signal the release of the partition |
| // lock. If its value is >= 0, the partition lock is already held, and |
| // callers can use TimedAcquire() to wait for it to be released. |
| Semaphore partition_sem_; |
| |
| // Lock to protect 'partition_lock_' and 'partition_lock_refs_'. |
| simple_spinlock p_lock_; |
| |
| // If 'partition_lock_' has been held by a transaction, |
| // 'partition_lock_refs_' keeps track of the number of times that the |
| // partition lock has been held by that transaction. |
| // |
| // NOTE: 'partition_lock_' is only set to non-null by a single thread (i.e. |
| // the prepare thread), but it may be released from a different thread (e.g. |
| // an apply thread). |
| int64_t partition_lock_refs_; |
| std::unique_ptr<PartitionLockState> partition_lock_; |
| |
| LockTable *locks_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LockManager); |
| }; |
| |
| // Hold a lock on a set of rows, for the scope of this object. |
| // Usage: |
| // { |
| // ScopedRowLock(&manager, op, my_encoded_row_keys, LOCK_EXCLUSIVE); |
| // .. do stuff with the row .. |
| // } |
| // // lock is released when the object exits its scope. |
| // |
| // This class implements C++11 move constructors and thus can be |
| // transferred around using std::move(). For example: |
| // |
| // void DoSomething(ScopedRowLock l) { |
| // // l owns the lock and will release at the end of this function |
| // } |
| // ScopedRowLock my_lock(&manager, ...); |
| // DoSomething(std::move(l); |
| // CHECK(!l.acquired()); // doesn't own lock anymore, since it moved |
| class ScopedRowLock { |
| public: |
| |
| // Construct an initially-unlocked lock holder. |
| // You can later assign this to actually hold a lock using |
| // the move-constructor: |
| // ScopedRowLock l; |
| // l = ScopedRowLock(...); |
| // or |
| // l = std::move(other_row_lock); |
| ScopedRowLock() {} |
| |
| // Lock rows in the given LockManager. The 'key' slices must remain |
| // valid and un-changed for the duration of this object's lifetime. |
| ScopedRowLock(LockManager* manager, |
| const OpState* op, |
| ArrayView<Slice> keys, |
| LockManager::LockMode mode); |
| |
| // Move constructor and assignment. |
| ScopedRowLock(ScopedRowLock&& other) noexcept; |
| ScopedRowLock& operator=(ScopedRowLock&& other) noexcept; |
| |
| void Release(); |
| |
| bool acquired() const { return !entries_.empty(); } |
| |
| ~ScopedRowLock(); |
| |
| private: |
| void TakeState(ScopedRowLock* other); |
| |
| LockManager* manager_ = nullptr; |
| |
| std::vector<LockEntry*> entries_; |
| }; |
| |
| // Similar to ScopedRowLock, hold a lock on a partition, for the scope of |
| // this object. Usage: |
| // { |
| // ScopedPartitionLock(&manager, txn_id); |
| // .. do stuff .. |
| // } |
| // // lock is released when the object exits its scope. |
| class ScopedPartitionLock { |
| public: |
| // Construct an initially-unlocked lock holder. |
| // You can later assign this to actually hold a lock using |
| // the move-constructor: |
| // ScopedPartitionLock l; |
| // l = ScopedPartitionLock(...); |
| // or |
| // l = std::move(other_partition_lock); |
| ScopedPartitionLock() |
| : code_(tserver::TabletServerErrorPB::UNKNOWN_ERROR) {} |
| |
| // 'wait_mode' indicates whether or not to wait until |
| // the lock is acquired. |
| ScopedPartitionLock(LockManager* manager, |
| const TxnId& txn_id, |
| LockManager::LockWaitMode wait_mode = LockManager::TRY_LOCK); |
| ~ScopedPartitionLock(); |
| |
| // Move constructor and assignment operator. |
| ScopedPartitionLock(ScopedPartitionLock&& other) noexcept; |
| ScopedPartitionLock& operator=(ScopedPartitionLock&& other) noexcept; |
| |
| // Disable the copy constructor. |
| ScopedPartitionLock(const ScopedPartitionLock&) = delete; |
| |
| // Returns true if this points at the same lock state as 'other'. |
| bool HasSameState(const ScopedPartitionLock& other) { |
| return lock_state_ == other.lock_state_; |
| } |
| |
| // Check whether the partition lock is acquired by the transaction. |
| // If false, set the tablet server error code accordingly to abort |
| // or retry the transaction. Otherwise, no error code is set. |
| bool IsAcquired(tserver::TabletServerErrorPB::Code* code) const; |
| |
| // Release a reference of the partition lock held by the transaction. |
| void Release(); |
| |
| private: |
| void TakeState(ScopedPartitionLock* other); |
| |
| LockManager* manager_ = nullptr; |
| PartitionLockState* lock_state_ = nullptr; |
| // The tablet server error code is only set when the lock |
| // is not acquired. Otherwise, the default is 'UNKNOWN_ERROR'. |
| tserver::TabletServerErrorPB::Code code_; |
| }; |
| |
| } // namespace tablet |
| } // namespace kudu |