Skip to content

Fix/2659 null deferred lock manager - #2660

Open
mask4z wants to merge 4 commits into
eclipse-ee4j:4.0from
mask4z:fix/2659-null-deferred-lock-manager
Open

mask4z wants to merge 4 commits into
eclipse-ee4j:4.0from
mask4z:fix/2659-null-deferred-lock-manager

Conversation

@mask4z

@mask4z mask4z commented Feb 26, 2026 •

Copy link
Copy Markdown

Summary

Fixes #2659 — NPE in WriteLockManager.appendLock when isTransitionedToDeferredLocks is true but the DeferredLockManager for the current thread has already been removed (e.g. torn down by a concurrent releaseDeferredLock() at depth 1 with no
deferred locks).

Also fixes a related double-release risk in UnitOfWorkImpl.releaseWriteLocks(): transitionToDeferredLocks() copies (not moves) keys from mergeManager.getAcquiredLocks() into DeferredLockManager.activeLocks, so calling both
releaseAllAcquiredLocks() and releaseActiveLocksOnThread() releases the same CacheKey objects twice.

Changes

  • WriteLockManager.appendLock — null-check DeferredLockManager before calling getActiveLocks().add(); fall back to mergeManager.getAcquiredLocks() if null.
  • WriteLockManager.transitionToDeferredLocks (catch block) — null-check before calling setIsThreadComplete(true) and removeDeferredLockManager().
  • ObjectChangeSet.getTargetVersionOfSourceObject — wrap acquireDeferredLock() / releaseDeferredLock() in try-finally; null-check session.getParent() before logging.
  • UnitOfWorkImpl.releaseWriteLocks — after releaseAllAcquiredLocks(), strip already-released keys from DeferredLockManager.activeLocks before calling releaseActiveLocksOnThread() to prevent double-release.

Test plan

Reported and diagnosed in: #2659

Renaldo Swanepoel added 4 commits February 26, 2026 19:05
- In ConcurrencyManager, catch exceptions in case anything happens that could have resulted in releaseAllLocksAquiredByThreadAlreadyPerformed to remained false and leave a lock applied.
- Added a null check in the WriteLockManager.appendLock as the deferred lock manager can sometimes be null.
- Added finally clauses to always release locks irrespective of something going wrong.
Ensure deferred lock manager along with its active locks are always cleared
…seWriteLocks()

  When transitionToDeferredLocks() is called during merge, it duplicates
  cache keys from mergeManager.getAcquiredLocks() into the DeferredLockManager's
  activeLocks list without removing them from getAcquiredLocks(). This means
  both lists can reference the same CacheKey instances.

  The safety-net cleanup added in releaseWriteLocks() called
  releaseActiveLocksOnThread() after releaseAllAcquiredLocks(), causing
  ConcurrencyManager.release() to be invoked twice on the same keys —
  throwing ConcurrencyException when depth was already zero.

  Fix: strip keys already released by releaseAllAcquiredLocks() from the
  DLM's activeLocks before calling releaseActiveLocksOnThread(), so only
  locks exclusively tracked by the DLM (those appended by appendLock's
  else-branch after isTransitionedToDeferredLocks was set) are released
  by the DLM cleanup path.
mask4z pushed a commit to mask4z/eclipselink that referenced this pull request Feb 27, 2026
…e-ee4j#2659

  Updates all 46 POM files from 4.0.0-M3.payara-p1 to
  4.0.0-M3.payara-p1.fix-2659 to align with eclipse-ee4j#2660
  and ensure the build is distinguishable from the unpatched Payara base.

  Also updates the OSGi Bundle-Version in the core module to
  4.0.0.M3_payara-p1_fix-2659 for runtime traceability.
mask4z added a commit to mask4z/eclipselink that referenced this pull request Feb 27, 2026
…e-ee4j#2659

  Updates all 46 POM files from 4.0.0-M3.payara-p1 to
  4.0.0-M3.payara-p1.fix-2659 to align with eclipse-ee4j#2660
  and ensure the build is distinguishable from the unpatched Payara base.

  Also updates the OSGi Bundle-Version in the core module to
  4.0.0.M3_payara-p1_fix-2659 for runtime traceability.
@mask4z

mask4z commented Jun 18, 2026

Copy link
Copy Markdown
Author

We've recently moved from Payara6 to Glassfish 8.0.3

I saw another ghost lock scenario in which I received the following error log:
"DTX5015: Caught exception in afterCompletion() callback:"
java.lang.NullPointerException: Cannot invoke "org.eclipse.persistence.internal.helper.DeferredLockManager.getActiveLocks()" because the return value of "org.eclipse.persistence.internal.helper.ConcurrencyManager.getDeferredLockManager(java.lang.Thread)" is null at org.eclipse.persistence.internal.helper.WriteLockManager.appendLock(WriteLockManager.java:557) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:836) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:716) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:320) at org.eclipse.persistence.mappings.ObjectReferenceMapping.mergeChangesIntoObject(ObjectReferenceMapping.java:441) at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeChangesIntoObject(ObjectBuilder.java:4288) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:899) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:716) at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:320) at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeChangesIntoParent(UnitOfWorkImpl.java:3428) at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.mergeChangesIntoParent(RepeatableWriteUnitOfWork.java:392) at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeClonesAfterCompletion(UnitOfWorkImpl.java:3561) at org.eclipse.persistence.transaction.AbstractSynchronizationListener.afterCompletion(AbstractSynchronizationListener.java:220) at org.eclipse.persistence.transaction.JTASynchronizationListener.afterCompletion(JTASynchronizationListener.java:82) at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:558) at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:853) at com.sun.ejb.containers.EJBContainerTransactionManager.completeNewTx(EJBContainerTransactionManager.java:752) at com.sun.ejb.containers.EJBContainerTransactionManager.postInvokeTx(EJBContainerTransactionManager.java:524) at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:3910) at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1767) at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1740) at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:176) at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:61) at jdk.proxy46.$Proxy681.getOrCreateContactForApplicantByRsaId(Unknown Source) at

@arjantijms arjantijms added the 4.0 label Sep 22, 2026
@arjantijms arjantijms added this to the 4.0.9 milestone Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants