org.eclipse.persistence.internal.helper.ConcurrencyManager Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.helper;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.persistence.config.SystemProperties;
import org.eclipse.persistence.exceptions.ConcurrencyException;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.localization.ToStringLocalization;
import org.eclipse.persistence.internal.localization.TraceLocalization;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
/**
* INTERNAL:
*
* Purpose: To maintain concurrency for a particular task.
* It is a wrappers of a semaphore that allows recursive waits by a single thread.
*
* Responsibilities:
*
* - Keep track of the active thread.
*
- Wait all other threads until the first thread is done.
*
- Maintain the depth of the active thread.
*
*/
public class ConcurrencyManager implements Serializable {
public static final Map DEFERRED_LOCK_MANAGERS = initializeDeferredLockManagers();
// Used for logging in case of dead-lock detection. Unique instance id.
private static final AtomicLong CONCURRENCY_MANAGER_ID = new AtomicLong(0);
protected static boolean shouldTrackStack = PrivilegedAccessHelper.getSystemProperty(SystemProperties.RECORD_STACK_ON_LOCK) != null;
protected AtomicInteger numberOfReaders;
protected AtomicInteger depth;
protected AtomicInteger numberOfWritersWaiting;
protected volatile transient Thread activeThread;
protected boolean lockedByMergeManager;
protected Exception stack;
// Extended logging info fields
// Unique ID assigned each time when a new instance of a concurrency manager is created
private final long concurrencyManagerId = CONCURRENCY_MANAGER_ID.incrementAndGet();
// Creation date
private final Date concurrencyManagerCreationDate = new Date();
// In case if two threads are working on the exact same entity that leads to both threads wanting to release the same cache key
// there is tracking each increment of number of readers and their release.
private final AtomicLong totalNumberOfKeysAcquiredForReading = new AtomicLong(0);
// Same as totalNumberOfKeysAcquiredForReading but incremented each time the cache key is suffering to release cache key.
private final AtomicLong totalNumberOfKeysReleasedForReading = new AtomicLong(0);
// Total number of times the cache key caused a blow up because it suffered a release of cache key when the counter
// was set to 0. It should happen if an entity being shared by two threads.
private final AtomicLong totalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHavingReachedCounterZero = new AtomicLong(0);
private static final Map THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK = new ConcurrentHashMap<>();
private static final Map THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK_NAME_OF_METHOD_CREATING_TRACE = new ConcurrentHashMap<>();
private static final Map THREADS_TO_WAIT_ON_ACQUIRE = new ConcurrentHashMap<>();
private static final Map THREADS_TO_WAIT_ON_ACQUIRE_NAME_OF_METHOD_CREATING_TRACE = new ConcurrentHashMap<>();
// Holds as a keys threads that needed to acquire one or more read locks on different cache keys.
private static final Map READ_LOCK_MANAGERS = new ConcurrentHashMap<>();
private static final Set THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS = ConcurrentHashMap.newKeySet();
private static final Map THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE = new ConcurrentHashMap<>();
private static final String ACQUIRE_METHOD_NAME = ConcurrencyManager.class.getName() + ".acquire(...)";
private static final String ACQUIRE_READ_LOCK_METHOD_NAME = ConcurrencyManager.class.getName() + ".acquireReadLock(...)";
private static final String ACQUIRE_WITH_WAIT_METHOD_NAME = ConcurrencyManager.class.getName() + ".acquireWithWait(...)";
private static final String ACQUIRE_DEFERRED_LOCK_METHOD_NAME = ConcurrencyManager.class.getName() + ".acquireDeferredLock(...)";
/**
* Initialize the newly allocated instance of this class.
* Set the depth to zero.
*/
public ConcurrencyManager() {
this.depth = new AtomicInteger(0);
this.numberOfReaders = new AtomicInteger(0);
this.numberOfWritersWaiting = new AtomicInteger(0);
}
/**
* Wait for all threads except the active thread.
* If the active thread just increment the depth.
* This should be called before entering a critical section.
*/
public void acquire() throws ConcurrencyException {
this.acquire(false);
}
/**
* Wait for all threads except the active thread.
* If the active thread just increment the depth.
* This should be called before entering a critical section.
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public synchronized void acquire(boolean forMerge) throws ConcurrencyException {
//Flag the time when we start the while loop
final long whileStartTimeMillis = System.currentTimeMillis();
Thread currentThread = Thread.currentThread();
DeferredLockManager lockManager = getDeferredLockManager(currentThread);
ReadLockManager readLockManager = getReadLockManager(currentThread);
// Waiting to acquire cache key will now start on the while loop
// NOTE: this step bares no influence in acquiring or not acquiring locks
// is just storing debug metadata that we can use when we detect the system is frozen in a dead lock
final boolean currentThreadWillEnterTheWhileWait = ((this.activeThread != null) || (this.numberOfReaders.get() > 0)) && (this.activeThread != currentThread);
if(currentThreadWillEnterTheWhileWait) {
putThreadAsWaitingToAcquireLockForWriting(currentThread, ACQUIRE_METHOD_NAME);
}
while (((this.activeThread != null) || (this.numberOfReaders.get() > 0)) && (this.activeThread != Thread.currentThread())) {
// This must be in a while as multiple threads may be released, or another thread may rush the acquire after one is released.
try {
this.numberOfWritersWaiting.incrementAndGet();
wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());
// Run a method that will fire up an exception if we having been sleeping for too long
ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(this, whileStartTimeMillis, lockManager, readLockManager, ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired());
} catch (InterruptedException exception) {
// If the thread is interrupted we want to make sure we release all of the locks the thread was owning
releaseAllLocksAcquiredByThread(lockManager);
// Improve concurrency manager metadata
// Waiting to acquire cache key is is over
if (currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForWriting(currentThread);
}
throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
} finally {
// Since above we increments the number of writers
// whether or not the thread is exploded by an interrupt
// we need to make sure we decrement the number of writer to not allow the code to be corrupted
this.numberOfWritersWaiting.decrementAndGet();
}
} // end of while loop
// Waiting to acquire cahe key is is over
if(currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForWriting(currentThread);
}
if (this.activeThread == null) {
this.activeThread = Thread.currentThread();
if (shouldTrackStack){
this.stack = new Exception();
}
}
this.lockedByMergeManager = forMerge;
this.depth.incrementAndGet();
}
/**
* If the lock is not acquired already acquire it and return true.
* If it has been acquired already return false
* Added for CR 2317
*/
public boolean acquireNoWait() throws ConcurrencyException {
return acquireNoWait(false);
}
/**
* If the lock is not acquired already acquire it and return true.
* If it has been acquired already return false
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public synchronized boolean acquireNoWait(boolean forMerge) throws ConcurrencyException {
if ((this.activeThread == null && this.numberOfReaders.get() == 0) || (this.activeThread == Thread.currentThread())) {
//if I own the lock increment depth
acquire(forMerge);
return true;
} else {
return false;
}
}
/**
* If the lock is not acquired already acquire it and return true.
* If it has been acquired already return false
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public synchronized boolean acquireWithWait(boolean forMerge, int wait) throws ConcurrencyException {
final Thread currentThread = Thread.currentThread();
if ((this.activeThread == null && this.numberOfReaders.get() == 0) || (this.activeThread == currentThread)) {
// if I own the lock increment depth
acquire(forMerge);
return true;
} else {
try {
putThreadAsWaitingToAcquireLockForWriting(currentThread, ACQUIRE_WITH_WAIT_METHOD_NAME);
wait(wait);
} catch (InterruptedException e) {
return false;
} finally {
removeThreadNoLongerWaitingToAcquireLockForWriting(currentThread);
}
if ((this.activeThread == null && this.numberOfReaders.get() == 0)
|| (this.activeThread == currentThread)) {
acquire(forMerge);
return true;
}
return false;
}
}
/**
* If the activeThread is not set, acquire it and return true.
* If the activeThread is set, it has been acquired already, return false.
* Added for Bug 5840635
* Call with true from the merge process, if true then the refresh will not refresh the object.
*/
public synchronized boolean acquireIfUnownedNoWait(boolean forMerge) throws ConcurrencyException {
// Only acquire lock if active thread is null. Do not check current thread.
if (this.activeThread == null && this.numberOfReaders.get() == 0) {
// if lock is unowned increment depth
acquire(forMerge);
return true;
} else {
return false;
}
}
/**
* Add deferred lock into a hashtable to avoid deadlock
*/
public void acquireDeferredLock() throws ConcurrencyException {
Thread currentThread = Thread.currentThread();
DeferredLockManager lockManager = getDeferredLockManager(currentThread);
ReadLockManager readLockManager = getReadLockManager(currentThread);
if (lockManager == null) {
lockManager = new DeferredLockManager();
putDeferredLock(currentThread, lockManager);
}
lockManager.incrementDepth();
synchronized (this) {
final long whileStartTimeMillis = System.currentTimeMillis();
final boolean currentThreadWillEnterTheWhileWait = this.numberOfReaders.get() != 0;
if(currentThreadWillEnterTheWhileWait) {
putThreadAsWaitingToAcquireLockForWriting(currentThread, ACQUIRE_DEFERRED_LOCK_METHOD_NAME);
}
while (this.numberOfReaders.get() != 0) {
// There are readers of this object, wait until they are done before determining if
//there are any other writers. If not we will wait on the readers for acquire. If another
//thread is also waiting on the acquire then a deadlock could occur. See bug 3049635
//We could release all active locks before releasing deferred but the object may not be finished building
//we could make the readers get a hard lock, but then we would just build a deferred lock even though
//the object is not being built.
try {
this.numberOfWritersWaiting.incrementAndGet();
wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());
ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(this, whileStartTimeMillis, lockManager, readLockManager, ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired());
} catch (InterruptedException exception) {
// If the thread is interrupted we want to make sure we release all of the locks the thread was owning
releaseAllLocksAcquiredByThread(lockManager);
if (currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForWriting(currentThread);
}
throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
} finally {
this.numberOfWritersWaiting.decrementAndGet();
}
}
if (currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForWriting(currentThread);
}
if ((this.activeThread == currentThread) || (!isAcquired())) {
lockManager.addActiveLock(this);
acquire();
} else {
lockManager.addDeferredLock(this);
if (AbstractSessionLog.getLog().shouldLog(SessionLog.FINER) && this instanceof CacheKey) {
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.CACHE, "acquiring_deferred_lock", ((CacheKey)this).getObject(), currentThread.getName());
}
}
}
}
/**
* Check the lock state, if locked, acquire and release a deferred lock.
* This optimizes out the normal deferred-lock check if not locked.
*/
public void checkDeferredLock() throws ConcurrencyException {
// If it is not locked, then just return.
if (this.activeThread == null) {
return;
}
acquireDeferredLock();
releaseDeferredLock();
}
/**
* Check the lock state, if locked, acquire and release a read lock.
* This optimizes out the normal read-lock check if not locked.
*/
public void checkReadLock() throws ConcurrencyException {
// If it is not locked, then just return.
if (this.activeThread == null) {
return;
}
acquireReadLock();
releaseReadLock();
}
/**
* Wait on any writer.
* Allow concurrent reads.
*/
public synchronized void acquireReadLock() throws ConcurrencyException {
final Thread currentThread = Thread.currentThread();
final long whileStartTimeMillis = System.currentTimeMillis();
DeferredLockManager lockManager = getDeferredLockManager(currentThread);
ReadLockManager readLockManager = getReadLockManager(currentThread);
final boolean currentThreadWillEnterTheWhileWait = (this.activeThread != null) && (this.activeThread != currentThread);
if (currentThreadWillEnterTheWhileWait) {
putThreadAsWaitingToAcquireLockForReading(currentThread, ACQUIRE_READ_LOCK_METHOD_NAME);
}
// Cannot check for starving writers as will lead to deadlocks.
while ((this.activeThread != null) && (this.activeThread != Thread.currentThread())) {
try {
wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());
ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(this, whileStartTimeMillis, lockManager, readLockManager, ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired());
} catch (InterruptedException exception) {
releaseAllLocksAcquiredByThread(lockManager);
if (currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForReading(currentThread);
}
throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
}
}
if (currentThreadWillEnterTheWhileWait) {
removeThreadNoLongerWaitingToAcquireLockForReading(currentThread);
}
try {
addReadLockToReadLockManager();
} finally {
this.numberOfReaders.incrementAndGet();
this.totalNumberOfKeysAcquiredForReading.incrementAndGet();
}
}
/**
* If this is acquired return false otherwise acquire readlock and return true
*/
public synchronized boolean acquireReadLockNoWait() {
if ((this.activeThread == null) || (this.activeThread == Thread.currentThread())) {
acquireReadLock();
return true;
} else {
return false;
}
}
/**
* Return the active thread.
*/
public Thread getActiveThread() {
return activeThread;
}
/**
* Return the deferred lock manager from the thread
*/
public static DeferredLockManager getDeferredLockManager(Thread thread) {
return getDeferredLockManagers().get(thread);
}
/**
* Return the deferred lock manager hashtable (thread - DeferredLockManager).
*/
protected static Map getDeferredLockManagers() {
return DEFERRED_LOCK_MANAGERS;
}
/**
* Init the deferred lock managers (thread - DeferredLockManager).
*/
protected static Map initializeDeferredLockManagers() {
return new ConcurrentHashMap<>();
}
/**
* Return the current depth of the active thread.
*/
public int getDepth() {
return depth.get();
}
/**
* Number of writer that want the lock.
* This is used to ensure that a writer is not starved.
*/
public int getNumberOfReaders() {
return numberOfReaders.get();
}
/**
* Number of writers that want the lock.
* This is used to ensure that a writer is not starved.
*/
public int getNumberOfWritersWaiting() {
return numberOfWritersWaiting.get();
}
/**
* Return if a thread has acquire this manager.
*/
public boolean isAcquired() {
return depth.get() > 0;
}
/**
* INTERNAL:
* Used byt the refresh process to determine if this concurrency manager is locked by
* the merge process. If it is then the refresh should not refresh the object
*/
public boolean isLockedByMergeManager() {
return this.lockedByMergeManager;
}
/**
* Check if the deferred locks of a thread are all released.
* Should write dead lock diagnostic information into the {@link #THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE}.
*
* @param thread
* the current thread to be explored. It starts by being the thread that it is stuck but then it evolves
* to be other that have acquired locks our main thread was needing but whcich themslves are stuck...
* threads in the deffered lock chain that are going nowhere themselves.
* @param recursiveSet
* this prevents the algorithm going into an infinite loop of expanding the same thread more than once.
* @param parentChainOfThreads
* this starts by being a basket containing the current thread, but each time we go deeper it evolves to
* contain the thread we will explore next.
* @return true if object is complete
*/
public static boolean isBuildObjectOnThreadComplete(Thread thread, Map recursiveSet, List parentChainOfThreads, boolean deadLockDiagnostic) {
if (recursiveSet.containsKey(thread)) {
return true;
}
recursiveSet.put(thread, thread);
DeferredLockManager lockManager = getDeferredLockManager(thread);
if (lockManager == null) {
return true;
}
Vector deferredLocks = lockManager.getDeferredLocks();
for (Enumeration deferredLocksEnum = deferredLocks.elements();
deferredLocksEnum.hasMoreElements();) {
ConcurrencyManager deferedLock = deferredLocksEnum.nextElement();
Thread activeThread = null;
if (deferedLock.isAcquired()) {
activeThread = deferedLock.getActiveThread();
// the active thread may be set to null at anypoint
// if added for CR 2330
if (activeThread != null) {
DeferredLockManager currentLockManager = getDeferredLockManager(activeThread);
if (currentLockManager == null) {
// deadlock diagnostic extension
if (deadLockDiagnostic && parentChainOfThreads != null) {
StringBuilder justificationForReturningFalse = new StringBuilder();
enrichStringBuildingExplainWhyThreadIsStuckInIsBuildObjectOnThreadComplete(parentChainOfThreads, deferedLock, activeThread, false, justificationForReturningFalse);
setJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse(justificationForReturningFalse.toString());
}
return false;
} else if (currentLockManager.isThreadComplete()) {
activeThread = deferedLock.getActiveThread();
// The lock may suddenly finish and no longer have an active thread.
if (activeThread != null) {
// deadlock diagnostic extension
List currentChainOfThreads = null;
if (deadLockDiagnostic) {
currentChainOfThreads = (parentChainOfThreads == null) ? new ArrayList<>() : new ArrayList<>(parentChainOfThreads);
currentChainOfThreads.add(activeThread);
}
if (!isBuildObjectOnThreadComplete(activeThread, recursiveSet, currentChainOfThreads, deadLockDiagnostic)) {
return false;
}
}
} else {
if (deadLockDiagnostic && parentChainOfThreads != null) {
StringBuilder justificationForReturningFalse = new StringBuilder();
enrichStringBuildingExplainWhyThreadIsStuckInIsBuildObjectOnThreadComplete(parentChainOfThreads, deferedLock, activeThread, true, justificationForReturningFalse);
setJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse(justificationForReturningFalse.toString());
}
return false;
}
}
}
}
if (parentChainOfThreads != null && parentChainOfThreads.size() == 1) {
clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse();
}
return true;
}
/**
* When the recursive algorithm decides to return false it is because it is confronted with a cache key that had to
* be deferred. And the cache key is either being owned by a thread that did not flage itsef as being finished and
* waiting in the wait for deferred locks. Or the thread that ows the cache key is not playing nice - and not using
* deferred locks - so it has acquire the cache key, it is going about its business (e.g. committing a transaction
* or perhaps doing object building. Normally, but not always, in object building threads do have a lock manager,
* but sometimes not when they agressive acquire lock policy. )
*
* @param chainOfThreadsExpandedInRecursion
* This the chaing threads that were expanded as we went down with the recursion
* @param finalDeferredLockCausingTrouble
* this is a lock that was deferred either by current thread or by a thread that is also itself waiting
* around . This lock is what is causing us ultimately to return FALSE, because the lock is still ACUIRED
* so not yet free. And the thread that owns it is also still not finished yet.
*
* @param activeThreadOnDeferredLock
* this is the thread that was spotted as owning/being actively owning the the deferred lock. So we can
* consider this thread as being the ultimate cause of why the current thread and perhaps a hole chaing
* of related threads are not evolving. But certainly the current thread.
* @param hasDeferredLockManager
* Some threads have deferred lock managers some not. Not clear when they do. But threads doing object
* building typically end up creating a deferred lock manager when they find themselves unable to acquire
* an object and need to defer on the cache key.
* @param justification
* this is what we want to populate it will allow us to build a trace to explain why the thread on the
* wait for deferred lock is going nowhere. This trace will be quite important to help us interpret the
* massive dumps since it is quite typical to find threads in this state.
*
*/
public static void enrichStringBuildingExplainWhyThreadIsStuckInIsBuildObjectOnThreadComplete(
List chainOfThreadsExpandedInRecursion,
ConcurrencyManager finalDeferredLockCausingTrouble,
Thread activeThreadOnDeferredLock,
boolean hasDeferredLockManager,
StringBuilder justification) {
// (a) summarize the threads navigated via deferred locks
int currentThreadNumber = 0;
for (Thread currentExpandedThread : chainOfThreadsExpandedInRecursion) {
currentThreadNumber++;
justification.append(TraceLocalization.buildMessage("concurrency_manager_build_object_thread_complete_1", new Object[] {currentThreadNumber, currentExpandedThread.getName()}));
}
justification.append(TraceLocalization.buildMessage("concurrency_manager_build_object_thread_complete_2"));
// (b) Described the cache key blocking us from finishing the oject building
String cacheKeyStr = ConcurrencyUtil.SINGLETON.createToStringExplainingOwnedCacheKey(finalDeferredLockCausingTrouble);
justification.append(TraceLocalization.buildMessage("concurrency_manager_build_object_thread_complete_3", new Object[] {cacheKeyStr}));
// (c) Describe the thread that has acquired the cache key and is not done yet
justification.append(TraceLocalization.buildMessage("concurrency_manager_build_object_thread_complete_4", new Object[] {activeThreadOnDeferredLock, hasDeferredLockManager}));
}
/**
* Return if this manager is within a nested acquire.
*/
public boolean isNested() {
return depth.get() > 1;
}
public void putDeferredLock(Thread thread, DeferredLockManager lockManager) {
getDeferredLockManagers().put(thread, lockManager);
}
/**
* Decrement the depth for the active thread.
* Assume the current thread is the active one.
* Raise an error if the depth become < 0.
* The notify will release the first thread waiting on the object,
* if no threads are waiting it will do nothing.
*/
public synchronized void release() throws ConcurrencyException {
if (this.depth.get() == 0) {
throw ConcurrencyException.signalAttemptedBeforeWait();
} else {
this.depth.decrementAndGet();
}
if (this.depth.get() == 0) {
this.activeThread = null;
if (shouldTrackStack){
this.stack = null;
}
this.lockedByMergeManager = false;
notifyAll();
}
}
/**
* Release the deferred lock.
* This uses a deadlock detection and resolution algorithm to avoid cache deadlocks.
* The deferred lock manager keeps track of the lock for a thread, so that other
* thread know when a deadlock has occurred and can resolve it.
*/
public void releaseDeferredLock() throws ConcurrencyException {
Thread currentThread = Thread.currentThread();
DeferredLockManager lockManager = getDeferredLockManager(currentThread);
ReadLockManager readLockManager = getReadLockManager(currentThread);
if (lockManager == null) {
return;
}
int depth = lockManager.getThreadDepth();
if (depth > 1) {
lockManager.decrementDepth();
return;
}
// If the set is null or empty, means there is no deferred lock for this thread, return.
if (!lockManager.hasDeferredLock()) {
lockManager.releaseActiveLocksOnThread();
removeDeferredLockManager(currentThread);
return;
}
lockManager.setIsThreadComplete(true);
final long whileStartTimeMillis = System.currentTimeMillis();
boolean releaseAllLocksAquiredByThreadAlreadyPerformed = false;
boolean currentThreadRegisteredAsWaitingForisBuildObjectOnThreadComplete = false;
clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse();
// Thread have three stages, one where they are doing work (i.e. building objects)
// two where they are done their own work but may be waiting on other threads to finish their work,
// and a third when they and all the threads they are waiting on are done.
// This is essentially a busy wait to determine if all the other threads are done.
while (true) {
boolean isBuildObjectCompleteSlow = ConcurrencyUtil.SINGLETON.tooMuchTimeHasElapsed(whileStartTimeMillis, ConcurrencyUtil.SINGLETON.getBuildObjectCompleteWaitTime());
try{
// 2612538 - the default size of Map (32) is appropriate
Map recursiveSet = new IdentityHashMap<>();
if (isBuildObjectOnThreadComplete(currentThread, recursiveSet, Arrays.asList(currentThread), isBuildObjectCompleteSlow)) {// Thread job done.
// Remove from debug metadata the fact that the current thread needed to wait
// for one or more build objects to be completed by other threads.
if(currentThreadRegisteredAsWaitingForisBuildObjectOnThreadComplete) {
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS.remove(currentThread);
}
clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse();
lockManager.releaseActiveLocksOnThread();
removeDeferredLockManager(currentThread);
AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.CACHE, "deferred_locks_released", currentThread.getName());
return;
} else {// Not done yet, wait and check again.
try {
// Add debug metadata to concurrency manager state
// The current thread will now be waiting for other threads to build the object(s) it could not acquire
if(!currentThreadRegisteredAsWaitingForisBuildObjectOnThreadComplete) {
currentThreadRegisteredAsWaitingForisBuildObjectOnThreadComplete = true;
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS.add(currentThread);
}
Thread.sleep(20);
ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(this, whileStartTimeMillis, lockManager, readLockManager, ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired());
} catch (InterruptedException interrupted) {
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS.remove(currentThread);
AbstractSessionLog.getLog().logThrowable(SessionLog.SEVERE, SessionLog.CACHE, interrupted);
releaseAllLocksAcquiredByThread(lockManager);
releaseAllLocksAquiredByThreadAlreadyPerformed = true;
clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse();
throw ConcurrencyException.waitWasInterrupted(interrupted.getMessage());
}
}
} catch (Error error) {
if (!releaseAllLocksAquiredByThreadAlreadyPerformed) {
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS.remove(currentThread);
AbstractSessionLog.getLog().logThrowable(SessionLog.SEVERE, SessionLog.CACHE, error);
releaseAllLocksAcquiredByThread(lockManager);
clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse();
}
throw error;
}
}
}
/**
* Decrement the number of readers. Used to allow concurrent reads.
*/
public synchronized void releaseReadLock() throws ConcurrencyException {
if (this.numberOfReaders.get() == 0) {
this.totalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHavingReachedCounterZero.incrementAndGet();
try {
removeReadLockFromReadLockManager();
} catch (Exception e) {
AbstractSessionLog.getLog().logThrowable(SessionLog.SEVERE, SessionLog.CACHE, e);
}
throw ConcurrencyException.signalAttemptedBeforeWait();
} else {
try {
removeReadLockFromReadLockManager();
} finally {
this.numberOfReaders.decrementAndGet();
this.totalNumberOfKeysReleasedForReading.incrementAndGet();
}
}
if (this.numberOfReaders.get() == 0) {
notifyAll();
}
}
/**
* Remove the deferred lock manager for the thread
*/
public static DeferredLockManager removeDeferredLockManager(Thread thread) {
return getDeferredLockManagers().remove(thread);
}
/**
* Set the active thread.
*/
public void setActiveThread(Thread activeThread) {
this.activeThread = activeThread;
}
/**
* Set the current depth of the active thread.
*/
protected void setDepth(int depth) {
this.depth.set(depth);
}
/**
* INTERNAL:
* Used by the mergemanager to let the read know not to refresh this object as it is being
* loaded by the merge process.
*/
public void setIsLockedByMergeManager(boolean state) {
this.lockedByMergeManager = state;
}
/**
* Track the number of readers.
*/
protected void setNumberOfReaders(int numberOfReaders) {
this.numberOfReaders.set(numberOfReaders);
}
/**
* Number of writers that want the lock.
* This is used to ensure that a writer is not starved.
*/
protected void setNumberOfWritersWaiting(int numberOfWritersWaiting) {
this.numberOfWritersWaiting.set(numberOfWritersWaiting);
}
public synchronized void transitionToDeferredLock() {
Thread currentThread = Thread.currentThread();
DeferredLockManager lockManager = getDeferredLockManager(currentThread);
if (lockManager == null) {
lockManager = new DeferredLockManager();
putDeferredLock(currentThread, lockManager);
}
lockManager.incrementDepth();
lockManager.addActiveLock(this);
}
/**
* For the thread to release all of its locks.
*
* @param lockManager
* the deferred lock manager
*/
public void releaseAllLocksAcquiredByThread(DeferredLockManager lockManager) {
Thread currentThread = Thread.currentThread();
//When this method is invoked during an acquire lock sometimes there is no lock manager
if (lockManager == null) {
String cacheKeyToString = ConcurrencyUtil.SINGLETON.createToStringExplainingOwnedCacheKey(this);
StringWriter writer = new StringWriter();
writer.write(TraceLocalization.buildMessage("concurrency_manager_release_locks_acquired_by_thread_1", new Object[] {currentThread.getName(), cacheKeyToString}));
AbstractSessionLog.getLog().log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), new Object[] {}, false);
return;
}
StringWriter writer = new StringWriter();
writer.write(TraceLocalization.buildMessage("concurrency_manager_release_locks_acquired_by_thread_2", new Object[] {currentThread.toString()}));
AbstractSessionLog.getLog().log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), new Object[] {}, false);
lockManager.releaseActiveLocksOnThread();
removeDeferredLockManager(currentThread);
}
/**
* The method is not synchronized because for now we assume that each thread will ask for its own lock manager. If
* we were writing a dead lock detection mechanism then a ThreadA could be trying understand the ReadLocks of a
* ThreadB and this would no longer be true.
*
* @param thread
* The thread for which we want to have look at the acquired read locks.
* @return Never null if the read lock manager does not yet exist for the current thread. otherwise its read log
* manager is returned.
*/
protected static ReadLockManager getReadLockManager(Thread thread) {
Map readLockManagers = getReadLockManagers();
return readLockManagers.get(thread);
}
/**
* Return the deferred lock manager hashtable (thread - DeferredLockManager).
*/
protected static Map getReadLockManagers() {
return READ_LOCK_MANAGERS;
}
/**
* Print the nested depth.
*/
@Override
public String toString() {
Object[] args = {getDepth()};
return Helper.getShortClassName(getClass()) + ToStringLocalization.buildMessage("nest_level", args);
}
public Exception getStack() {
return stack;
}
public void setStack(Exception stack) {
this.stack = stack;
}
public static boolean shouldTrackStack() {
return shouldTrackStack;
}
/**
* INTERNAL:
* This can be set during debugging to record the stacktrace when a lock is acquired.
* Then once IdentityMapAccessor.printIdentityMapLocks() is called the stack call for each
* lock will be printed as well. Because locking issues are usually quite time sensitive setting
* this flag may inadvertently remove the deadlock because of the change in timings.
*
* There is also a system level property for this setting. "eclipselink.cache.record-stack-on-lock"
*/
public static void setShouldTrackStack(boolean shouldTrackStack) {
ConcurrencyManager.shouldTrackStack = shouldTrackStack;
}
private static String getPropertyRecordStackOnLock() {
return PrivilegedAccessHelper.callDoPrivileged(() -> System.getProperty(SystemProperties.RECORD_STACK_ON_LOCK));
}
/**
* Normally this mehtod should only be called from withing the concurrency manager.
* However the write lock manager while it is building clones also does some while loop waiting
* to try to acquire a cache key this acquiring logic is not being managed directly inside of the wait manager.
*
*/
public void putThreadAsWaitingToAcquireLockForWriting(Thread thread, String methodName){
THREADS_TO_WAIT_ON_ACQUIRE.put(thread, this);
THREADS_TO_WAIT_ON_ACQUIRE_NAME_OF_METHOD_CREATING_TRACE.put(thread, methodName);
}
/**
* The thread has acquired the lock for writing or decided to defer acquiring the lock putting this lock into its
* deferred lock list.
*/
public void removeThreadNoLongerWaitingToAcquireLockForWriting(Thread thread) {
THREADS_TO_WAIT_ON_ACQUIRE.remove(thread);
THREADS_TO_WAIT_ON_ACQUIRE_NAME_OF_METHOD_CREATING_TRACE.remove(thread);
}
/**
* The thread is trying to acquire a read lock but it is not being able to make process on getting the read lock.
*
* @param methodName
* metadata to help us debug trace leaking. If we start blowing up threads we do not want the traces
* created by the current thread to remain.
*/
public void putThreadAsWaitingToAcquireLockForReading(Thread currentThread, String methodName) {
THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK.put(currentThread, this);
THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK_NAME_OF_METHOD_CREATING_TRACE.put(currentThread, methodName);
}
public void removeThreadNoLongerWaitingToAcquireLockForReading(Thread thread) {
THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK.remove(thread);
THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK_NAME_OF_METHOD_CREATING_TRACE.remove(thread);
}
/** Getter for {@link #concurrencyManagerId} */
public long getConcurrencyManagerId() {
return concurrencyManagerId;
}
/** Getter for {@link #concurrencyManagerCreationDate} */
public Date getConcurrencyManagerCreationDate() {
return concurrencyManagerCreationDate;
}
/** Getter for {@link #totalNumberOfKeysAcquiredForReading} */
public long getTotalNumberOfKeysAcquiredForReading() {
return totalNumberOfKeysAcquiredForReading.get();
}
/** Getter for {@link #totalNumberOfKeysReleasedForReading} */
public long getTotalNumberOfKeysReleasedForReading() {
return totalNumberOfKeysReleasedForReading.get();
}
/** Getter for {@link #totalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHavingReachedCounterZero} */
public long getTotalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHavingReachedCounterZero() {
return totalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHavingReachedCounterZero.get();
}
/** Getter for {@link #THREADS_TO_WAIT_ON_ACQUIRE} */
public static Map getThreadsToWaitOnAcquire() {
return new HashMap<>(THREADS_TO_WAIT_ON_ACQUIRE);
}
/** Getter for {@link #THREADS_TO_WAIT_ON_ACQUIRE_NAME_OF_METHOD_CREATING_TRACE} */
public static Map getThreadsToWaitOnAcquireMethodName() {
return new HashMap<>(THREADS_TO_WAIT_ON_ACQUIRE_NAME_OF_METHOD_CREATING_TRACE);
}
/** Getter for {@link #THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK} */
public static Map getThreadsToWaitOnAcquireReadLock() {
return THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK;
}
/** Getter for {@link #THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK_NAME_OF_METHOD_CREATING_TRACE} */
public static Map getThreadsToWaitOnAcquireReadLockMethodName() {
return THREADS_TO_WAIT_ON_ACQUIRE_READ_LOCK_NAME_OF_METHOD_CREATING_TRACE;
}
/** Getter for {@link #THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS} */
public static Set getThreadsWaitingToReleaseDeferredLocks() {
return new HashSet<>(THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS);
}
/** Getter for {@link #THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE} */
public static Map getThreadsWaitingToReleaseDeferredLocksJustification() {
return new HashMap<>(THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE);
}
/**
* The current thread has incremented the number of readers on the current cache key. It also wants to record into
* the read lock manager that this thread has acquired the cache key. This method should be user in all places where
* the cache key nunber of readers is incremented.
*/
protected void addReadLockToReadLockManager() {
Thread currentThread = Thread.currentThread();
ReadLockManager readLockManager = getReadLockManagerEnsureResultIsNotNull(currentThread);
ConcurrencyManager concurrencyManagerCacheKey = this;
readLockManager.addReadLock(concurrencyManagerCacheKey);
}
/**
* The current thread is about to decrement the number of readers in cache key. The thread also wants to update the
* read lock manager and remove the cache key that has previously been aquired from there.
*/
protected void removeReadLockFromReadLockManager() {
Thread currentThread = Thread.currentThread();
ReadLockManager readLockManager = getReadLockManager(currentThread);
if (readLockManager != null) {
ConcurrencyManager concurrencyManagerCacheKey = this;
readLockManager.removeReadLock(concurrencyManagerCacheKey);
removeReadLockManagerIfEmpty(currentThread);
} else {
// We have a problem we do not want ever see a decrement on the number of readers if we
// are not tracing one or more predecessor add read lock keys.
// so we will put the error message into a fresh new read lock manager
final int currentNumberOfReaders = this.numberOfReaders.get();
final int decrementedNumberOfReaders = currentNumberOfReaders - 1;
String errorMessage = ConcurrencyUtil.SINGLETON.readLockManagerProblem01CreateLogErrorMessageToIndicateThatCurrentThreadHasNullReadLockManagerWhileDecrementingNumberOfReaders(currentNumberOfReaders, decrementedNumberOfReaders, this);
readLockManager = getReadLockManagerEnsureResultIsNotNull(currentThread);
readLockManager.addRemoveReadLockProblemsDetected(errorMessage);
}
}
/**
* Same as {@link #getReadLockManager(Thread)} but in this case a not null result is ensured
*
* @param thread
* the thread wanting its read lock manager
* @return the read lock manager for the current thread.
*/
protected static ReadLockManager getReadLockManagerEnsureResultIsNotNull(Thread thread) {
Map readLockManagers = getReadLockManagers();
if (!readLockManagers.containsKey(thread)) {
ReadLockManager readLockManager = new ReadLockManager();
readLockManagers.putIfAbsent(thread, readLockManager);
return readLockManager;
}
return readLockManagers.get(thread);
}
/**
* Just like we see that the satic map of deffered locks is cleared of cache values for
* the current thread we also want to try to keep the static map of acquired read locks by a thread light weight by
* removing the association between the current thread and a read lock manager whenever the read lock manager
* becomes empty.
*
* @param thread
* the thread that wants its read lock manager destroyed if it is empty.
*/
protected static void removeReadLockManagerIfEmpty(Thread thread) {
Map readLockManagers = getReadLockManagers();
if (readLockManagers.containsKey(thread)) {
ReadLockManager readLockManager = readLockManagers.get(thread);
if (readLockManager.isEmpty()) {
readLockManagers.remove(thread);
}
}
}
/**
* Clear the justification why the {@link #isBuildObjectOnThreadComplete(Thread, Map, List, boolean) } is
* going nowhere.
*
*
* WHEN TO INVOKE:
* Should be invoked if we decide to blowup a thread with the explosive approach, for a thread in wait for release
* deferred lock. We do not want to keep traces of threads that left eclipselink code.
*
* Should be infokved when the algorithm returns TRUE - build object is complete.
* Should be invoked when we are not yet stuck for sufficient time and the release defferred logic algorithm is
* using the {@link #isBuildObjectOnThreadComplete(Thread, Map, List, boolean)} instead of the more verbose and slower
* {@link #isBuildObjectOnThreadComplete(Thread, Map, List, boolean)}.
*/
public static void clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse() {
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE.remove(Thread.currentThread());
}
/**
* See {@link #clearJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse()} in this case we want to store the
* justification computed by the
* {@link #enrichStringBuildingExplainWhyThreadIsStuckInIsBuildObjectOnThreadComplete(List, ConcurrencyManager, Thread, boolean, StringBuilder)}
*
* @param justification
* a string that helps us understand why the recursive algorithm returned false, building object is not
* yet complete.
*/
public static void setJustificationWhyMethodIsBuildingObjectCompleteReturnsFalse(String justification) {
THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE.put(Thread.currentThread(), justification);
}
}