Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L2 Cache Deadlock Prevention #2270

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kalinchan
Copy link

EclipseLink L2 Caching NullPointer Exception triggers a deadlock

The code that triggers the error in question is this line of code from the ObjectChangeSet class. This class seems to be responsible for synchronizing data that has been broadcasted to the current instance’s L2 cache:

protected Object getObjectForMerge(MergeManager mergeManager, AbstractSession session, Object primaryKey, ClassDescriptor descriptor) {
    ...
    CacheKey cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, descriptor.getJavaClass(), descriptor, true);
    if (cacheKey != null) {
        if (cacheKey.acquireReadLockNoWait()) {
            domainObject = cacheKey.getObject();
            cacheKey.releaseReadLock();
        } else {
            if (!mergeManager.isTransitionedToDeferredLocks()) {
                session.getIdentityMapAccessorInstance().getWriteLockManager().transitionToDeferredLocks(mergeManager);
            }
            //1 - LOCK ACQUIRED
            cacheKey.acquireDeferredLock();
            domainObject = cacheKey.getObject();
            int tries = 0;
            while (domainObject == null) {
                ++tries;
                if (tries > MAX_TRIES){
                    //NPE triggered here - session.getParent is null
                    session.getParent().log(SessionLog.SEVERE, SessionLog.CACHE, "entity_not_available_during_merge", new Object[]{descriptor.getJavaClassName(), cacheKey.getKey(), Thread.currentThread().getName(), cacheKey.getActiveThread()});
                    break;
                }
                synchronized (cacheKey) {
                        if (cacheKey.isAcquired()) {
                            try {
                                cacheKey.wait(10);
                            } catch (InterruptedException e) {
                                //ignore and return
                            }
                        }
                        domainObject = cacheKey.getObject();
                    }
                }
                //2 - LOCK RELEASED
                cacheKey.releaseDeferredLock();
            }

It seems that session.getParent() can be null in some specific contexts, as EclipseLink’s AbstractSession class method does return null explicitly until overridden in a child class:

/**
  * INTERNAL:
  * Gets the parent session.
  */
public AbstractSession getParent() {
    return null;
}

EclipseLink L2 Caching Deadlock Detection triggers a NullPointerException, causing deadlocks

The issue has been reported by a customer who experienced multiple NPE events in the following stack trace:

java.lang.NullPointerException
at org.eclipse.persistence.internal.identitymaps.CacheKey.hashCode(CacheKey.java:406)
at java.util.HashMap.hash(HashMap.java:340)
at java.util.HashMap.containsKey(HashMap.java:597)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.get(ConcurrencyUtil.java:1572)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.enrichMapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKeyInfoAboutReadLocks(ConcurrencyUtil.java:1516)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.createConcurrencyManagerState(ConcurrencyUtil.java:722)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.dumpConcurrencyManagerInformationStep01(ConcurrencyUtil.java:543)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.dumpConcurrencyManagerInformationIfAppropriate(ConcurrencyUtil.java:477)
at org.eclipse.persistence.internal.helper.ConcurrencyUtil.determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyUtil.java:170)
at org.eclipse.persistence.internal.helper.ConcurrencyManager.releaseDeferredLock(ConcurrencyManager.java:661)
at org.eclipse.persistence.internal.identitymaps.CacheKey.releaseDeferredLock(CacheKey.java:472)
...
The problem is that the error is caused by this piece of code that is tasked with releasing a deferred lock:

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);
    //Detection of deadlocks occur
    ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(this, whileStartTimeMillis, lockManager, readLockManager, ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired());
} catch (InterruptedException interrupted) { 
    //NPE aren't caught here so the locks are never removed!!
    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());
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant