blob: e4be5f5256c6c3912fb50894f8cf4f4f583d2cd9 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.txn;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.sleepycat.je.dbi.MemoryBudget;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A Lock embodies the lock state of a LSN. It includes a set of owners and
* a list of waiters.
*/
public // for Sizeof
class LockImpl implements Lock {
private static final int REMOVE_LOCKINFO_OVERHEAD =
0 - MemoryBudget.LOCKINFO_OVERHEAD;
/**
* A single locker always appears only once in the logical set of owners.
* The owners set is always in one of the following states.
*
* 1- Empty
* 2- A single writer
* 3- One or more readers
* 4- Multiple writers or a mix of readers and writers, all for
* txns which share locks (all ThreadLocker instances for the same
* thread)
*
* Both ownerSet and waiterList are a collection of LockInfo. Since the
* common case is that there is only one owner or waiter, we have added an
* optimization to avoid the cost of collections. FirstOwner and
* firstWaiter are used for the first owner or waiter of the lock, and the
* corresponding collection is instantiated and used only if more owners
* arrive.
*
* In terms of memory accounting, we count up the cost of each added or
* removed LockInfo, but not the cost of the HashSet/List entry
* overhead. We could do the latter for more precise accounting.
*/
private LockInfo firstOwner;
private Set<LockInfo> ownerSet;
private LockInfo firstWaiter;
private List<LockInfo> waiterList;
/**
* Create a Lock.
*/
public LockImpl() {
}
/* Used when releasing lock. */
LockImpl(LockImpl lock) {
this.firstOwner = lock.firstOwner;
this.ownerSet = lock.ownerSet;
this.firstWaiter = lock.firstWaiter;
this.waiterList = lock.waiterList;
}
/* Used when mutating from a ThinLockImpl to a LockImpl. */
LockImpl(LockInfo firstOwner) {
this.firstOwner = firstOwner;
}
/**
* The first waiter goes into the firstWaiter member variable. Once the
* waiterList is made, all appended waiters go into waiterList, even after
* the firstWaiter goes away and leaves that field null, so as to leave the
* list ordered.
*/
private void addWaiterToEndOfList(LockInfo waiter,
MemoryBudget mb,
int lockTableIndex) {
/* Careful: order important! */
if (waiterList == null) {
if (firstWaiter == null) {
firstWaiter = waiter;
} else {
waiterList = new ArrayList<LockInfo>();
waiterList.add(waiter);
}
} else {
waiterList.add(waiter);
}
mb.updateLockMemoryUsage
(MemoryBudget.LOCKINFO_OVERHEAD, lockTableIndex);
}
/**
* Add this waiter to the front of the list.
*/
private void addWaiterToHeadOfList(LockInfo waiter,
MemoryBudget mb,
int lockTableIndex) {
/* Shuffle the current first waiter down a slot. */
if (firstWaiter != null) {
if (waiterList == null) {
waiterList = new ArrayList<LockInfo>();
}
waiterList.add(0, firstWaiter);
}
firstWaiter = waiter;
mb.updateLockMemoryUsage
(MemoryBudget.LOCKINFO_OVERHEAD, lockTableIndex);
}
/**
* Get a list of waiters for debugging and error messages.
*/
public List<LockInfo> getWaitersListClone() {
List<LockInfo> dumpWaiters = new ArrayList<LockInfo>();
if (firstWaiter != null) {
dumpWaiters.add(firstWaiter);
}
if (waiterList != null) {
dumpWaiters.addAll(waiterList);
}
return dumpWaiters;
}
/**
* Remove this locker from the waiter list.
*/
public void flushWaiter(Locker locker,
MemoryBudget mb,
int lockTableIndex) {
if ((firstWaiter != null) && (firstWaiter.getLocker() == locker)) {
firstWaiter = null;
mb.updateLockMemoryUsage
(REMOVE_LOCKINFO_OVERHEAD, lockTableIndex);
} else if (waiterList != null) {
Iterator<LockInfo> iter = waiterList.iterator();
while (iter.hasNext()) {
LockInfo info = iter.next();
if (info.getLocker() == locker) {
iter.remove();
mb.updateLockMemoryUsage
(REMOVE_LOCKINFO_OVERHEAD, lockTableIndex);
return;
}
}
}
}
private void addOwner(LockInfo newLock,
MemoryBudget mb,
int lockTableIndex) {
if (firstOwner == null) {
firstOwner = newLock;
} else {
if (ownerSet == null) {
ownerSet = new HashSet<LockInfo>();
}
ownerSet.add(newLock);
}
mb.updateLockMemoryUsage
(MemoryBudget.LOCKINFO_OVERHEAD, lockTableIndex);
}
/**
* Get a new Set of the owners.
*/
public Set<LockInfo> getOwnersClone(boolean cloneLockInfo) {
/* No need to update memory usage, the returned Set is transient. */
Set<LockInfo> owners;
if (ownerSet != null) {
if (cloneLockInfo) {
owners = new HashSet<LockInfo>();
for (LockInfo owner : ownerSet) {
owners.add(owner.clone());
}
} else {
owners = new HashSet<LockInfo>(ownerSet);
}
} else {
owners = new HashSet<LockInfo>();
}
if (firstOwner != null) {
owners.add(cloneLockInfo ? firstOwner.clone() : firstOwner);
}
return owners;
}
/**
* Remove this LockInfo from the owner set and clear its memory budget.
*/
private boolean flushOwner(LockInfo oldOwner,
MemoryBudget mb,
int lockTableIndex) {
boolean removed = false;
if (oldOwner != null) {
if (firstOwner == oldOwner) {
firstOwner = null;
removed = true;
} else if (ownerSet != null) {
removed = ownerSet.remove(oldOwner);
}
}
if (removed) {
mb.updateLockMemoryUsage(REMOVE_LOCKINFO_OVERHEAD, lockTableIndex);
}
return removed;
}
/**
* Remove this locker from the owner set.
*/
private LockInfo flushOwner(Locker locker,
MemoryBudget mb,
int lockTableIndex) {
LockInfo flushedInfo = null;
if ((firstOwner != null) &&
(firstOwner.getLocker() == locker)) {
flushedInfo = firstOwner;
firstOwner = null;
} else if (ownerSet != null) {
Iterator<LockInfo> iter = ownerSet.iterator();
while (iter.hasNext()) {
LockInfo o = iter.next();
if (o.getLocker() == locker) {
iter.remove();
flushedInfo = o;
}
}
}
if (flushedInfo != null) {
mb.updateLockMemoryUsage(REMOVE_LOCKINFO_OVERHEAD, lockTableIndex);
}
return flushedInfo;
}
/**
* Returns the owner LockInfo for a locker, or null if locker is not an
* owner.
*/
private LockInfo getOwnerLockInfo(Locker locker) {
if ((firstOwner != null) && (firstOwner.getLocker() == locker)) {
return firstOwner;
}
if (ownerSet != null) {
Iterator<LockInfo> iter = ownerSet.iterator();
while (iter.hasNext()) {
LockInfo o = iter.next();
if (o.getLocker() == locker) {
return o;
}
}
}
return null;
}
/**
* Return true if locker is an owner of this Lock for lockType,
* false otherwise.
*/
public boolean isOwner(Locker locker, LockType lockType) {
LockInfo o = getOwnerLockInfo(locker);
return o != null && o.getLockType() == lockType;
}
/**
* Return true if locker is an owner of this Lock and this is a write
* lock.
*/
public boolean isOwnedWriteLock(Locker locker) {
LockInfo o = getOwnerLockInfo(locker);
return o != null && o.getLockType().isWriteLock();
}
public LockType getOwnedLockType(Locker locker) {
LockInfo o = getOwnerLockInfo(locker);
return (o != null) ? o.getLockType() : null;
}
/**
* Return true if locker is a waiter on this Lock.
*
* This method is only used by unit tests.
*/
public boolean isWaiter(Locker locker) {
if (firstWaiter != null) {
if (firstWaiter.getLocker() == locker) {
return true;
}
}
if (waiterList != null) {
Iterator<LockInfo> iter = waiterList.iterator();
while (iter.hasNext()) {
LockInfo info = iter.next();
if (info.getLocker() == locker) {
return true;
}
}
}
return false;
}
public int nWaiters() {
int count = 0;
if (firstWaiter != null) {
count++;
}
if (waiterList != null) {
count += waiterList.size();
}
return count;
}
public int nOwners() {
int count = 0;
if (firstOwner != null) {
count++;
}
if (ownerSet != null) {
count += ownerSet.size();
}
return count;
}
/**
* Attempts to acquire the lock and returns the LockGrantType.
*
* Assumes we hold the lockTableLatch when entering this method.
*/
public LockAttemptResult lock(LockType requestType,
Locker locker,
boolean nonBlockingRequest,
boolean jumpAheadOfWaiters,
MemoryBudget mb,
int lockTableIndex) {
assert validateRequest(locker); // intentional side effect
/* Request an ordinary lock by checking the owners list. */
LockInfo newLock = new LockInfo(locker, requestType);
LockGrantType grant = tryLock
(newLock, jumpAheadOfWaiters || (nWaiters() == 0), mb,
lockTableIndex);
/* Do we have to wait for this lock? */
if (grant == LockGrantType.WAIT_NEW ||
grant == LockGrantType.WAIT_PROMOTION ||
grant == LockGrantType.WAIT_RESTART) {
/*
* If the request type can cause a restart and a restart conflict
* does not already exist, then we have to check the waiters list
* for restart conflicts. A restart conflict must take precedence
* or it may be missed.
*/
if (requestType.getCausesRestart() &&
grant != LockGrantType.WAIT_RESTART) {
LockInfo waiter = null;
Iterator<LockInfo> iter = null;
if (waiterList != null) {
iter = waiterList.iterator();
}
if (firstWaiter != null) {
waiter = firstWaiter;
} else if ((iter != null) && (iter.hasNext())) {
waiter = iter.next();
}
while (waiter != null) {
/*
* Check for a restart conflict. Ignore LockType.RESTART
* in the waiter list when checking for conflicts.
*/
Locker waiterLocker = waiter.getLocker();
LockType waiterType = waiter.getLockType();
if (waiterType != LockType.RESTART &&
locker != waiterLocker &&
!locker.sharesLocksWith(waiterLocker)) {
LockConflict conflict =
waiterType.getConflict(requestType);
if (conflict.getRestart()) {
grant = LockGrantType.WAIT_RESTART;
break;
}
}
/* Move to the next waiter, if it's in the list. */
if ((iter != null) && (iter.hasNext())) {
waiter = iter.next();
} else {
waiter = null;
}
}
}
/* Add the waiter or deny the lock as appropriate. */
if (nonBlockingRequest) {
grant = LockGrantType.DENIED;
} else {
if (grant == LockGrantType.WAIT_PROMOTION) {
/*
* By moving our waiter to the top of the list we reduce
* the time window where deadlocks can occur due to the
* promotion.
*/
addWaiterToHeadOfList(newLock, mb, lockTableIndex);
} else {
assert grant == LockGrantType.WAIT_NEW ||
grant == LockGrantType.WAIT_RESTART;
/*
* If waiting to restart, change the lock type to RESTART
* to avoid granting the lock later. We wait until the
* RESTART waiter moves to the head of waiter list to
* prevent the requester from spinning performing repeated
* restarts, but we don't grant the lock.
*/
if (grant == LockGrantType.WAIT_RESTART) {
newLock.setLockType(LockType.RESTART);
}
addWaiterToEndOfList(newLock, mb, lockTableIndex);
}
}
}
/* Set 'success' later. */
return new LockAttemptResult(this, grant, false);
}
/**
* Releases a lock and moves the next waiter(s) to the owners.
* @return
* null if we were not the owner,
* a non-empty set if owners should be notified after releasing,
* an empty set if no notification is required.
*/
public Set<Locker> release(Locker locker,
MemoryBudget mb,
int lockTableIndex) {
LockInfo removedLock = flushOwner(locker, mb, lockTableIndex);
if (removedLock == null) {
/* Not owner. */
return null;
}
Set<Locker> lockersToNotify = Collections.emptySet();
if (nWaiters() == 0) {
/* No more waiters, so no one to notify. */
return lockersToNotify;
}
/*
* Move the next set of waiters to the owners set. Iterate through the
* firstWaiter field, then the waiterList.
*/
LockInfo waiter = null;
Iterator<LockInfo> iter = null;
boolean isFirstWaiter = false;
if (waiterList != null) {
iter = waiterList.iterator();
}
if (firstWaiter != null) {
waiter = firstWaiter;
isFirstWaiter = true;
} else if ((iter != null) && (iter.hasNext())) {
waiter = iter.next();
}
while (waiter != null) {
/* Make the waiter an owner if the lock can be acquired. */
LockType waiterType = waiter.getLockType();
Locker waiterLocker = waiter.getLocker();
LockGrantType grant;
if (waiterType == LockType.RESTART) {
/* Special case for restarts: see rangeInsertConflict. */
grant = rangeInsertConflict(waiterLocker) ?
LockGrantType.WAIT_NEW : LockGrantType.NEW;
} else {
/* Try locking. */
grant = tryLock(waiter, true, mb, lockTableIndex);
}
/* Check if granted. */
if (grant == LockGrantType.NEW ||
grant == LockGrantType.EXISTING ||
grant == LockGrantType.PROMOTION) {
/* Remove it from the waiters list. */
if (isFirstWaiter) {
firstWaiter = null;
} else {
iter.remove();
}
if (lockersToNotify == Collections.EMPTY_SET) {
lockersToNotify = new HashSet<Locker>();
}
lockersToNotify.add(waiterLocker);
mb.updateLockMemoryUsage
(REMOVE_LOCKINFO_OVERHEAD, lockTableIndex);
} else {
assert grant == LockGrantType.WAIT_NEW ||
grant == LockGrantType.WAIT_PROMOTION ||
grant == LockGrantType.WAIT_RESTART;
/* Stop on first waiter that cannot be an owner. */
break;
}
/* Move to the next waiter, if it's in the list. */
if ((iter != null) && (iter.hasNext())) {
waiter = iter.next();
isFirstWaiter = false;
} else {
waiter = null;
}
}
return lockersToNotify;
}
public void stealLock(@Nullable Locker locker,
MemoryBudget mb,
int lockTableIndex) {
if (firstOwner != null) {
Locker thisLocker = firstOwner.getLocker();
if (thisLocker != locker &&
thisLocker.getPreemptable()) {
thisLocker.setPreempted();
firstOwner = null;
mb.updateLockMemoryUsage(REMOVE_LOCKINFO_OVERHEAD,
lockTableIndex);
}
}
if (ownerSet != null) {
Iterator<LockInfo> iter = ownerSet.iterator();
while (iter.hasNext()) {
LockInfo lockInfo = iter.next();
Locker thisLocker = lockInfo.getLocker();
if (thisLocker != locker &&
thisLocker.getPreemptable()) {
thisLocker.setPreempted();
iter.remove();
mb.updateLockMemoryUsage(REMOVE_LOCKINFO_OVERHEAD,
lockTableIndex);
}
}
}
}
/**
* Called from lock() to try locking a new request, and from release() to
* try locking a waiting request.
*
* @param newLock is the lock that is requested.
*
* @param firstWaiterInLine determines whether to grant the lock when a
* NEW lock can be granted, but other non-conflicting owners exist; for
* example, when a new READ lock is requested but READ locks are held by
* other owners. This parameter should be true if the requestor is the
* first waiter in line (or if there are no waiters), and false otherwise.
*
* @param mb is the current memory budget.
*
* @return LockGrantType.EXISTING, NEW, PROMOTION, WAIT_RESTART, WAIT_NEW
* or WAIT_PROMOTION.
*/
private LockGrantType tryLock(LockInfo newLock,
boolean firstWaiterInLine,
MemoryBudget mb,
int lockTableIndex) {
/* If no one owns this right now, just grab it. */
if (nOwners() == 0) {
addOwner(newLock, mb, lockTableIndex);
return LockGrantType.NEW;
}
Locker locker = newLock.getLocker();
LockType requestType = newLock.getLockType();
LockUpgrade upgrade = null;
LockInfo lockToUpgrade = null;
boolean ownerExists = false;
boolean ownerConflicts = false;
/*
* Iterate through the current owners. See if there is a current owner
* who has to be upgraded from read to write. Also track whether there
* is a conflict with another owner.
*/
LockInfo owner = null;
Iterator<LockInfo> iter = null;
if (ownerSet != null) {
iter = ownerSet.iterator();
}
if (firstOwner != null) {
owner = firstOwner;
} else if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
}
while (owner != null) {
Locker ownerLocker = owner.getLocker();
LockType ownerType = owner.getLockType();
if (locker == ownerLocker) {
/*
* Requestor currently holds this lock: check for upgrades.
* If no type change is needed, return EXISTING now to avoid
* iterating further; otherwise, we need to check for conflicts
* before granting the upgrade.
*/
assert (upgrade == null); // An owner should appear only once
upgrade = ownerType.getUpgrade(requestType);
if (upgrade.getUpgrade() == null) {
return LockGrantType.EXISTING;
} else {
lockToUpgrade = owner;
}
} else {
/*
* Requestor does not hold this lock: check for conflicts.
* If the owner shares locks with the requestor, ignore it;
* otherwise, if a restart conflict exists, return it now;
* otherwise, save the conflict information.
*/
if (!locker.sharesLocksWith(ownerLocker) &&
!ownerLocker.sharesLocksWith(locker)) {
LockConflict conflict = ownerType.getConflict(requestType);
if (conflict.getRestart()) {
return LockGrantType.WAIT_RESTART;
} else {
if (!conflict.getAllowed()) {
ownerConflicts = true;
}
ownerExists = true;
}
}
}
/* Move on to the next owner. */
if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
} else {
owner = null;
}
}
/* Now handle the upgrade or conflict as appropriate. */
if (upgrade != null) {
/* The requestor holds this lock. */
LockType upgradeType = upgrade.getUpgrade();
assert upgradeType != null;
if (!ownerConflicts) {
/* No conflict: grant the upgrade. */
lockToUpgrade.setLockType(upgradeType);
return upgrade.getPromotion() ?
LockGrantType.PROMOTION : LockGrantType.EXISTING;
} else {
/* Upgrade cannot be granted at this time. */
return LockGrantType.WAIT_PROMOTION;
}
} else {
/* The requestor doesn't hold this lock. */
if (!ownerConflicts && (!ownerExists || firstWaiterInLine)) {
/* No conflict: grant the lock. */
addOwner(newLock, mb, lockTableIndex);
return LockGrantType.NEW;
} else {
/* Lock cannot be granted at this time. */
return LockGrantType.WAIT_NEW;
}
}
}
/**
* Called from release() when a RESTART request is waiting to determine if
* any RANGE_INSERT owners exist. We can't call tryLock for a RESTART
* lock because it must never be granted.
*/
private boolean rangeInsertConflict(Locker waiterLocker) {
LockInfo owner = null;
Iterator<LockInfo> iter = null;
if (ownerSet != null) {
iter = ownerSet.iterator();
}
if (firstOwner != null) {
owner = firstOwner;
} else if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
}
while (owner != null) {
Locker ownerLocker = owner.getLocker();
if (ownerLocker != waiterLocker &&
!ownerLocker.sharesLocksWith(waiterLocker) &&
owner.getLockType() == LockType.RANGE_INSERT) {
return true;
}
/* Move on to the next owner. */
if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
} else {
owner = null;
}
}
return false;
}
/**
* Downgrade a write lock to a read lock.
*/
public void demote(Locker locker) {
LockInfo owner = getOwnerLockInfo(locker);
if (owner != null) {
LockType type = owner.getLockType();
if (type.isWriteLock()) {
owner.setLockType((type == LockType.RANGE_WRITE) ?
LockType.RANGE_READ : LockType.READ);
}
}
}
/**
* Return the locker that has a write ownership on this lock. If no
* write owner exists, return null.
*/
public Locker getWriteOwnerLocker() {
LockInfo owner = null;
Iterator<LockInfo> iter = null;
if (ownerSet != null) {
iter = ownerSet.iterator();
}
if (firstOwner != null) {
owner = firstOwner;
} else if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
}
while (owner != null) {
/* Return locker if it owns a write lock. */
if (owner.getLockType().isWriteLock()) {
return owner.getLocker();
}
/* Move on to the next owner. */
if ((iter != null) && (iter.hasNext())) {
owner = iter.next();
} else {
owner = null;
}
}
return null;
}
/**
* Debugging aid, validation before a lock request.
*/
private boolean validateRequest(Locker locker) {
if (firstWaiter != null) {
if (firstWaiter.getLocker() == locker) {
assert false : "locker " + locker +
" is already on waiters list.";
}
}
if (waiterList != null) {
Iterator<LockInfo> iter = waiterList.iterator();
while (iter.hasNext()) {
LockInfo o = iter.next();
if (o.getLocker() == locker) {
assert false : "locker " + locker +
" is already on waiters list.";
}
}
}
return true;
}
public boolean isThin() {
return false;
}
/**
* Debug dumper.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" LockAddr:").append(System.identityHashCode(this));
sb.append(" Owners:");
if (nOwners() == 0) {
sb.append(" (none)");
} else {
if (firstOwner != null) {
sb.append(firstOwner);
}
if (ownerSet != null) {
Iterator<LockInfo> iter = ownerSet.iterator();
while (iter.hasNext()) {
LockInfo info = iter.next();
sb.append(info);
}
}
}
sb.append(" Waiters:");
if (nWaiters() == 0) {
sb.append(" (none)");
} else {
sb.append(getWaitersListClone());
}
return sb.toString();
}
}