blob: 938d9917cbd76e5b41c81e538cae0ffcd9e5d5a5 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sqlite_vfs/shared_locks.h"
#include <atomic>
#include "base/check.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "third_party/sqlite/sqlite3.h"
namespace sqlite_vfs {
namespace {
// Properties of the primary database lock.
constexpr uint32_t kMaxSharedLocks = 0x08000000;
constexpr uint32_t kSharedMask = 0x0FFFFFFF;
constexpr uint32_t kReservedBit = 0x20000000;
constexpr uint32_t kPendingBit = 0x40000000;
constexpr uint32_t kAbandonedBit = 0x80000000;
// Properties of the WAL-locks.
constexpr uint32_t kWalLockExclusive = 0x80000000;
constexpr uint32_t kWalMaxSharedLocks = 0x7FFFFFFF;
} // namespace
// static
base::UnsafeSharedMemoryRegion SharedLocks::CreateRegion(bool wal_mode) {
size_t size = wal_mode ? sizeof(DatabaseAndWalLocks) : sizeof(DatabaseLock);
return base::UnsafeSharedMemoryRegion::Create(size);
}
// static
std::optional<SharedLocks> SharedLocks::Create(
const base::UnsafeSharedMemoryRegion& region,
bool wal_mode) {
CHECK(region.IsValid());
size_t required_size =
wal_mode ? sizeof(DatabaseAndWalLocks) : sizeof(DatabaseLock);
CHECK_GE(region.GetSize(), required_size);
auto mapping = region.Map();
if (!mapping.IsValid()) {
return std::nullopt;
}
return SharedLocks(std::move(mapping), wal_mode);
}
SharedLocks::~SharedLocks() = default;
// The primary database lock is encoded over 32-bits:
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |A|P|R|0| SHARED COUNT |
// +---+-+-+-----------------------+-------------------------------+
//
// Where
//
// SHARED COUNT: The number of SHARED locks held by readers.
// A: Whether the lock is abandoned. If set no further use is permitted.
// R: The RESERVED lock is held. New shared locks are still permitted.
// P: The PENDING lock is held. No new shared locks are permitted while any
// process holds the PENDING lock.
//
// A process holds the EXCLUSIVE lock when it holds the PENDING lock and the
// SHARED COUNT is zero.
int SharedLocks::Lock(int mode, int& current_mode) {
CHECK(mode == SQLITE_LOCK_SHARED || mode == SQLITE_LOCK_RESERVED ||
mode == SQLITE_LOCK_EXCLUSIVE);
CHECK_LT(current_mode, mode);
auto& database_lock = GetDatabaseLock();
switch (mode) {
case SQLITE_LOCK_SHARED: {
// Try to increment the SHARED lock count as long as the PENDING lock
// remains unheld and there is room remaining to count a new SHARED lock.
uint32_t lock_snapshot = database_lock.load();
if ((lock_snapshot & kAbandonedBit) != 0) {
return SQLITE_IOERR_LOCK;
}
for (int i = 0; i < 5; ++i) {
if ((lock_snapshot & kPendingBit) != 0 ||
(lock_snapshot & kSharedMask) == kMaxSharedLocks) {
break;
}
if (database_lock.compare_exchange_strong(lock_snapshot,
lock_snapshot + 1)) {
// The SHARED lock was successfully acquired.
current_mode = SQLITE_LOCK_SHARED;
return SQLITE_OK;
}
if ((lock_snapshot & kAbandonedBit) != 0) {
return SQLITE_IOERR_LOCK;
}
// Perform up to four retries in case this client is racing against
// other changes to the shared lock.
}
return SQLITE_BUSY;
}
case SQLITE_LOCK_RESERVED: {
// To acquire a RESERVED lock, the current connection must already have
// a shared access to it.
CHECK_EQ(current_mode, SQLITE_LOCK_SHARED);
// Acquire a RESERVED lock to prevent a different writer to declare its
// intention to modify the database. At this point, readers are still
// allowed to get a SHARED lock on the database.
const uint32_t lock_snapshot = database_lock.fetch_or(kReservedBit);
if ((lock_snapshot & kAbandonedBit) != 0) {
return SQLITE_IOERR_LOCK;
}
if ((lock_snapshot & kReservedBit) != 0) {
return SQLITE_BUSY;
}
// The RESERVED lock was successfully acquired.
current_mode = SQLITE_LOCK_RESERVED;
return SQLITE_OK;
}
case SQLITE_LOCK_EXCLUSIVE: {
// Acquiring an EXCLUSIVE lock may happen through multiple calls to
// SandboxedFile::Lock(...) and the PENDING lock may be kept between these
// calls.
// To acquire an EXCLUSIVE lock, the current connection must already have
// at least SHARED lock. Owning RESERVED lock not mandatory.
CHECK_GE(current_mode, SQLITE_LOCK_SHARED);
// Acquire the PENDING lock, if not already acquired. Hold it until the
// EXCLUSIVE lock is obtained. No new SHARED locks will be granted in
// the meantime, but current SHARED locks remain valid.
uint32_t lock_snapshot = 0;
if (current_mode < SQLITE_LOCK_PENDING) {
lock_snapshot = database_lock.fetch_or(kPendingBit);
if ((lock_snapshot & kAbandonedBit) != 0) {
// This instance may have just set `kPendingBit`. There is no need to
// clear it since all other parties will detect that the instance is
// abandoned on their next attempt to acquire any lock.
return SQLITE_IOERR_LOCK;
}
if ((lock_snapshot & kPendingBit) != 0) {
// This connection is not the owner of the PENDING lock.
return SQLITE_BUSY;
}
// The PENDING lock was acquired. Keep it for subsequent calls until all
// SHARED locks are released.
current_mode = SQLITE_LOCK_PENDING;
// Update the copy of the current state of the lock for use below.
lock_snapshot |= kPendingBit;
} else {
// Fetch the current state of the lock for use below.
lock_snapshot = database_lock.load();
if ((lock_snapshot & kAbandonedBit) != 0) {
return SQLITE_IOERR_LOCK;
}
}
// Do not grant the EXCLUSIVE lock until all other readers have released
// their SHARED locks. This connection still owns and keeps a SHARED lock.
if ((lock_snapshot & kSharedMask) != 1) {
return SQLITE_BUSY;
}
// There is no active SHARED lock except for this connection. The PENDING
// lock is owned by this connection so it is valid to grant the EXCLUSIVE
// lock.
current_mode = SQLITE_LOCK_EXCLUSIVE;
return SQLITE_OK;
}
default:
NOTREACHED(); // Not possible as per CHECK at entry.
}
}
int SharedLocks::Unlock(int mode, int& current_mode) {
CHECK(mode == SQLITE_LOCK_NONE || mode == SQLITE_LOCK_SHARED);
CHECK_GT(current_mode, mode);
auto& database_lock = GetDatabaseLock();
// Release the RESERVED or RESERVED and PENDING bits, if held.
if (uint32_t clear_mask =
(current_mode >= SQLITE_LOCK_PENDING
? (kPendingBit | kReservedBit)
: (current_mode == SQLITE_LOCK_RESERVED ? kReservedBit : 0U))) {
database_lock.fetch_and(~clear_mask);
}
// Release the SHARED lock if no longer needed.
if (mode == SQLITE_LOCK_NONE) {
const uint32_t lock_snapshot = database_lock.fetch_sub(1);
CHECK_GE(lock_snapshot & kSharedMask, 1u);
}
// Lock was successfully released.
current_mode = mode;
return SQLITE_OK;
}
bool SharedLocks::IsReserved() {
return (GetDatabaseLock().load() & kReservedBit) != 0;
}
LockState SharedLocks::Abandon() {
uint32_t previous_state = GetDatabaseLock().fetch_or(kAbandonedBit);
if ((previous_state & (kReservedBit | kPendingBit)) != 0) {
return LockState::kWriting;
}
if ((previous_state & kSharedMask) != 0) {
return LockState::kReading;
}
return LockState::kNotHeld;
}
bool SharedLocks::IsAbandoned() const {
return (const_cast<SharedLocks*>(this)->GetDatabaseLock().load(
std::memory_order_relaxed) &
kAbandonedBit) != 0;
}
int SharedLocks::ShmLock(int lock_index,
int num_locks,
LockOperation operation,
LockType type) {
CHECK(wal_mode_);
CHECK_GE(lock_index, 0);
CHECK_GE(num_locks, 0);
CHECK_LE(lock_index + num_locks, kNumWalLocks);
// Releases `count` (out of a maximum of `num_locks`) locks.
auto release_locks = [&](int count) {
for (int j = count - 1; j >= 0; --j) {
auto& wal_lock = GetWalLock(lock_index + j);
if (type == LockType::kExclusive) { // SQLITE_SHM_EXCLUSIVE
const uint32_t previous_value = wal_lock.exchange(0);
CHECK_EQ(previous_value, kWalLockExclusive);
} else { // SQLITE_SHM_SHARED
const uint32_t previous_value = wal_lock.fetch_sub(1);
CHECK_GE(previous_value, 1u);
CHECK_EQ(previous_value & kWalLockExclusive, 0u);
}
}
};
switch (operation) {
case LockOperation::kAcquire: { // SQLITE_SHM_LOCK
// Stop right away if this database has been abandoned.
if ((GetDatabaseLock().load(std::memory_order_relaxed) & kAbandonedBit) !=
0) {
return SQLITE_IOERR_LOCK;
}
int locks_acquired = 0;
int result = SQLITE_OK;
for (int i = 0; i < num_locks; ++i) {
auto& wal_lock = GetWalLock(lock_index + i);
if (type == LockType::kExclusive) { // SQLITE_SHM_EXCLUSIVE
uint32_t expected = 0;
if (!wal_lock.compare_exchange_strong(expected, kWalLockExclusive)) {
result = SQLITE_BUSY;
break;
}
} else { // SQLITE_SHM_SHARED
uint32_t current = wal_lock.load();
while (true) {
if (current & kWalLockExclusive) {
// Another connection holds an exclusive lock.
result = SQLITE_BUSY;
break;
}
if (current == kWalMaxSharedLocks) {
// No space to add a new shared lock until another connection
// releases theirs.
result = SQLITE_BUSY;
break;
}
// compare_exchange_weak is safe because spurious failures are
// handled by the retry loop.
if (wal_lock.compare_exchange_weak(current, current + 1)) {
break;
}
}
if (result != SQLITE_OK) {
break;
}
}
locks_acquired = i + 1;
}
// Check once more to be sure that the database wasn't abandoned while
// acquiring the desired lock(s).
if (result == SQLITE_OK &&
(GetDatabaseLock().load(std::memory_order_relaxed) & kAbandonedBit) !=
0) {
result = SQLITE_IOERR_LOCK;
}
if (result != SQLITE_OK) {
release_locks(locks_acquired);
return result;
}
return SQLITE_OK;
}
case LockOperation::kRelease:
release_locks(num_locks);
return SQLITE_OK;
}
}
void SharedLocks::ShmBarrier() {
std::atomic_thread_fence(std::memory_order_seq_cst);
}
SharedLocks::SharedLocks(base::WritableSharedMemoryMapping mapping,
bool wal_mode)
: mapping_(std::move(mapping)), wal_mode_(wal_mode) {
static_assert(kNumWalLocks == SQLITE_SHM_NLOCK);
size_t required_size =
wal_mode ? sizeof(DatabaseAndWalLocks) : sizeof(DatabaseLock);
CHECK_GE(mapping_.size(), required_size);
}
SharedLocks::DatabaseLock& SharedLocks::GetDatabaseLock() {
if (wal_mode_) {
return mapping_.GetMemoryAs<DatabaseAndWalLocks>()->primary_lock;
}
return *mapping_.GetMemoryAs<DatabaseLock>();
}
SharedLocks::WalLock& SharedLocks::GetWalLock(int index) {
CHECK(wal_mode_);
CHECK_GE(index, 0);
CHECK_LT(index, kNumWalLocks);
return mapping_.GetMemoryAs<DatabaseAndWalLocks>()->wal_locks[index];
}
} // namespace sqlite_vfs