From 51c09f683444e1380dad72b932f49a1cb45d7959 Mon Sep 17 00:00:00 2001 From: Radek Felcman Date: Wed, 26 Feb 2025 14:21:04 +0100 Subject: [PATCH 1/3] DeadLock, DeadLock diagnostic and CacheKey NPE This PR aggregates following PRs into single one: DeadLock diagnostic fix - Lock map snapshots #2288 Cache DeadLock Detection test move from org.eclipse.persistence.jpa.jse.test into new org.eclipse.persistence.jpa.testapps.deadlock.diagnostic Maven module #2304 New eclipselink dead lock scenario ... enhancement and unit test #2350 CacheKey.equals() NPE bugfix plus unit test #2364 Signed-off-by: Radek Felcman --- antbuild.properties | 3 +- antbuild.xml | 13 +- .../tests/junit/helper/HelperTest.java | 22 +- .../config/MergeManagerOperationMode.java | 42 +++ .../config/PersistenceUnitProperties.java | 19 +- .../persistence/config/SystemProperties.java | 19 +- .../internal/helper/ConcurrencyManager.java | 70 ++++- .../internal/helper/ConcurrencyUtil.java | 123 ++++++-- .../persistence/internal/helper/Helper.java | 26 +- .../internal/helper/ReadLockManager.java | 12 +- .../internal/helper/WriteLockManager.java | 17 +- .../type/CacheKeyToThreadRelationships.java | 4 +- .../helper/type/ConcurrencyManagerState.java | 23 +- .../internal/identitymaps/CacheKey.java | 7 +- .../identitymaps/FullIdentityMap.java | 7 +- .../i18n/LoggingLocalizationResource.java | 7 +- .../i18n/TraceLocalizationResource.java | 20 +- .../queries/OrderedListContainerPolicy.java | 3 +- .../internal/sessions/AbstractSession.java | 99 +++++- .../antbuild.properties | 62 ++++ .../antbuild.xml | 253 ++++++++++++++++ .../build.properties | 15 + .../src/META-INF/beans.xml | 2 +- .../src/META-INF/persistence.xml | 94 ++++++ .../CacheDeadLockDetectionDetail.java | 11 +- .../CacheDeadLockDetectionMaster.java | 11 +- .../DeadLockDiagnosticTableCreator.java | 103 +++++++ .../jpa/deadlock/diagnostic}/MainThread.java | 13 +- .../diagnostic}/event/EventObserver.java | 8 +- .../diagnostic}/event/EventProducer.java | 6 +- .../diagnostic}/event/EventProducerImpl.java | 6 +- .../diagnostic/weaving/EquipmentCacheKey.java | 33 ++ .../diagnostic/weaving/PortCacheKey.java | 32 ++ .../weaving/impl/EquipmentCacheKeyDAO.java | 125 ++++++++ .../weaving/impl/PortCacheKeyDAO.java | 111 +++++++ .../CacheDeadLockDetectionTest.java | 210 +++++++++++++ .../diagnostic/CacheDeadLockManagersTest.java | 284 ++++++++++++++++++ .../diagnostic/CacheKeyNullKeyTest.java | 214 +++++++++++++ .../jpa/deadlock/diagnostic/LogWrapper.java | 50 +++ .../test.properties | 24 ++ .../antbuild.properties | 13 +- jpa/eclipselink.jpa.test.jse/antbuild.xml | 55 +--- .../CacheDeadLockDetectionTest.java | 172 ----------- .../internal/jpa/EntityManagerSetupImpl.java | 19 +- 44 files changed, 2123 insertions(+), 339 deletions(-) create mode 100644 foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/MergeManagerOperationMode.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/antbuild.properties create mode 100644 jpa/eclipselink.jpa.test.deadlock/antbuild.xml create mode 100644 jpa/eclipselink.jpa.test.deadlock/build.properties rename jpa/{eclipselink.jpa.test.jse => eclipselink.jpa.test.deadlock}/src/META-INF/beans.xml (89%) create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/META-INF/persistence.xml rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/CacheDeadLockDetectionDetail.java (85%) rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/CacheDeadLockDetectionMaster.java (85%) create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/DeadLockDiagnosticTableCreator.java rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/MainThread.java (83%) rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/event/EventObserver.java (82%) rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/event/EventProducer.java (68%) rename jpa/{eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi => eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic}/event/EventProducerImpl.java (76%) create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/EquipmentCacheKey.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/PortCacheKey.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/EquipmentCacheKeyDAO.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/PortCacheKeyDAO.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockDetectionTest.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockManagersTest.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheKeyNullKeyTest.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/LogWrapper.java create mode 100644 jpa/eclipselink.jpa.test.deadlock/test.properties delete mode 100644 jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/CacheDeadLockDetectionTest.java diff --git a/antbuild.properties b/antbuild.properties index 36f53a3d80f..d37c43a757d 100644 --- a/antbuild.properties +++ b/antbuild.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2008, 2024 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2008, 2025 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 @@ -111,6 +111,7 @@ eclipselink.jpa.spring.test=${eclipselink.jpa.base}/eclipselink.jpa.spring.test eclipselink.jpa.wdf.test=${eclipselink.jpa.base}/eclipselink.jpa.wdf.test eclipselink.jpa.xsds=${eclipselink.jpa}/resource/org/eclipse/persistence/jpa eclipselink.jpa.plugins=${eclipselink.jpa.base}/${plugins.dir} +eclipselink.deadlock.test=${eclipselink.jpa.base}/eclipselink.jpa.test.deadlock eclipselink.modelgen=${eclipselink.jpa.base}/org.eclipse.persistence.jpa.modelgen eclipselink.jpars=${eclipselink.jpa.base}/org.eclipse.persistence.jpars eclipselink.jpars.test=${eclipselink.jpa.base}/eclipselink.jpars.test diff --git a/antbuild.xml b/antbuild.xml index 8a440521bff..7cc780a8548 100644 --- a/antbuild.xml +++ b/antbuild.xml @@ -1,6 +1,6 @@ + + + @@ -1855,7 +1860,7 @@ depends="clear-db, test-core-srg, test-jpa22-srg, test-jpars, test-moxy-srg, test-sdo-srg, test-dbws-srg, test-dbws-builder-srg, generate-report" /> + + + + diff --git a/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/junit/helper/HelperTest.java b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/junit/helper/HelperTest.java index 46278cdf7c6..4c2785b19d9 100644 --- a/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/junit/helper/HelperTest.java +++ b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/junit/helper/HelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 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 @@ -20,6 +20,8 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.Vector; import org.eclipse.persistence.internal.helper.Helper; @@ -315,4 +317,22 @@ public void timestampFromStringTest() { Helper.setShouldOptimizeDates(optimizedDatesState); } } + + @Test + public void copyMapTest() { + Map inputMap = new HashMap<>(); + inputMap.put(1L, "one"); + inputMap.put(2L, "two"); + Map copyMap = Helper.copyMap(inputMap); + Assert.assertEquals(inputMap, copyMap); + } + + @Test + public void copySetTest() { + Set inputSet = new HashSet<>(); + inputSet.add("one"); + inputSet.add("two"); + Set copySet = Helper.copySet(inputSet); + Assert.assertEquals(inputSet, copySet); + } } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/MergeManagerOperationMode.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/MergeManagerOperationMode.java new file mode 100644 index 00000000000..25572032c0d --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/MergeManagerOperationMode.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.config; + +/** + * INTERNAL: + *

+ * Purpose: It is data model behind {@linkplain org.eclipse.persistence.config.SystemProperties#CONCURRENCY_MANAGER_ALLOW_GET_CACHE_KEY_FOR_MERGE_MODE} + * or {@linkplain org.eclipse.persistence.config.PersistenceUnitProperties#CONCURRENCY_MANAGER_ALLOW_GET_CACHE_KEY_FOR_MERGE_MODE}. + */ +public final class MergeManagerOperationMode { + + /** + * {@code ORIGIN} (DEFAULT) - There is infinite {@linkplain java.lang.Object#wait()} call in case of some conditions during time when object/entity referred from + * {@code org.eclipse.persistence.internal.identitymaps.CacheKey} is locked and modified by another thread. In some cases it should leads into deadlock. + */ + public static final String ORIGIN = "ORIGIN"; + + /** + * {@code WAITLOOP} - Merge manager will try in the loop with timeout wait {@code cacheKey.wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());} + * fetch object/entity from {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey}. If fetch will be successful object/entity loop finish and continue + * with remaining code. If not @{code java.lang.InterruptedException} is thrown and caught and used {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey} instance + * status is set into invalidation state. This strategy avoid deadlock issue, but there should be impact to the performance. + */ + public static final String WAITLOOP = "WAITLOOP"; + + private MergeManagerOperationMode() { + // no instance please + } +} diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/PersistenceUnitProperties.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/PersistenceUnitProperties.java index 27ae1f4008b..4153eda456d 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/PersistenceUnitProperties.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/PersistenceUnitProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -4051,6 +4051,23 @@ public class PersistenceUnitProperties { */ public static final String CONCURRENCY_MANAGER_ALLOW_INTERRUPTED_EXCEPTION = "eclipselink.concurrency.manager.allow.interruptedexception"; + /** + *

+ * This property control in {@link org.eclipse.persistence.internal.sessions.AbstractSession#getCacheKeyFromTargetSessionForMerge(java.lang.Object, org.eclipse.persistence.internal.descriptors.ObjectBuilder, org.eclipse.persistence.descriptors.ClassDescriptor, org.eclipse.persistence.internal.sessions.MergeManager)} + * strategy how {@code org.eclipse.persistence.internal.identitymaps.CacheKey} will be fetched from shared cache. + *

+ * Allowed Values (case-sensitive String): + *

    + *
  • {@code ORIGIN} (DEFAULT) - There is infinite {@linkplain java.lang.Object#wait()} call in case of some conditions during time when object/entity referred from + * {@code org.eclipse.persistence.internal.identitymaps.CacheKey} is locked and modified by another thread. In some cases it should leads into deadlock. + *
  • {@code WAITLOOP} - Merge manager will try in the loop with timeout wait {@code cacheKey.wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());} + * fetch object/entity from {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey}. If fetch will be successful object/entity loop finish and continue + * with remaining code. If not @{code java.lang.InterruptedException} is thrown and caught and used {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey} instance + * status is set into invalidation state. This strategy avoid deadlock issue, but there should be impact to the performance. + *
+ */ + public static final String CONCURRENCY_MANAGER_ALLOW_GET_CACHE_KEY_FOR_MERGE_MODE = "eclipselink.concurrency.manager.allow.getcachekeyformerge.mode"; + /** *

* This property control (enable/disable) if ConcurrencyException fired when dead-lock diagnostic is enabled. diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/SystemProperties.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/SystemProperties.java index eee92c9543f..b84017cde45 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/SystemProperties.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/config/SystemProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 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 @@ -155,6 +155,23 @@ public class SystemProperties { */ public static final String CONCURRENCY_MANAGER_ALLOW_INTERRUPTED_EXCEPTION = "eclipselink.concurrency.manager.allow.interruptedexception"; + /** + *

+ * This property control in {@link org.eclipse.persistence.internal.sessions.AbstractSession#getCacheKeyFromTargetSessionForMerge(java.lang.Object, org.eclipse.persistence.internal.descriptors.ObjectBuilder, org.eclipse.persistence.descriptors.ClassDescriptor, org.eclipse.persistence.internal.sessions.MergeManager)} + * strategy how {@code org.eclipse.persistence.internal.identitymaps.CacheKey} will be fetched from shared cache. + *

+ * Allowed Values (case-sensitive String): + *

    + *
  • {@code ORIGIN} (DEFAULT) - There is infinite {@linkplain java.lang.Object#wait()} call in case of some conditions during time when object/entity referred from + * {@code org.eclipse.persistence.internal.identitymaps.CacheKey} is locked and modified by another thread. In some cases it should leads into deadlock. + *
  • {@code WAITLOOP} - Merge manager will try in the loop with timeout wait {@code cacheKey.wait(ConcurrencyUtil.SINGLETON.getAcquireWaitTime());} + * fetch object/entity from {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey}. If fetch will be successful object/entity loop finish and continue + * with remaining code. If not @{code java.lang.InterruptedException} is thrown and caught and used {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey} instance + * status is set into invalidation state. This strategy avoid deadlock issue, but there should be impact to the performance. + *
+ */ + public static final String CONCURRENCY_MANAGER_ALLOW_GET_CACHE_KEY_FOR_MERGE_MODE = "eclipselink.concurrency.manager.allow.getcachekeyformerge.mode"; + /** *

* This property control (enable/disable) if ConcurrencyException fired when dead-lock diagnostic is enabled. diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyManager.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyManager.java index 5d163dbd216..537cea1700c 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyManager.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -429,6 +429,13 @@ protected static Map getDeferredLockManagers() { return DEFERRED_LOCK_MANAGERS; } + /** + * Return snapshot of the deferred lock manager hashtable (thread - DeferredLockManager). + */ + protected static Map getDeferredLockManagersSnapshot() { + return Helper.copyMap(DEFERRED_LOCK_MANAGERS); + } + /** * Init the deferred lock managers (thread - DeferredLockManager). */ @@ -853,7 +860,7 @@ public void releaseAllLocksAcquiredByThread(DeferredLockManager lockManager) { * @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) { + public static ReadLockManager getReadLockManager(Thread thread) { Map readLockManagers = getReadLockManagers(); return readLockManagers.get(thread); } @@ -865,6 +872,13 @@ protected static Map getReadLockManagers() { return READ_LOCK_MANAGERS; } + /** + * Return snapshot of the deferred lock manager hashtable (thread - DeferredLockManager). + */ + protected static Map getReadLockManagersSnapshot() { + return Helper.copyMap(READ_LOCK_MANAGERS); + } + /** * Print the nested depth. */ @@ -969,33 +983,33 @@ public long getTotalNumberOfKeysReleasedForReadingBlewUpExceptionDueToCacheKeyHa } /** Getter for {@link #THREADS_TO_WAIT_ON_ACQUIRE} */ - public static Map getThreadsToWaitOnAcquire() { - return new HashMap<>(THREADS_TO_WAIT_ON_ACQUIRE); + public static Map getThreadsToWaitOnAcquireSnapshot() { + return Helper.copyMap(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); + public static Map getThreadsToWaitOnAcquireMethodNameSnapshot() { + return Helper.copyMap(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; + public static Map getThreadsToWaitOnAcquireReadLockSnapshot() { + return Helper.copyMap(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; + public static Map getThreadsToWaitOnAcquireReadLockMethodNameSnapshot() { + return Helper.copyMap(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); + public static Set getThreadsWaitingToReleaseDeferredLocksSnapshot() { + return Helper.copySet(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); + public static Map getThreadsWaitingToReleaseDeferredLocksJustificationSnapshot() { + return Helper.copyMap(THREADS_WAITING_TO_RELEASE_DEFERRED_LOCKS_BUILD_OBJECT_COMPLETE_GOES_NOWHERE); } /** @@ -1107,4 +1121,32 @@ public Lock getInstanceLock() { public Condition getInstanceLockCondition() { return this.instanceLockCondition; } + + public boolean isCacheKey() { + return false; + } + + /** + * Check if {@code org.eclipse.persistence.internal.helper.ConcurrencyManager} or child like {@code org.eclipse.persistence.internal.identitymaps.CacheKey} is currently being owned for writing + * and if that owning thread happens to be the current thread doing the check. + * + * @return {@code false} means either the thread is currently not owned by any other thread for writing purposes. Or otherwise if is owned by some thread + * but the thread is not the current thread. {@code false} is returned if and only if instance is being owned by some thread + * and that thread is not the current thread, it is some other competing thread. + */ + public boolean isAcquiredForWritingAndOwnedByDifferentThread() { + instanceLock.lock(); + try { + if (!this.isAcquired()) { + return false; + } + if (this.activeThread == null) { + return false; + } + Thread currentThread = Thread.currentThread(); + return this.activeThread != currentThread; + } finally { + instanceLock.unlock(); + } + } } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyUtil.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyUtil.java index 1e93eac323e..b931396f76c 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyUtil.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ConcurrencyUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024 Oracle, IBM and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025 Oracle, IBM 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 @@ -15,6 +15,7 @@ // IBM - ConcurrencyUtil call of ThreadMXBean.getThreadInfo() needs doPriv package org.eclipse.persistence.internal.helper; +import org.eclipse.persistence.config.MergeManagerOperationMode; import org.eclipse.persistence.config.SystemProperties; import org.eclipse.persistence.internal.helper.type.CacheKeyToThreadRelationships; import org.eclipse.persistence.internal.helper.type.ConcurrencyManagerState; @@ -25,6 +26,7 @@ import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedGetSystemProperty; import org.eclipse.persistence.internal.security.PrivilegedGetThreadInfo; +import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.logging.AbstractSessionLog; import org.eclipse.persistence.logging.SessionLog; @@ -68,6 +70,7 @@ public class ConcurrencyUtil { private boolean useSemaphoreToLimitConcurrencyOnWriteLockManagerAcquireRequiredLocks = getBooleanProperty(SystemProperties.CONCURRENCY_MANAGER_USE_SEMAPHORE_TO_SLOW_DOWN_WRITE_LOCK_MANAGER_ACQUIRE_REQUIRED_LOCKS, DEFAULT_USE_SEMAPHORE_TO_SLOW_DOWN_WRITE_LOCK_MANAGER_ACQUIRE_REQUIRED_LOCKS); private int noOfThreadsAllowedToObjectBuildInParallel = getIntProperty(SystemProperties.CONCURRENCY_MANAGER_OBJECT_BUILDING_NO_THREADS, DEFAULT_CONCURRENCY_MANAGER_OBJECT_BUILDING_NO_THREADS); private int noOfThreadsAllowedToDoWriteLockManagerAcquireRequiredLocksInParallel = getIntProperty(SystemProperties.CONCURRENCY_MANAGER_WRITE_LOCK_MANAGER_ACQUIRE_REQUIRED_LOCKS_NO_THREADS, DEFAULT_CONCURRENCY_MANAGER_WRITE_LOCK_MANAGER_ACQUIRE_REQUIRED_LOCKS_NO_THREADS); + private String concurrencyManagerAllowGetCacheKeyForMergeMode = getStringProperty(SystemProperties.CONCURRENCY_MANAGER_ALLOW_GET_CACHE_KEY_FOR_MERGE_MODE, MergeManagerOperationMode.ORIGIN); private long concurrencySemaphoreMaxTimePermit = getLongProperty(SystemProperties.CONCURRENCY_SEMAPHORE_MAX_TIME_PERMIT, DEFAULT_CONCURRENCY_SEMAPHORE_MAX_TIME_PERMIT); private long concurrencySemaphoreLogTimeout = getLongProperty(SystemProperties.CONCURRENCY_SEMAPHORE_LOG_TIMEOUT, DEFAULT_CONCURRENCY_SEMAPHORE_LOG_TIMEOUT); @@ -121,11 +124,15 @@ private ConcurrencyUtil() { * is most likely too dangerous and possibly the eclipselink code is not robust enough to code with such * scenarios We do not care so much about blowing up exception during object building but during * committing of transactions we are very afraid + * @return Returns a boolean value if the code believes that it is stuck. {@code true} means the code ended up either logging + * a tiny Dump message or the massive dump message. + * {@code false} means that the code did not do any logging and just quickly returned. This boolean flag is especially + * meaningful if we have the interrupt exceptions disabled and so the method is not being caused to blow up. + * In that case there is still a way to know if we believe to be stuck. * * @throws InterruptedException - * we fire an interrupted exception to ensure that the code blows up and releases all of the locks it - * had. + * it fires an interrupted exception to ensure that the code blows up and releases all the locks it had. */ - public void determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyManager concurrencyManager, + public boolean determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyManager concurrencyManager, final long whileStartTimeMillis, DeferredLockManager lockManager, ReadLockManager readLockManager, boolean callerIsWillingToAllowInterruptedExceptionToBeFiredUpIfNecessary) throws InterruptedException { @@ -138,7 +145,7 @@ public void determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyManag if (!tooMuchTimeHasElapsed) { // this thread is not stuck for that long let us allow the code to continue waiting for the lock to be acquired // or for the deferred locks to be considered as finished - return; + return false; } // (b) We believe this is a dead lock @@ -155,7 +162,7 @@ public void determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyManag // this thread has recently logged a small message about the fact that it is stuck // no point in logging another message like that for some time // let us allow for this thread to silently continue stuck without logging anything - return ; + return true; } // (c) This thread it is dead lock since the whileStartDate indicates a dead lock and @@ -197,6 +204,7 @@ public void determineIfReleaseDeferredLockAppearsToBeDeadLocked(ConcurrencyManag } else { AbstractSessionLog.getLog().log(SessionLog.SEVERE, SessionLog.CACHE,"concurrency_manager_allow_concurrency_exception_fired_up"); } + return true; } /** @@ -327,6 +335,14 @@ public void setNoOfThreadsAllowedToDoWriteLockManagerAcquireRequiredLocksInParal this.noOfThreadsAllowedToDoWriteLockManagerAcquireRequiredLocksInParallel = noOfThreadsAllowedToDoWriteLockManagerAcquireRequiredLocksInParallel; } + public String getConcurrencyManagerAllowGetCacheKeyForMergeMode() { + return concurrencyManagerAllowGetCacheKeyForMergeMode; + } + + public void setConcurrencyManagerAllowGetCacheKeyForMergeMode(String concurrencyManagerAllowGetCacheKeyForMergeMode) { + this.concurrencyManagerAllowGetCacheKeyForMergeMode = concurrencyManagerAllowGetCacheKeyForMergeMode; + } + public long getConcurrencySemaphoreMaxTimePermit() { return concurrencySemaphoreMaxTimePermit; } @@ -432,7 +448,7 @@ public boolean tooMuchTimeHasElapsed(final long whileStartTimeMillis, final long } /** - * Invoke the {@link #dumpConcurrencyManagerInformationStep01(Map, Map, Map, Map, Map, Map, Map, Set, Map, Map)} if sufficient time has passed. + * Invoke the {@link #dumpConcurrencyManagerInformationStep01(Map, Map, Map, Map, Map, Map, Map, Set, Map, Map, Map)} if sufficient time has passed. * This log message will potentially create a massive dump in the server log file. So we need to check when was the * last time that the masive dump was produced and decide if we can log again the state of the concurrency manager. * @@ -464,16 +480,17 @@ public void dumpConcurrencyManagerInformationIfAppropriate() { } // do the "MassiveDump" logging if enough time has passed since the previous massive dump logging - Map deferredLockManagers = ConcurrencyManager.getDeferredLockManagers(); - Map readLockManagersOriginal = ConcurrencyManager.getReadLockManagers(); - Map mapThreadToWaitOnAcquireOriginal = ConcurrencyManager.getThreadsToWaitOnAcquire(); - Map mapThreadToWaitOnAcquireMethodNameOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireMethodName(); - Map mapThreadToWaitOnAcquireReadLockOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireReadLock(); - Map mapThreadToWaitOnAcquireReadLockMethodNameOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireReadLockMethodName(); - Map> mapThreadToWaitOnAcquireInsideWriteLockManagerOriginal = WriteLockManager.getThreadToFailToAcquireCacheKeys(); - Set setThreadWaitingToReleaseDeferredLocksOriginal = ConcurrencyManager.getThreadsWaitingToReleaseDeferredLocks(); - Map mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone = ConcurrencyManager.getThreadsWaitingToReleaseDeferredLocksJustification(); - Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal = WriteLockManager.getMapWriteLockManagerThreadToObjectIdsWithChangeSet(); + Map deferredLockManagers = ConcurrencyManager.getDeferredLockManagersSnapshot(); + Map readLockManagersOriginal = ConcurrencyManager.getReadLockManagersSnapshot(); + Map mapThreadToWaitOnAcquireOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireSnapshot(); + Map mapThreadToWaitOnAcquireMethodNameOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireMethodNameSnapshot(); + Map mapThreadToWaitOnAcquireReadLockOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireReadLockSnapshot(); + Map mapThreadToWaitOnAcquireReadLockMethodNameOriginal = ConcurrencyManager.getThreadsToWaitOnAcquireReadLockMethodNameSnapshot(); + Map> mapThreadToWaitOnAcquireInsideWriteLockManagerOriginal = WriteLockManager.getThreadToFailToAcquireCacheKeysSnapshot(); + Set setThreadWaitingToReleaseDeferredLocksOriginal = ConcurrencyManager.getThreadsWaitingToReleaseDeferredLocksSnapshot(); + Map mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone = ConcurrencyManager.getThreadsWaitingToReleaseDeferredLocksJustificationSnapshot(); + Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal = WriteLockManager.getMapWriteLockManagerThreadToObjectIdsWithChangeSetSnapshot(); + Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys = AbstractSession.getThreadsToWaitMergeManagerWaitingDeferredCacheKeysSnapshot(); dumpConcurrencyManagerInformationStep01( deferredLockManagers, readLockManagersOriginal, @@ -484,7 +501,8 @@ public void dumpConcurrencyManagerInformationIfAppropriate() { mapThreadToWaitOnAcquireReadLockMethodNameOriginal, setThreadWaitingToReleaseDeferredLocksOriginal, mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone, - mapThreadToObjectIdWithWriteLockManagerChangesOriginal); + mapThreadToObjectIdWithWriteLockManagerChangesOriginal, + mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys); } /** @@ -538,7 +556,8 @@ protected void dumpConcurrencyManagerInformationStep01(Map mapThreadToWaitOnAcquireReadLockMethodNameOriginal, Set setThreadWaitingToReleaseDeferredLocksOriginal, Map mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone, - Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal) { + Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal, + Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys) { // (a) create object to represent our cache state. ConcurrencyManagerState concurrencyManagerState = createConcurrencyManagerState( deferredLockManagers, @@ -550,7 +569,8 @@ protected void dumpConcurrencyManagerInformationStep01(Mapissue 2094. + * @param mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys + * This parameter is related to the + * {@link AbstractSession#THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS} + * and to the issue 2094 it allows check + * threads that are at post-commit phase and are trying to merge their change set into the original + * objects in the cache. When this is taking place some of the cache keys that the merge manager is + * needing might be locked by other threads. This can lead to deadlocks, if our merge manager thread + * happens to be the owner of cache keys that matter to the owner of the cache keys the merge manager + * will need to acquire. + * @return A string describing all threads that are stuck in the + * {@code org.eclipse.persistence.internal.sessions.AbstractSession.getCacheKeyFromTargetSessionForMergeScenarioMergeManagerNotNullAndCacheKeyOriginalStillNull(CacheKey, Object, ObjectBuilder, ClassDescriptor, MergeManager) } + * logic. There is thread that want to return to the merge manage a cacheKey where the cacheKey object is no + * longer null. The threads are stuck because the cache key object is still null and the cache key is + * acquired most likely by some random thread doing object building. Deadlocks may be occurring between the + * merge manager and the object building threads. + */ + private String createInformationAboutThreadsHavingDifficultyGettingCacheKeysWithObjectDifferentThanNullDuringMergeClonesToCacheAfterTransactionCommit( + Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys) { + // (a) Create a header string of information + StringWriter writer = new StringWriter(); + writer.write(TraceLocalization.buildMessage("concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_page_header" + , new Object[] {mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys.size()})); + int currentThreadNumber = 0; + for (Map.Entry currentEntry : mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys.entrySet()) { + currentThreadNumber++; + Thread thread = currentEntry.getKey(); + String justification = currentEntry.getValue(); + writer.write(TraceLocalization.buildMessage("concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_body", + new Object[] {currentThreadNumber, thread.getName(), justification})); + } + writer.write(TraceLocalization.buildMessage("concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_page_end")); + return writer.toString(); + } + /** * create a DTO that tries to represent the current snapshot of the concurrency manager and write lock manager cache * state @@ -683,7 +746,8 @@ public ConcurrencyManagerState createConcurrencyManagerState( Map mapThreadToWaitOnAcquireReadLockMethodNameOriginal, Set setThreadWaitingToReleaseDeferredLocksOriginal, Map mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone, - Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal) { + Map> mapThreadToObjectIdWithWriteLockManagerChangesOriginal, + Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys) { // (a) As a first step we want to clone-copy the two maps // once we start working with the maps and using them to do dead lock detection // or simply print the state of the system we do not want the maps to continue changing as the threads referenced in the maps @@ -740,7 +804,8 @@ public ConcurrencyManagerState createConcurrencyManagerState( readLockManagerMapClone, deferredLockManagerMapClone, unifiedMapOfThreadsStuckTryingToAcquireWriteLock, mapThreadToWaitOnAcquireMethodNameClone, mapThreadToWaitOnAcquireReadLockClone, mapThreadToWaitOnAcquireReadLockMethodNameClone, setThreadWaitingToReleaseDeferredLocksClone, mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone, - mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey, mapThreadToObjectIdWithWriteLockManagerChangesClone); + mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey, mapThreadToObjectIdWithWriteLockManagerChangesClone, + mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys); } /** @@ -1689,4 +1754,18 @@ private boolean getBooleanProperty(final String key, final boolean defaultValue) } return defaultValue; } + + private String getStringProperty(final String key, final String defaultValue) { + final String value = PrivilegedAccessHelper.callDoPrivileged( + () -> System.getProperty(key, String.valueOf(defaultValue)) + ); + if (value != null) { + try { + return value.trim(); + } catch (Exception ignoreE) { + return defaultValue; + } + } + return defaultValue; + } } \ No newline at end of file diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/Helper.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/Helper.java index b331dc7ba58..e65b83a0b7b 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/Helper.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/Helper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; @@ -52,10 +53,12 @@ import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.Vector; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; import org.eclipse.persistence.config.SystemProperties; import org.eclipse.persistence.exceptions.ConversionException; @@ -800,6 +803,27 @@ public static Vector copyVector(List originalVector, int startIndex, int stopInd return newVector; } + /** Return a copy of the map. Returns an unmodifiable map. + * @param originalMap - original map + * @return Unmodifiable copy of originalMap + */ + public static Map copyMap(Map originalMap) { + Set> entries = originalMap.entrySet(); + Map shallowCopy = entries.stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return Collections.unmodifiableMap(shallowCopy); + } + + /** Return a copy of the set. Returns an unmodifiable set. + * @param originalSet - original set + * @return Unmodifiable copy of originalSet + */ + public static Set copySet(Set originalSet) { + Set shallowCopy = (Set) originalSet.stream() + .collect(Collectors.toSet()); + return Collections.unmodifiableSet(shallowCopy); + } + /** * Copy an array of strings to a new array * @param original diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ReadLockManager.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ReadLockManager.java index 0975400868d..9cb8e6f723f 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ReadLockManager.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/ReadLockManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025 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 @@ -15,6 +15,9 @@ package org.eclipse.persistence.internal.helper; import org.eclipse.persistence.internal.helper.type.ReadLockAcquisitionMetadata; +import org.eclipse.persistence.internal.identitymaps.CacheKey; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.logging.SessionLog; import java.util.*; import java.util.concurrent.locks.Lock; @@ -55,7 +58,12 @@ public void addReadLock(ConcurrencyManager concurrencyManager) { final Thread currentThread = Thread.currentThread(); final long currentThreadId = currentThread.getId(); ReadLockAcquisitionMetadata readLockAcquisitionMetadata = ConcurrencyUtil.SINGLETON.createReadLockAcquisitionMetadata(concurrencyManager); - + if (concurrencyManager.isCacheKey()) { + Object primaryKey = ((CacheKey)concurrencyManager).getKey(); + if (primaryKey == null) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.CACHE, "cache_key_null_read_lock_manager", new Object[] {concurrencyManager.toString()}, true); + } + } this.readLocks.add(FIRST_INDEX_OF_COLLECTION, concurrencyManager); if (!mapThreadToReadLockAcquisitionMetadata.containsKey(currentThreadId)) { List newList = Collections.synchronizedList(new ArrayList()); diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/WriteLockManager.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/WriteLockManager.java index 97bf8ad4fbe..fd017a79591 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/WriteLockManager.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/WriteLockManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -49,8 +49,6 @@ import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.mappings.DatabaseMapping; -import static java.util.Collections.unmodifiableMap; - /** * INTERNAL: *

@@ -133,6 +131,8 @@ public class WriteLockManager { private final Lock instancePrevailingQueueLock = new ReentrantLock(); private final Condition toWaitOnLockCondition = toWaitOnLock.newCondition(); + private static final String ACQUIRE_LOCK_FOR_CLONE_METHOD_NAME = WriteLockManager.class.getName() + ".acquireLocksForClone(...)"; + public WriteLockManager() { this.prevailingQueue = new ExposedNodeLinkedList(); } @@ -168,9 +168,8 @@ public Map acquireLocksForClone(Object objectForClone, ClassDescriptor descripto // of the concurrency manager that we use for creating the massive log dump // to indicate that the current thread is now stuck trying to acquire some arbitrary // cache key for writing - StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[1]; lastCacheKeyWeNeededToWaitToAcquire = toWaitOn; - lastCacheKeyWeNeededToWaitToAcquire.putThreadAsWaitingToAcquireLockForWriting(currentThread, stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "(...)"); + lastCacheKeyWeNeededToWaitToAcquire.putThreadAsWaitingToAcquireLockForWriting(currentThread, ACQUIRE_LOCK_FOR_CLONE_METHOD_NAME); // Since we know this one of those methods that can appear in the dead locks // we threads frozen here forever inside of the wait that used to have no timeout @@ -655,13 +654,13 @@ protected CacheKey waitOnObjectLock(ClassDescriptor descriptor, Object primaryKe // Helper data structures to have tracebility about object ids with change sets and cache keys we are sturggling to acquire /** Getter for {@link #THREAD_TO_FAIL_TO_ACQUIRE_CACHE_KEYS} */ - public static Map> getThreadToFailToAcquireCacheKeys() { - return unmodifiableMap(THREAD_TO_FAIL_TO_ACQUIRE_CACHE_KEYS); + public static Map> getThreadToFailToAcquireCacheKeysSnapshot() { + return Helper.copyMap(THREAD_TO_FAIL_TO_ACQUIRE_CACHE_KEYS); } /** Getter for {@link #MAP_WRITE_LOCK_MANAGER_THREAD_TO_OBJECT_IDS_WITH_CHANGE_SET} */ - public static Map> getMapWriteLockManagerThreadToObjectIdsWithChangeSet() { - return unmodifiableMap(MAP_WRITE_LOCK_MANAGER_THREAD_TO_OBJECT_IDS_WITH_CHANGE_SET); + public static Map> getMapWriteLockManagerThreadToObjectIdsWithChangeSetSnapshot() { + return Helper.copyMap(MAP_WRITE_LOCK_MANAGER_THREAD_TO_OBJECT_IDS_WITH_CHANGE_SET); } /** diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/CacheKeyToThreadRelationships.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/CacheKeyToThreadRelationships.java index 2410ec32e02..5b73e6a3ff2 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/CacheKeyToThreadRelationships.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/CacheKeyToThreadRelationships.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025 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 @@ -64,7 +64,7 @@ public class CacheKeyToThreadRelationships { /** * These are threads that have registered themselves as waiting for the cache key. See - * {@link org.eclipse.persistence.internal.helper.ConcurrencyManager#getThreadsToWaitOnAcquire()} + * {@link org.eclipse.persistence.internal.helper.ConcurrencyManager#getThreadsToWaitOnAcquireSnapshot()} * * (acquire lock for writing or as deferred - the cache key must be found with number of readers 0). */ diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/ConcurrencyManagerState.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/ConcurrencyManagerState.java index 94989c68a9f..01928434a3a 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/ConcurrencyManagerState.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/helper/type/ConcurrencyManagerState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025 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 @@ -84,6 +84,17 @@ public class ConcurrencyManagerState { */ private final Map> mapThreadToObjectIdWithWriteLockManagerChangesClone; + /** + * This field is related to the + * {@link org.eclipse.persistence.internal.sessions.AbstractSession#THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS} + * and to the bug https://github.com/eclipse-ee4j/eclipselink/issues/2094 it allows to monitor on threads + * that are at post-commit phase and are trying to merge their change set into the original objects in the cache. + * When this is taking place some of the cache keys that the merge manager is needing might be locked by other + * threads. This can lead to deadlocks, if our merge manager thread happens to be the owner of cache keys that + * matter to the owner of the cache keys the merge manager will need to acquire. + */ + private final Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys; + /** * Create a new ConcurrencyManagerState. * @@ -97,6 +108,7 @@ public class ConcurrencyManagerState { * @param mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone * @param mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey * @param mapThreadToObjectIdWithWriteLockManagerChangesClone + * @param mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys */ public ConcurrencyManagerState( Map readLockManagerMapClone, @@ -108,7 +120,8 @@ public ConcurrencyManagerState( Set setThreadWaitingToReleaseDeferredLocksClone, Map mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone, Map mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey, - Map> mapThreadToObjectIdWithWriteLockManagerChangesClone) { + Map> mapThreadToObjectIdWithWriteLockManagerChangesClone, + Map mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys) { super(); this.readLockManagerMapClone = readLockManagerMapClone; this.deferredLockManagerMapClone = deferredLockManagerMapClone; @@ -120,6 +133,7 @@ public ConcurrencyManagerState( this.mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone = mapThreadsThatAreCurrentlyWaitingToReleaseDeferredLocksJustificationClone; this.mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey = mapOfCacheKeyToDtosExplainingThreadExpectationsOnCacheKey; this.mapThreadToObjectIdWithWriteLockManagerChangesClone = mapThreadToObjectIdWithWriteLockManagerChangesClone; + this.mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys = mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys; } /** Getter for {@link #readLockManagerMapClone} */ @@ -171,4 +185,9 @@ public Map getUnifiedMapOfThreadsStuckTryingToAcquireWriteLockMe public Map getMapThreadToWaitOnAcquireReadLockCloneMethodName() { return unmodifiableMap(mapThreadToWaitOnAcquireReadLockCloneMethodName); } + + /** Getter for {@link #mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys} */ + public Map getMapThreadsToWaitMergeManagerWaitingDeferredCacheKeys() { + return unmodifiableMap(mapThreadsToWaitMergeManagerWaitingDeferredCacheKeys); + } } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/CacheKey.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/CacheKey.java index 30c6ae177a3..22f2e89f036 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/CacheKey.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/CacheKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 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 @@ -604,4 +604,9 @@ public Object waitForObject() { getInstanceLock().unlock(); } } + + @Override + public boolean isCacheKey() { + return true; + } } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/FullIdentityMap.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/FullIdentityMap.java index 6278bf12f39..079f1debd36 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/FullIdentityMap.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/identitymaps/FullIdentityMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 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 @@ -23,6 +23,8 @@ import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.indirection.ValueHolderInterface; import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.mappings.ForeignReferenceMapping; /** @@ -113,6 +115,9 @@ public CacheKey getCacheKey(Object searchKey, boolean forMerge) { */ @Override protected CacheKey putCacheKeyIfAbsent(CacheKey searchKey) { + if (searchKey.getKey() == null) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.CACHE, "cache_key_null_identity_map", new Object[] {searchKey.toString()}, true); + } searchKey.setOwningMap(this); return (CacheKey)((ConcurrentMap)this.cacheKeys).putIfAbsent(searchKey.getKey(), searchKey); } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java index d7ece81ad9a..c4c29f093c7 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2021 IBM Corporation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -500,8 +500,9 @@ public class LoggingLocalizationResource extends ListResourceBundle { "\nReencrypt it by `passwordUpdate.sh` from eclipselink.zip bundle."}, { "encryptor_script_usage", "Usage is `passwordUpdate.sh|.cmd -ip `"}, { "encryptor_script_description", "This application internally decrypts an old encrypted password that was used by a previous EclipseLink version and encrypts it by the latest algorithm."}, - { "encryptor_script_output", "Reencrypted password is: {0}"} - + { "encryptor_script_output", "The reencrypted password is: {0}"}, + { "cache_key_null_read_lock_manager", "CacheKey instance: {0} , locked by cache read lock manager has null primary key." }, + { "cache_key_null_identity_map", "CacheKey instance: {0} , stored into identity map has null primary key." } }; /** diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/TraceLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/TraceLocalizationResource.java index f217f11463c..7514c878937 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/TraceLocalizationResource.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/TraceLocalizationResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2021 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -269,6 +269,24 @@ public class TraceLocalizationResource extends ListResourceBundle { + " stopping the candidate thread to make progress... We expect this code spot to never be invoked. " + " Either this thread made progress or if it continues to be stuck in the releaseDeferredLock " + " we most likely have an implementation bug somewhere. "}, + { "concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_justification", + " Merge manager logic is currently stuck in the process of trying to return the cache key: {0} \n" + + " this cache key is currently acquired by a competing thread: {1} . \n" + + " This cache key has the problem that its original object is still set to NULL. \n" + + " The operation of this current thread is that by waiting for some time the current owner of the cache key will finish object building and release the cache key. \n" + + " Note: There is real risk that we are in a deadlock. The daedlock exists if the current thread: {2} \n" + + " is owning other cache key resources as a writer. Any lock acquired by the current thread might be needed by competing threads. \n" + + " Much in the same manner that our current thread is stuck hoping to see this specific cache key being released. \n"}, + { "concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_page_header" + , "Concurrency manager - Page 08 start - Threads in MergeManager Acquiring Cache Keys for Clones to be merged into eclipselink server session cache of originals" + + "\n This section provides information about threads within the MergeManager that require cache keys for merging clones with changes." + + "\n Specifically, it focuses on the threads working in the context of an ObjectChangeSet where the server session CacheKey is found to still have CacheKy.object null," + + "\n and the CacheKey is acquired by a competing thread (typically an ObjectBuilder thread)." + + "\nTotal number of threads waiting to see lock being released: {0}\n\n"}, + { "concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_body" + , "[currentThreadNumber: {0}] [ThreadName: {1}]: Justification for being stuck: {2}\n"}, + { "concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_page_end" + , "Concurrency manager - Page 08 end - Threads in MergeManager Acquiring Cache Keys for Clones to be merged into eclipselink server session cache of originals\n"}, { "XML_call", "XML call" }, { "XML_data_call", "XML data call" }, { "XML_data_delete", "XML data delete" }, diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/queries/OrderedListContainerPolicy.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/queries/OrderedListContainerPolicy.java index 578a22b8764..70c9606756f 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/queries/OrderedListContainerPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/queries/OrderedListContainerPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 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 @@ -715,7 +715,6 @@ public void updateChangeRecordForSelfMerge(ChangeRecord changeRecord, Object sou if (changeObject.getChangeSet() == sourceSet){ changeObject.setChangeSet(((UnitOfWorkChangeSet)unitOfWork.getUnitOfWorkChangeSet()).findOrCreateLocalObjectChangeSet(target, mapping.getReferenceDescriptor(), unitOfWork.isCloneNewObject(target))); } - return; } } diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/sessions/AbstractSession.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/sessions/AbstractSession.java index cd930922b4f..a8526d7475f 100644 --- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/sessions/AbstractSession.java +++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/sessions/AbstractSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2021 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -50,8 +50,10 @@ import java.util.Map; import java.util.Set; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import org.eclipse.persistence.config.MergeManagerOperationMode; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.config.ReferenceMode; import org.eclipse.persistence.descriptors.ClassDescriptor; @@ -77,8 +79,11 @@ import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.descriptors.ObjectBuilder; import org.eclipse.persistence.internal.helper.ConcurrencyManager; +import org.eclipse.persistence.internal.helper.ConcurrencyUtil; +import org.eclipse.persistence.internal.helper.DeferredLockManager; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.helper.QueryCounter; +import org.eclipse.persistence.internal.helper.ReadLockManager; import org.eclipse.persistence.internal.helper.linkedlist.ExposedNodeLinkedList; import org.eclipse.persistence.internal.history.HistoricalSession; import org.eclipse.persistence.internal.identitymaps.CacheKey; @@ -87,6 +92,7 @@ import org.eclipse.persistence.internal.indirection.ProtectedValueHolder; import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy; import org.eclipse.persistence.internal.localization.ExceptionLocalization; +import org.eclipse.persistence.internal.localization.TraceLocalization; import org.eclipse.persistence.internal.queries.JoinedAttributeManager; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedClassForName; @@ -164,6 +170,18 @@ * @see DatabaseSessionImpl */ public abstract class AbstractSession extends CoreAbstractSession implements org.eclipse.persistence.sessions.Session, CommandProcessor, Serializable, Cloneable { + + /** + * See issue 2094. + * These are threads involved in the "getCacheKeyFromTargetSessionForMerge" and that detect that the cache key + * somehow still has the Object of the cache key set to null. + * If the cache key is acquired by different thread, the thread will be waiting and hoping for that cache key + * to eventually stop being acquired. + * But this process is highly risk as the thread doing the wait might be the owner of resources of the thread + * that is currently the owner of the cache key it desires. + */ + private static final Map THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS = new ConcurrentHashMap<>(); + /** ExceptionHandler handles database exceptions. */ transient protected ExceptionHandler exceptionHandler; @@ -2861,18 +2879,71 @@ protected CacheKey getCacheKeyFromTargetSessionForMerge(Object implementation, O getIdentityMapAccessorInstance().getWriteLockManager().transitionToDeferredLocks(mergeManager); } cacheKey.acquireDeferredLock(); - original = cacheKey.getObject(); - if (original == null) { - synchronized (cacheKey) { - if (cacheKey.isAcquired()) { + + switch (ConcurrencyUtil.SINGLETON.getConcurrencyManagerAllowGetCacheKeyForMergeMode()) { + case MergeManagerOperationMode.ORIGIN: { + original = cacheKey.getObject(); + if (original == null) { + synchronized (cacheKey) { + if (cacheKey.isAcquired()) { + try { + cacheKey.wait(); + } catch (InterruptedException e) { + //ignore and return + } + } + original = cacheKey.getObject(); + } + } + break; + } + case MergeManagerOperationMode.WAITLOOP: { + final Thread currentThread = Thread.currentThread(); + final String currentThreadName = currentThread.getName(); + final long whileStartTimeMillis = System.currentTimeMillis(); + final DeferredLockManager lockManager = ConcurrencyManager.getDeferredLockManager(currentThread); + final ReadLockManager readLockManager = ConcurrencyManager.getReadLockManager(currentThread); + + original = cacheKey.getObject(); + boolean originalIsStillNull = original == null; + boolean isToBeStuckIntoDeadlock = false; + if (!originalIsStillNull) { + return cacheKey; + } + cacheKey.getInstanceLock().lock(); + try { + boolean someOtherThreadCurrentlyOwningTheCacheKey = cacheKey.isAcquiredForWritingAndOwnedByDifferentThread(); + if (!someOtherThreadCurrentlyOwningTheCacheKey) { + return cacheKey; + } + final String cacheKeyToStringOwnedByADifferentThread = ConcurrencyUtil.SINGLETON.createToStringExplainingOwnedCacheKey(cacheKey); + String justification = TraceLocalization.buildMessage("concurrency_util_threads_having_difficulty_getting_cache_keys_with_object_different_than_null_during_merge_clones_to_cache_after_transaction_commit_justification", + new Object[] {cacheKeyToStringOwnedByADifferentThread, cacheKey.getActiveThread(), currentThreadName}); try { - cacheKey.wait(); + setThreadsToWaitMergeManagerWaitingDeferredCacheKeys(justification); + while (someOtherThreadCurrentlyOwningTheCacheKey && originalIsStillNull && !isToBeStuckIntoDeadlock) { + cacheKey.getInstanceLockCondition().await(ConcurrencyUtil.SINGLETON.getAcquireWaitTime(), TimeUnit.MILLISECONDS); + isToBeStuckIntoDeadlock = ConcurrencyUtil.SINGLETON.determineIfReleaseDeferredLockAppearsToBeDeadLocked(cacheKey, whileStartTimeMillis, + lockManager, readLockManager, true); + someOtherThreadCurrentlyOwningTheCacheKey = cacheKey.isAcquiredForWritingAndOwnedByDifferentThread(); + original = cacheKey.getObject(); + originalIsStillNull = original == null; + + } } catch (InterruptedException e) { - //ignore and return + cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); + return cacheKey; + } finally { + if (isToBeStuckIntoDeadlock) { + cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); + } } + } finally { + clearThreadsToWaitMergeManagerWaitingDeferredCacheKeys(); + cacheKey.getInstanceLock().unlock(); } - original = cacheKey.getObject(); } + break; } cacheKey.releaseDeferredLock(); } @@ -5477,4 +5548,16 @@ public void setTolerateInvalidJPQL(boolean b) { public boolean shouldTolerateInvalidJPQL() { return this.tolerateInvalidJPQL; } + + public static Map getThreadsToWaitMergeManagerWaitingDeferredCacheKeysSnapshot() { + return Helper.copyMap(THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS); + } + + public static void clearThreadsToWaitMergeManagerWaitingDeferredCacheKeys() { + THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS.remove(Thread.currentThread()); + } + + public static void setThreadsToWaitMergeManagerWaitingDeferredCacheKeys(String justification) { + THREADS_TO_WAIT_MERGE_MANAGER_WAITING_DEFERRED_CACHE_KEYS.put(Thread.currentThread(), justification); + } } diff --git a/jpa/eclipselink.jpa.test.deadlock/antbuild.properties b/jpa/eclipselink.jpa.test.deadlock/antbuild.properties new file mode 100644 index 00000000000..90af64cd09d --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/antbuild.properties @@ -0,0 +1,62 @@ +# +# Copyright (c) 2025 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 +# + +custom=true +# Edit this if/when the release number changes +release.version=2.7.16 + +# Link to dependencies not in SVN, assumes default location, if you store elsewhere you need to edit these. +junit.lib=../../../extension.lib.external/junit.jar +eclipselink.jar=eclipselink.jar +coretest.framework.jar=eclipselink-coretest-framework.jar +jpatest.framework.jar=eclipselink-jpatest-framework.jar +core.test.jar=eclipselink-core-tests.jar + + +# Edit this property to "true" to enable debug attributes like lines, variables and source-filename. +javac.debug=true +javac.debuglevel=lines,vars,source + +# Edit this property when you would like to override the java version during testing +javac.version=1.8 + +# Edit this property to increase the maxMemory heap memory used by the tests if you get an OutOfMemoryException - use JConsole.exe to triage the memory spike at the end of the test +# The current default is 768m (the LC "m" is required") +max.heap.memory=768m + +# Do not edit the properties below. + +# General Infrastructure Definitions +-------------------------------------- +test.properties=./test.properties +src.dir=src +classes.dir=classes +classes21.dir=classes21 +build.dir=build +resource.dir=resource +plugins.dir=plugins +eclipselink.external.libs=../../../extension.lib.external +annotation-api.jar=jakarta.annotation-api.jar +transaction.jar=jakarta.transaction-api.jar +validation.jar=jakarta.validation-api.jar +persistence22.jar=jakarta.persistence_2.2.3.jar +cdi-api.jar=jakarta.enterprise.cdi-api.jar +inject.jar=jakarta.inject.jar +interceptor-api.jar=jakarta.interceptor-api.jar +el-api.jar=jakarta.el-api.jar +jboss-classfilewriter.jar=jboss-classfilewriter.jar +jboss-weld-api.jar=weld-api.jar +jboss-weld-spi.jar=weld-spi.jar +jboss-weld-environment-common.jar=weld-environment-common.jar +jboss-weld-se-core.jar=weld-se-core.jar +jboss-weld-core-impl.jar=weld-core-impl.jar +jboss-logging.jar=jboss-logging.jar diff --git a/jpa/eclipselink.jpa.test.deadlock/antbuild.xml b/jpa/eclipselink.jpa.test.deadlock/antbuild.xml new file mode 100644 index 00000000000..52d943cc8ad --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/antbuild.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jpa/eclipselink.jpa.test.deadlock/build.properties b/jpa/eclipselink.jpa.test.deadlock/build.properties new file mode 100644 index 00000000000..fbf62672fe6 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/build.properties @@ -0,0 +1,15 @@ +# +# Copyright (c) 2025 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 +# + +bin.includes = .,\ + META-INF/ +source.. = src/ diff --git a/jpa/eclipselink.jpa.test.jse/src/META-INF/beans.xml b/jpa/eclipselink.jpa.test.deadlock/src/META-INF/beans.xml similarity index 89% rename from jpa/eclipselink.jpa.test.jse/src/META-INF/beans.xml rename to jpa/eclipselink.jpa.test.deadlock/src/META-INF/beans.xml index 443c14a5e0f..1f7e6d82779 100644 --- a/jpa/eclipselink.jpa.test.jse/src/META-INF/beans.xml +++ b/jpa/eclipselink.jpa.test.deadlock/src/META-INF/beans.xml @@ -1,7 +1,7 @@ + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionDetail + true + + + + + + + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionDetail + true + + + + + + + + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionDetail + true + + + + + + + + + + + + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl.EquipmentCacheKeyDAO + org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl.PortCacheKeyDAO + true + + + + + + + + + + + + + + + diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionDetail.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionDetail.java similarity index 85% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionDetail.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionDetail.java index b3ec74ce6d0..903c2561f08 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionDetail.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionDetail.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025 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 @@ -12,22 +12,25 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock.model; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic; import javax.persistence.*; @Entity -@Table(name = "cachedeadlock_detail") +@Table(name = "CACHEDEADLOCK_DETAIL") @Cacheable(true) @NamedQuery(name="DetailEntity.findById", query="SELECT t FROM CacheDeadLockDetectionDetail t WHERE t.id = :id") public class CacheDeadLockDetectionDetail { + @Id + @Column(name = "ID") private long id; + @Column(name = "NAME") private String name; @ManyToOne - @JoinColumn(name = "id_fk") + @JoinColumn(name = "CACHEDEADLOCK_MASTER_FK") private CacheDeadLockDetectionMaster master; public CacheDeadLockDetectionDetail() { diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionMaster.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionMaster.java similarity index 85% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionMaster.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionMaster.java index be954aebe7e..98382fd13ac 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/model/CacheDeadLockDetectionMaster.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/CacheDeadLockDetectionMaster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 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 @@ -12,20 +12,23 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock.model; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity -@Table(name = "cachedeadlock_master") +@Table(name = "CACHEDEADLOCK_MASTER") @Cacheable(true) @NamedQuery(name="MasterEntity.findById", query="SELECT t FROM CacheDeadLockDetectionMaster t WHERE t.id = :id") public class CacheDeadLockDetectionMaster { + @Id + @Column(name = "ID") private long id; + @Column(name = "NAME") private String name; private List details = new ArrayList<>(); @@ -60,7 +63,7 @@ public void setName(String name) { } @OneToMany(fetch = FetchType.LAZY, mappedBy = "master", cascade = CascadeType.ALL) - @JoinColumn(name = "id_fk", referencedColumnName = "id") + @JoinColumn(name = "CACHEDEADLOCK_MASTER_FK", referencedColumnName = "ID") public List getDetails() { return details; } diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/DeadLockDiagnosticTableCreator.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/DeadLockDiagnosticTableCreator.java new file mode 100644 index 00000000000..035707a9fe3 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/DeadLockDiagnosticTableCreator.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 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.testing.models.jpa.deadlock.diagnostic; + +import org.eclipse.persistence.tools.schemaframework.FieldDefinition; +import org.eclipse.persistence.tools.schemaframework.ForeignKeyConstraint; +import org.eclipse.persistence.tools.schemaframework.TableDefinition; + +public class DeadLockDiagnosticTableCreator extends org.eclipse.persistence.tools.schemaframework.TableCreator { + + public DeadLockDiagnosticTableCreator() { + setName("DeadLockDiagnostic"); + + addTableDefinition(buildCACHEDEADLOCK_MASTERTable()); + addTableDefinition(buildCACHEDEADLOCK_DETAILTable()); + } + + public TableDefinition buildCACHEDEADLOCK_MASTERTable() { + TableDefinition table = new TableDefinition(); + table.setName("CACHEDEADLOCK_MASTER"); + + FieldDefinition fieldID = new FieldDefinition(); + fieldID.setName("ID"); + fieldID.setTypeName("NUMBER"); + fieldID.setSize(0); + fieldID.setSubSize(0); + fieldID.setIsPrimaryKey(true); + fieldID.setIsIdentity(false); + fieldID.setUnique(false); + fieldID.setShouldAllowNull(false); + table.addField(fieldID); + + FieldDefinition fieldNAME = new FieldDefinition(); + fieldNAME.setName("NAME"); + fieldNAME.setTypeName("VARCHAR"); + fieldNAME.setSize(400); + fieldNAME.setShouldAllowNull(true); + fieldNAME.setIsPrimaryKey(false); + fieldNAME.setUnique(false); + fieldNAME.setIsIdentity(false); + table.addField(fieldNAME); + + return table; + } + + public TableDefinition buildCACHEDEADLOCK_DETAILTable() { + TableDefinition table = new TableDefinition(); + table.setName("CACHEDEADLOCK_DETAIL"); + + FieldDefinition fieldID = new FieldDefinition(); + fieldID.setName("ID"); + fieldID.setTypeName("NUMBER"); + fieldID.setSize(0); + fieldID.setSubSize(0); + fieldID.setIsPrimaryKey(true); + fieldID.setIsIdentity(false); + fieldID.setUnique(false); + fieldID.setShouldAllowNull(false); + table.addField(fieldID); + + FieldDefinition fieldNAME = new FieldDefinition(); + fieldNAME.setName("NAME"); + fieldNAME.setTypeName("VARCHAR"); + fieldNAME.setSize(400); + fieldNAME.setShouldAllowNull(true); + fieldNAME.setIsPrimaryKey(false); + fieldNAME.setUnique(false); + fieldNAME.setIsIdentity(false); + table.addField(fieldNAME); + + FieldDefinition fieldCACHEDEADLOCK_MASTER_FK = new FieldDefinition(); + fieldCACHEDEADLOCK_MASTER_FK.setName("CACHEDEADLOCK_MASTER_FK"); + fieldCACHEDEADLOCK_MASTER_FK.setTypeName("NUMBER"); + fieldCACHEDEADLOCK_MASTER_FK.setSize(0); + fieldCACHEDEADLOCK_MASTER_FK.setSubSize(0); + fieldCACHEDEADLOCK_MASTER_FK.setIsPrimaryKey(false); + fieldCACHEDEADLOCK_MASTER_FK.setIsIdentity(false); + fieldCACHEDEADLOCK_MASTER_FK.setUnique(false); + fieldCACHEDEADLOCK_MASTER_FK.setShouldAllowNull(true); + table.addField(fieldCACHEDEADLOCK_MASTER_FK); + + ForeignKeyConstraint foreignKeyM_CACHEDEADLOCK_MASTER = new ForeignKeyConstraint(); + foreignKeyM_CACHEDEADLOCK_MASTER.setName("M_CACHEDEADLOCK_MASTER_FK"); + foreignKeyM_CACHEDEADLOCK_MASTER.setTargetTable("CACHEDEADLOCK_MASTER"); + foreignKeyM_CACHEDEADLOCK_MASTER.addSourceField("CACHEDEADLOCK_MASTER_FK"); + foreignKeyM_CACHEDEADLOCK_MASTER.addTargetField("ID"); + table.addForeignKeyConstraint(foreignKeyM_CACHEDEADLOCK_MASTER); + + return table; + } +} diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/MainThread.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/MainThread.java similarity index 83% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/MainThread.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/MainThread.java index cbd27f61357..e0b9c1f18c4 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/MainThread.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/MainThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 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 @@ -12,11 +12,9 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic; -import org.eclipse.persistence.jpa.test.cachedeadlock.cdi.event.EventProducer; -import org.eclipse.persistence.jpa.test.cachedeadlock.model.CacheDeadLockDetectionMaster; -import org.eclipse.persistence.jpa.test.cachedeadlock.model.CacheDeadLockDetectionDetail; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.event.EventProducer; import javax.enterprise.inject.se.SeContainer; import javax.inject.Inject; @@ -41,8 +39,8 @@ public MainThread() {} public MainThread(SeContainer container, EntityManagerFactory emf, EntityManager em) { this.container = container; - this.emf = emf; - this.em = em; + MainThread.emf = emf; + MainThread.em = em; eventProducer = container.select(EventProducer.class).get(); queryMaster = em.createNamedQuery("MasterEntity.findById", CacheDeadLockDetectionMaster.class); queryMaster.setParameter("id", ID); @@ -50,6 +48,7 @@ public MainThread(SeContainer container, EntityManagerFactory emf, EntityManager queryDetail.setParameter("id", ID); } + @Override public void run() { CacheDeadLockDetectionMaster resultsMaster = queryMaster.getSingleResult(); CacheDeadLockDetectionDetail resultsDetail = queryDetail.getSingleResult(); diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventObserver.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventObserver.java similarity index 82% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventObserver.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventObserver.java index 8d1b4f34505..bf082778a94 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventObserver.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventObserver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 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 @@ -12,10 +12,10 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock.cdi.event; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.event; -import org.eclipse.persistence.jpa.test.cachedeadlock.MainThread; -import org.eclipse.persistence.jpa.test.cachedeadlock.model.CacheDeadLockDetectionMaster; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.MainThread; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster; import javax.persistence.EntityManager; import javax.persistence.Query; diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducer.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducer.java similarity index 68% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducer.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducer.java index 0cff2bf9457..4471d821be7 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducer.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 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 @@ -12,9 +12,9 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock.cdi.event; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.event; -import org.eclipse.persistence.jpa.test.cachedeadlock.model.CacheDeadLockDetectionMaster; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster; public interface EventProducer { diff --git a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducerImpl.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducerImpl.java similarity index 76% rename from jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducerImpl.java rename to jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducerImpl.java index 1bf52bc4074..47bf58b552b 100644 --- a/jpa/eclipselink.jpa.test.jse/src/org/eclipse/persistence/jpa/test/cachedeadlock/cdi/event/EventProducerImpl.java +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/event/EventProducerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 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 @@ -12,9 +12,9 @@ // Contributors: // Oracle - initial API and implementation -package org.eclipse.persistence.jpa.test.cachedeadlock.cdi.event; +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.event; -import org.eclipse.persistence.jpa.test.cachedeadlock.model.CacheDeadLockDetectionMaster; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster; import javax.enterprise.event.Event; import javax.enterprise.inject.Any; diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/EquipmentCacheKey.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/EquipmentCacheKey.java new file mode 100644 index 00000000000..cdd4add76e1 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/EquipmentCacheKey.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving; + +import java.util.ArrayList; +import java.util.List; + + +public interface EquipmentCacheKey { + + long getEntityId(); + + String getId(); + void setId(String id); + + List getPorts(); + void setPortCacheKeys(ArrayList portCacheKeys); + + PortCacheKey removePort(int i); + void addPortCacheKey(PortCacheKey p3); +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/PortCacheKey.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/PortCacheKey.java new file mode 100644 index 00000000000..35364038f81 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/PortCacheKey.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving; + +public interface PortCacheKey { + + long getEntityId(); + + String getId(); + void setId(String id); + + int getPortCacheKeyOrder(); + void setPortCacheKeyOrder(int portOrder); + + EquipmentCacheKey getEquipmentCacheKey(); + void setEquipmentCacheKey(EquipmentCacheKey equipmentCacheKey); + + EquipmentCacheKey getVirtualEquipmentCacheKey(); + void setVirtualEquipmentCacheKey(EquipmentCacheKey equipmentCacheKey); +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/EquipmentCacheKeyDAO.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/EquipmentCacheKeyDAO.java new file mode 100644 index 00000000000..895c5c59993 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/EquipmentCacheKeyDAO.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import javax.persistence.Version; + +import org.eclipse.persistence.annotations.PrivateOwned; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.EquipmentCacheKey; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.PortCacheKey; + +import java.util.ArrayList; + +@Entity +@Table(name="EquipmentCacheKey") +@NamedQueries({ + @NamedQuery(name="EquipmentCacheKey.findEquipmentById", query="select o from EquipmentCacheKeyDAO o where o.id = :id") +}) +public class EquipmentCacheKeyDAO implements EquipmentCacheKey, java.io.Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "JDOID") + @GeneratedValue(strategy=GenerationType.IDENTITY, generator="PKGen") + @TableGenerator( + name="PKGen", + table="JDO_SEQUENCE_CACHE_KEY", + pkColumnName="ID", + pkColumnValue="jdoid", + valueColumnName="SEQUENCE_VALUE", + initialValue=0, + allocationSize=50) + protected long entityId; + + private String id; + + @OneToMany(cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, + fetch=FetchType.LAZY, + targetEntity= PortCacheKeyDAO.class, + mappedBy="equipment") + @PrivateOwned + @OrderBy("portOrder ASC") + private java.util.List portCacheKeys = new ArrayList<>(); + + @Column(name="JDOVERSION") + @Version + int entityVersion; + + public EquipmentCacheKeyDAO() {} + + @Override + public long getEntityId() { + return entityId; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id ) { + this.id = id; + } + + @Override + public java.util.List getPorts() { + return portCacheKeys; + } + + @Override + public void setPortCacheKeys(ArrayList portCacheKeys) { + this.portCacheKeys = portCacheKeys; + } + + @Override + public void addPortCacheKey(PortCacheKey p) { + getPorts().add(p); + p.setEquipmentCacheKey(this); + } + + @Override + public PortCacheKey removePort(int i) { + PortCacheKey portCacheKey = getPorts().remove(i); + + if (portCacheKey != null) { + portCacheKey.setEquipmentCacheKey(null); + } + + return portCacheKey; + } + + @Override + public String toString() { + return "EquipmentDAO{" + + "entityId=" + entityId + + ", id='" + id + '\'' + + ", entityVersion=" + entityVersion + + '}'; + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/PortCacheKeyDAO.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/PortCacheKeyDAO.java new file mode 100644 index 00000000000..a44fedbc3c9 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/models/jpa/deadlock/diagnostic/weaving/impl/PortCacheKeyDAO.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.EquipmentCacheKey; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.PortCacheKey; + +@Entity +@Table(name="PortCacheKey") +public class PortCacheKeyDAO implements PortCacheKey, java.io.Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "JDOID") + @GeneratedValue(strategy=GenerationType.IDENTITY, generator="PKGen") + private long entityId; + + @ManyToOne(targetEntity= EquipmentCacheKeyDAO.class) + @JoinColumn(name="equipment") + private EquipmentCacheKeyDAO equipment; + + @Column(name="portOrder") + private int portOrder; + + @ManyToOne(targetEntity= EquipmentCacheKeyDAO.class) + @JoinColumn(name="virtualEquipment") + private EquipmentCacheKeyDAO virtualEquipment; + + private String id; + + @Column(name="JDOVERSION") + @Version + int entityVersion; + + @Override + public int getPortCacheKeyOrder() { + return portOrder; + } + + @Override + public void setPortCacheKeyOrder(int portOrder) { + this.portOrder = portOrder; + } + + @Override + public long getEntityId() { + return entityId; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id ) { + this.id = id; + } + + @Override + public EquipmentCacheKey getEquipmentCacheKey() { + return equipment; + } + + @Override + public void setEquipmentCacheKey(EquipmentCacheKey equipmentCacheKey) { + this.equipment = (EquipmentCacheKeyDAO) equipmentCacheKey; + } + + @Override + public EquipmentCacheKey getVirtualEquipmentCacheKey() { + return virtualEquipment; + } + + @Override + public void setVirtualEquipmentCacheKey(EquipmentCacheKey virtualEquipmentCacheKey) { + this.virtualEquipment = (EquipmentCacheKeyDAO) virtualEquipmentCacheKey; + } + + @Override + public String toString() { + return "PortDAO{" + + "entityId=" + entityId + + ", portOrder=" + portOrder + + ", id='" + id + '\'' + + ", entityVersion=" + entityVersion + + '}'; + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockDetectionTest.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockDetectionTest.java new file mode 100644 index 00000000000..d6b9b0cdfcc --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockDetectionTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2024, 2025 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 +package org.eclipse.persistence.testing.tests.jpa.deadlock.diagnostic; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import junit.framework.Test; +import junit.framework.TestSuite; +import org.junit.Assert; + +import org.eclipse.persistence.config.MergeManagerOperationMode; +import org.eclipse.persistence.internal.helper.ConcurrencyUtil; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.jpa.JpaEntityManager; +import org.eclipse.persistence.logging.AbstractSessionLog; + +import org.eclipse.persistence.testing.framework.junit.JUnitTestCase; +import org.eclipse.persistence.testing.framework.junit.JUnitTestCaseHelper; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionDetail; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.DeadLockDiagnosticTableCreator; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.MainThread; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.event.EventProducer; + +public class CacheDeadLockDetectionTest extends JUnitTestCase { + + public static final int RECORDS_NO = 10; + public static final int NO_OF_THREADS = 100; + + SeContainer container; + + EventProducer eventProducer; + + public CacheDeadLockDetectionTest() { + } + + public CacheDeadLockDetectionTest(String name) { + super(name); + setPuName(getPersistenceUnitName()); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.setName("CacheDeadLockDetectionTest"); + suite.addTest(new CacheDeadLockDetectionTest("testSetup")); + suite.addTest(new CacheDeadLockDetectionTest("testVerifyPersistenceAndBasicLogOutput")); + suite.addTest(new CacheDeadLockDetectionTest("testVerifySemaphorePersistenceProperties")); + return suite; + } + + /** + * The setup is done as a test, both to record its failure, and to allow + * execution in the server. + */ + public void testSetup() { + EntityManagerFactory emf = Persistence.createEntityManagerFactory("cachedeadlockdetection-pu", JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + new DeadLockDiagnosticTableCreator().replaceTables(((JpaEntityManager)em).getServerSession()); + clearCache("cachedeadlockdetection-pu"); + try { + em.getTransaction().begin(); + initData(em); + em.getTransaction().commit(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + if (emf.isOpen()) { + emf.close(); + } + } + } + + public void testVerifyPersistenceAndBasicLogOutput() { + initContainer(); + EntityManagerFactory emf = Persistence.createEntityManagerFactory("cachedeadlockdetection-pu", JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + AbstractSession serverSession = ((JpaEntityManager)em).getServerSession(); + LogWrapper logWrapper = new LogWrapper(); + serverSession.setSessionLog(logWrapper); + AbstractSessionLog.setLog(logWrapper); + verifyPersistenceProperties(); + threadExecution(em, emf); + try { + Thread.sleep(7000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + if (emf.isOpen()) { + emf.close(); + } + closeContainer(); + } + //Check if at least one log message is generated + assertTrue(logWrapper.getMessageCount("Stuck thread problem: unique tiny message number") > 0); + assertTrue(logWrapper.getMessageCount("Start full concurrency manager state (massive) dump No") > 0); + } + + public void testVerifySemaphorePersistenceProperties() { + EntityManagerFactory emfSemaphore = Persistence.createEntityManagerFactory("cachedeadlocksemaphore-pu", JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager emSemaphore = emfSemaphore.createEntityManager(); + verifySemaphoreProperties(); + } + + private void initContainer() { + container = SeContainerInitializer.newInstance().initialize(); + eventProducer = container.select(EventProducer.class).get(); + } + + private void closeContainer() { + if (container != null && container.isRunning()) { + container.close(); + } + } + + private void threadExecution(EntityManager em, EntityManagerFactory emf) { + ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(NO_OF_THREADS); + for (int i = 1; i <= NO_OF_THREADS; i++) { + Thread thread = new Thread(new MainThread(container, emf, em)); + thread.setName("MainThread: " + i); + executor.execute(thread); + } + executor.shutdown(); + // Wait for everything to finish. + try { + if (!executor.awaitTermination(10000, TimeUnit.MILLISECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + e.printStackTrace(); + Assert.fail(); + } + } + + private static void initData(EntityManager em) { + for (int i = 1, j = 1; i <= RECORDS_NO; i++, j = j + 2) { + CacheDeadLockDetectionMaster cacheDeadLockDetectionMaster = new CacheDeadLockDetectionMaster(i, "M" + i); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail1 = new CacheDeadLockDetectionDetail(j, "D" + j); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail2 = new CacheDeadLockDetectionDetail(j + 1, "D" + (j + 1)); + cacheDeadLockDetectionDetail1.setMaster(cacheDeadLockDetectionMaster); + cacheDeadLockDetectionDetail2.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail1); + em.persist(cacheDeadLockDetectionDetail2); + } + } + + private void verifyPersistenceProperties() { + Assert.assertEquals(1L, ConcurrencyUtil.SINGLETON.getAcquireWaitTime()); + Assert.assertEquals(2L, ConcurrencyUtil.SINGLETON.getMaxAllowedSleepTime()); + Assert.assertEquals(800L, ConcurrencyUtil.SINGLETON.getMaxAllowedFrequencyToProduceTinyDumpLogMessage()); + Assert.assertEquals(1000L, ConcurrencyUtil.SINGLETON.getMaxAllowedFrequencyToProduceMassiveDumpLogMessage()); + Assert.assertEquals(5L, ConcurrencyUtil.SINGLETON.getBuildObjectCompleteWaitTime()); + //MergeManagerOperationMode.ORIGIN is default value not explicitly specified in persistence.xml + Assert.assertEquals(MergeManagerOperationMode.ORIGIN, ConcurrencyUtil.SINGLETON.getConcurrencyManagerAllowGetCacheKeyForMergeMode()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowTakingStackTraceDuringReadLockAcquisition()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowConcurrencyExceptionToBeFiredUp()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired()); + } + + private void verifySemaphoreProperties() { + Assert.assertEquals(1L, ConcurrencyUtil.SINGLETON.getAcquireWaitTime()); + Assert.assertEquals(2L, ConcurrencyUtil.SINGLETON.getMaxAllowedSleepTime()); + Assert.assertEquals(1000L, ConcurrencyUtil.SINGLETON.getMaxAllowedFrequencyToProduceTinyDumpLogMessage()); + Assert.assertEquals(2000L, ConcurrencyUtil.SINGLETON.getMaxAllowedFrequencyToProduceMassiveDumpLogMessage()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowTakingStackTraceDuringReadLockAcquisition()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowConcurrencyExceptionToBeFiredUp()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isAllowInterruptedExceptionFired()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isUseSemaphoreInObjectBuilder()); + Assert.assertEquals(5L, ConcurrencyUtil.SINGLETON.getNoOfThreadsAllowedToObjectBuildInParallel()); + Assert.assertTrue(ConcurrencyUtil.SINGLETON.isUseSemaphoreToLimitConcurrencyOnWriteLockManagerAcquireRequiredLocks()); + Assert.assertEquals(6L, ConcurrencyUtil.SINGLETON.getNoOfThreadsAllowedToDoWriteLockManagerAcquireRequiredLocksInParallel()); + Assert.assertEquals(7L, ConcurrencyUtil.SINGLETON.getConcurrencySemaphoreMaxTimePermit()); + Assert.assertEquals(8L, ConcurrencyUtil.SINGLETON.getConcurrencySemaphoreLogTimeout()); + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockManagersTest.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockManagersTest.java new file mode 100644 index 00000000000..f7e1a8c9364 --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheDeadLockManagersTest.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2024, 2025 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 +package org.eclipse.persistence.testing.tests.jpa.deadlock.diagnostic; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import javax.persistence.Query; +import junit.framework.Test; +import junit.framework.TestSuite; +import org.eclipse.persistence.config.MergeManagerOperationMode; +import org.eclipse.persistence.descriptors.ClassDescriptor; +import org.eclipse.persistence.internal.helper.ConcurrencyUtil; +import org.eclipse.persistence.internal.helper.WriteLockManager; +import org.eclipse.persistence.internal.identitymaps.CacheKey; +import org.eclipse.persistence.internal.jpa.EJBQueryImpl; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.internal.sessions.IdentityMapAccessor; +import org.eclipse.persistence.jpa.JpaEntityManager; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.queries.ObjectBuildingQuery; +import org.eclipse.persistence.testing.framework.junit.JUnitTestCase; +import org.eclipse.persistence.testing.framework.junit.JUnitTestCaseHelper; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionDetail; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.CacheDeadLockDetectionMaster; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.DeadLockDiagnosticTableCreator; +import org.junit.Assert; + +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class CacheDeadLockManagersTest extends JUnitTestCase { + + public static final int RECORDS_NO = 10; + + public CacheDeadLockManagersTest() { + } + + public CacheDeadLockManagersTest(String name) { + super(name); + setPuName(getPersistenceUnitName()); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.setName("CacheDeadLockDetectionTest"); + suite.addTest(new CacheDeadLockManagersTest("testSetup")); + suite.addTest(new CacheDeadLockManagersTest("testWriteLockManagerAcquireLocksForClone")); + suite.addTest(new CacheDeadLockManagersTest("testAbstractSessionCacheKeyFromTargetSessionForMerge")); + suite.addTest(new CacheDeadLockManagersTest("testAbstractSessionCacheKeyFromTargetSessionForMergeWithLockedCacheKey")); + return suite; + } + + /** + * The setup is done as a test, both to record its failure, and to allow + * execution in the server. + */ + public void testSetup() { + final String PU_NAME = "cachedeadlockdetection-pu"; + + EntityManagerFactory emf = Persistence.createEntityManagerFactory(PU_NAME, JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + new DeadLockDiagnosticTableCreator().replaceTables(((JpaEntityManager)em).getServerSession()); + clearCache(PU_NAME); + try { + em.getTransaction().begin(); + initData(em); + em.getTransaction().commit(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + if (emf.isOpen()) { + emf.close(); + } + } + } + + public void testWriteLockManagerAcquireLocksForClone() { + final String PU_NAME = "cachedeadlockdetection-pu"; + + EntityManagerFactory emf = Persistence.createEntityManagerFactory(PU_NAME, JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + AbstractSession serverSession = ((JpaEntityManager)em).getServerSession(); + LogWrapper logWrapper = new LogWrapper(); + serverSession.setSessionLog(logWrapper); + AbstractSessionLog.setLog(logWrapper); + try { + Long primaryKey = Long.valueOf(1L); + Query query = em.createQuery("SELECT c FROM CacheDeadLockDetectionMaster c WHERE c.id = :id"); + query.setParameter("id", primaryKey); + CacheDeadLockDetectionMaster result = (CacheDeadLockDetectionMaster) query.getSingleResult(); + ClassDescriptor descriptor = serverSession.getDescriptor(CacheDeadLockDetectionMaster.class); + CacheKey cacheKey = serverSession.retrieveCacheKey(primaryKey, descriptor, null, (ObjectBuildingQuery) ((EJBQueryImpl)query).getDatabaseQuery()); + cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); + //Tricky part with some negative value to simulate deadlock detection. + ConcurrencyUtil.SINGLETON.setMaxAllowedSleepTime(-1000000); + ConcurrencyUtil.SINGLETON.setAllowConcurrencyExceptionToBeFiredUp(true); + WriteLockManager writeLockManager = new WriteLockManager(); + Map map = writeLockManager.acquireLocksForClone(result, descriptor, cacheKey, serverSession); + } catch (Exception e) { + assertEquals(2, logWrapper.getMessageCount(WriteLockManager.class.getName() + ".acquireLocksForClone")); + } finally { + if (em != null) { + if (em.isOpen()) { + em.close(); + } + } + } + } + + public void testAbstractSessionCacheKeyFromTargetSessionForMerge() { + final String PU_NAME = "cachedeadlockdetection-loopwait-pu"; + final long MASTER_ID = 1000L; + final long DETAIL_ID_1 = 1111L; + final long DETAIL_ID_2 = 1112L; + + EntityManagerFactory emf = Persistence.createEntityManagerFactory(PU_NAME, JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + assertEquals(MergeManagerOperationMode.WAITLOOP, ConcurrencyUtil.SINGLETON.getConcurrencyManagerAllowGetCacheKeyForMergeMode()); + clearCache(PU_NAME); + try { + em.getTransaction().begin(); + CacheDeadLockDetectionMaster cacheDeadLockDetectionMaster = new CacheDeadLockDetectionMaster(MASTER_ID, "M1000"); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail1 = new CacheDeadLockDetectionDetail(DETAIL_ID_1, "D1111"); + cacheDeadLockDetectionDetail1.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail1); + em.getTransaction().commit(); + + IdentityMapAccessor identityMapAccessor = (IdentityMapAccessor) ((JpaEntityManager)em).getServerSession().getIdentityMapAccessor(); + CacheKey cacheKey = identityMapAccessor.getCacheKeyForObject(cacheDeadLockDetectionMaster); + Semaphore semaphore = new Semaphore(1); + semaphore.acquire(); + Object backupObject = cacheKey.getObject(); + //Lock existing cache key by another thread + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + cacheKey.acquire(true); + cacheKey.setObject(null); + semaphore.acquire(); + cacheKey.setObject(backupObject); + cacheKey.release(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + thread.start(); + + em.getTransaction().begin(); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail2 = new CacheDeadLockDetectionDetail(DETAIL_ID_2, "D1112"); + cacheDeadLockDetectionDetail2.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail2); + //Release semaphore which block second thread and unlock cacheKey to allow process next piece of code without any issue. + semaphore.release(); + em.getTransaction().commit(); + CacheDeadLockDetectionDetail findResult = em.find(CacheDeadLockDetectionDetail.class, DETAIL_ID_2); + assertEquals(DETAIL_ID_2, findResult.getId()); + assertEquals("D1112", findResult.getName()); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + if (emf.isOpen()) { + emf.close(); + } + } + } + + public void testAbstractSessionCacheKeyFromTargetSessionForMergeWithLockedCacheKey() { + final String PU_NAME = "cachedeadlockdetection-loopwait-pu"; + final long MASTER_ID = 2000L; + final long DETAIL_ID_1 = 2111L; + final long DETAIL_ID_2 = 2222L; + + EntityManagerFactory emf = Persistence.createEntityManagerFactory(PU_NAME, JUnitTestCaseHelper.getDatabaseProperties()); + EntityManager em = emf.createEntityManager(); + AbstractSession serverSession = ((JpaEntityManager)em).getServerSession(); + LogWrapper logWrapper = new LogWrapper(); + serverSession.setSessionLog(logWrapper); + AbstractSessionLog.setLog(logWrapper); + assertEquals(MergeManagerOperationMode.WAITLOOP, ConcurrencyUtil.SINGLETON.getConcurrencyManagerAllowGetCacheKeyForMergeMode()); + clearCache(PU_NAME); + try { + em.getTransaction().begin(); + CacheDeadLockDetectionMaster cacheDeadLockDetectionMaster = new CacheDeadLockDetectionMaster(MASTER_ID, "M2000"); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail1 = new CacheDeadLockDetectionDetail(DETAIL_ID_1, "D2111"); + cacheDeadLockDetectionDetail1.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail1); + em.getTransaction().commit(); + + IdentityMapAccessor identityMapAccessor = (IdentityMapAccessor) ((JpaEntityManager)em).getServerSession().getIdentityMapAccessor(); + CacheKey cacheKey = identityMapAccessor.getCacheKeyForObject(cacheDeadLockDetectionMaster); + Semaphore semaphore = new Semaphore(1); + semaphore.acquire(); + Object backupObject = cacheKey.getObject(); + //Lock existing cache key by another thread + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + cacheKey.acquire(true); + cacheKey.setObject(null); + semaphore.acquire(); + cacheKey.setObject(backupObject); + cacheKey.release(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + thread.start(); + + em.getTransaction().begin(); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail2 = new CacheDeadLockDetectionDetail(DETAIL_ID_2, "D2222"); + cacheDeadLockDetectionDetail2.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail2); + //Sleep is there to simulate, that main thread is doing some more time consuming operations and allow dead lock detection -> log messages. + Thread.sleep(1000); + em.getTransaction().commit(); + CacheDeadLockDetectionDetail findResult = em.find(CacheDeadLockDetectionDetail.class, DETAIL_ID_2); + assertEquals(DETAIL_ID_2, findResult.getId()); + assertEquals("D2222", findResult.getName()); + assertEquals(1, logWrapper.getMessageCount("Page 08 start")); + assertEquals(1, logWrapper.getMessageCount("competing thread: " + thread)); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + if (emf.isOpen()) { + emf.close(); + } + } + } + + private static void initData(EntityManager em) { + for (int i = 1, j = 1; i <= RECORDS_NO; i++, j = j + 2) { + CacheDeadLockDetectionMaster cacheDeadLockDetectionMaster = new CacheDeadLockDetectionMaster(i, "M" + i); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail1 = new CacheDeadLockDetectionDetail(j, "D" + j); + CacheDeadLockDetectionDetail cacheDeadLockDetectionDetail2 = new CacheDeadLockDetectionDetail(j + 1, "D" + (j + 1)); + cacheDeadLockDetectionDetail1.setMaster(cacheDeadLockDetectionMaster); + cacheDeadLockDetectionDetail2.setMaster(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionMaster); + em.persist(cacheDeadLockDetectionDetail1); + em.persist(cacheDeadLockDetectionDetail2); + } + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheKeyNullKeyTest.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheKeyNullKeyTest.java new file mode 100644 index 00000000000..23996e18b7f --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/CacheKeyNullKeyTest.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2025 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 +package org.eclipse.persistence.testing.tests.jpa.deadlock.diagnostic; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Query; + +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.jpa.JpaEntityManager; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.testing.framework.junit.JUnitTestCase; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.EquipmentCacheKey; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.PortCacheKey; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl.EquipmentCacheKeyDAO; +import org.eclipse.persistence.testing.models.jpa.deadlock.diagnostic.weaving.impl.PortCacheKeyDAO; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * INTERNAL: + * This is tests which ensures, that {@linkplain org.eclipse.persistence.internal.helper.ReadLockManager} doesn't store in + * ${@code Vector readLocks} {@linkplain org.eclipse.persistence.internal.identitymaps.CacheKey} instance with {@code key == null}. + * Test is based on log capture handled by {@linkplain org.eclipse.persistence.testing.tests.jpa.deadlock.diagnostic.LogWrapper}. + * Required conditions which led into NPE in {@linkplain org.eclipse.persistence.internal.helper.ReadLockManager} before fix were: + *

    + *
  • Entity ID is generated by {@code @GeneratedValue}
  • + *
  • L2 cache is enabled
  • + *
  • Weaving is enabled
  • + *
  • Entity model and sequence of operations described in the test bellow.
  • + *
+ */ +public class CacheKeyNullKeyTest extends JUnitTestCase { + + public CacheKeyNullKeyTest() { + } + + public CacheKeyNullKeyTest(String name) { + super(name); + } + + @Override + public String getPersistenceUnitName() { + return "cachekeynullkey-pu"; + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.setName("CacheKeyNullKeyTest"); + suite.addTest(new CacheKeyNullKeyTest("testCacheKeyNullKey")); + return suite; + } + + public void testCacheKeyNullKey() throws InterruptedException { + EntityManagerFactory factory = getEntityManagerFactory(); + + // Clean up first + cleanupEquipmentAndPorts(factory); + + // Create a piece equipment with one port. + createEquipment(factory); + + // Add two ports to the equipment + addPorts(factory); + + // Fetch the equipment and validate there is no null elements in + // the ArrayList of Port. + verifyPorts(factory); + } + + protected void cleanupEquipmentAndPorts(EntityManagerFactory factory) { + EntityManager em = null; + + try { + em = factory.createEntityManager(); + beginTransaction(em); + + em.createQuery("DELETE FROM PortCacheKeyDAO").executeUpdate(); + em.createQuery("DELETE FROM EquipmentCacheKeyDAO").executeUpdate(); + + commitTransaction(em); + } catch (RuntimeException e) { + if (isTransactionActive(em)) { + rollbackTransaction(em); + } + throw e; + } finally { + if (em != null) { + em.close(); + } + } + } + + protected void createEquipment(EntityManagerFactory factory) { + EntityManager em = null; + + try { + em = factory.createEntityManager(); + + beginTransaction(em); + + EquipmentCacheKey eq = new EquipmentCacheKeyDAO(); + eq.setId("eq"); + + PortCacheKey portCacheKey = new PortCacheKeyDAO(); + portCacheKey.setId("p1"); + portCacheKey.setPortCacheKeyOrder(0); + + eq.addPortCacheKey(portCacheKey); + + em.persist(eq); + commitTransaction(em); + } catch (Exception e) { + if (em != null && isTransactionActive(em)) { + rollbackTransaction(em); + } + + fail("En error occurred creating new equipment: " + e.getMessage()); + } finally { + if (em != null) { + em.close(); + } + } + } + + protected void addPorts(EntityManagerFactory factory) { + EntityManager em = null; + try { + em = factory.createEntityManager(); + + beginTransaction(em); + Query query = em.createNamedQuery("EquipmentCacheKey.findEquipmentById"); + query.setParameter("id", "eq"); + EquipmentCacheKey eq = (EquipmentCacheKey) query.getResultList().get(0); + commitTransaction(em); + + em = factory.createEntityManager(); + beginTransaction(em); + eq = em.merge(eq); + + PortCacheKey portCacheKey = new PortCacheKeyDAO(); + portCacheKey.setId("p2"); + portCacheKey.setPortCacheKeyOrder(1); + eq.addPortCacheKey(portCacheKey); + + portCacheKey = new PortCacheKeyDAO(); + portCacheKey.setId("p3"); + portCacheKey.setPortCacheKeyOrder(2); + eq.addPortCacheKey(portCacheKey); + + eq = em.merge(eq); + commitTransaction(em); + } catch (Exception e) { + if (isTransactionActive(em)) { + rollbackTransaction(em); + } + + fail("En error occurred adding new ports: " + e.getMessage()); + } finally { + if (em != null) { + em.close(); + } + } + } + + protected void verifyPorts(EntityManagerFactory factory) { + EntityManager em = null; + LogWrapper logWrapper = new LogWrapper(); + + try { + em = factory.createEntityManager(); + AbstractSession serverSession = ((JpaEntityManager)em).getServerSession(); + serverSession.setSessionLog(logWrapper); + AbstractSessionLog.setLog(logWrapper); + beginTransaction(em); + Query query = em.createNamedQuery("EquipmentCacheKey.findEquipmentById"); + query.setParameter("id", "eq"); + EquipmentCacheKey eq = (EquipmentCacheKey) query.getResultList().get(0); + commitTransaction(em); + + for (PortCacheKey portCacheKey : eq.getPorts()) { + if (portCacheKey == null) { + fail("A null PORT was found in the collection of ports."); + } + } + } catch (Exception e) { + e.printStackTrace(); + if (isTransactionActive(em)) { + rollbackTransaction(em); + } + + fail("En error occurred fetching the results to verify: " + e.getMessage()); + } finally { + if (em != null) { + em.close(); + } + } + //Check that org.eclipse.persistence.internal.helper.ReadLockManager doesn't keep any CacheKey with NULL key. + assertEquals(0, logWrapper.getMessageCount("locked by cache read lock manager has null primary key")); + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/LogWrapper.java b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/LogWrapper.java new file mode 100644 index 00000000000..fb9415d4f7a --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/src/org/eclipse/persistence/testing/tests/jpa/deadlock/diagnostic/LogWrapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, 2025 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.testing.tests.jpa.deadlock.diagnostic; + +import org.eclipse.persistence.logging.DefaultSessionLog; +import org.eclipse.persistence.logging.SessionLogEntry; + +import java.io.StringWriter; +import java.util.regex.Pattern; + +//Simple log handler which counts selected messages. +public class LogWrapper extends DefaultSessionLog { + + private StringWriter sw = new StringWriter(); + + public LogWrapper() { + super.setWriter(sw); + } + + + @Override + public synchronized void log(SessionLogEntry entry) { + super.log(entry); + } + + @Override + public boolean shouldLog(int level, String category) { + return true; + } + + public long getMessageCount(String checkedMessage) { + String logOutput = sw.toString(); + return Pattern.compile(checkedMessage, Pattern.LITERAL) + .matcher(logOutput) + .results() + .count(); + } +} diff --git a/jpa/eclipselink.jpa.test.deadlock/test.properties b/jpa/eclipselink.jpa.test.deadlock/test.properties new file mode 100644 index 00000000000..d6b1ee56f1e --- /dev/null +++ b/jpa/eclipselink.jpa.test.deadlock/test.properties @@ -0,0 +1,24 @@ +# +# Copyright (c) 2025 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 +# + +# Testing environment properties. + +# Link to dependencies not in SVN, assumes default location, if you store elsewhere you need to edit these. +extensions.depend.dir=../../../../extension.lib.external + +# Enter Database Connection Info +jdbc.driver.jar=${extensions.depend.dir}/mysql-connector-java-5.1.18-bin.jar +db.driver=com.mysql.cj.jdbc.Driver +db.url=jdbc:mysql://localhost/user +db.user=user +db.pwd=password +db.platform=org.eclipse.persistence.platform.database.MySQLPlatform diff --git a/jpa/eclipselink.jpa.test.jse/antbuild.properties b/jpa/eclipselink.jpa.test.jse/antbuild.properties index 7038a5ee0c7..a1234bf56ba 100644 --- a/jpa/eclipselink.jpa.test.jse/antbuild.properties +++ b/jpa/eclipselink.jpa.test.jse/antbuild.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2025 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 @@ -45,14 +45,3 @@ annotation-api.jar=jakarta.annotation-api.jar transaction.jar=jakarta.transaction-api.jar validation.jar=jakarta.validation-api.jar persistence22.jar=jakarta.persistence_2.2.3.jar -cdi-api.jar=jakarta.enterprise.cdi-api.jar -inject.jar=jakarta.inject.jar -interceptor-api.jar=jakarta.interceptor-api.jar -el-api.jar=jakarta.el-api.jar -jboss-classfilewriter.jar=jboss-classfilewriter.jar -jboss-weld-api.jar=weld-api.jar -jboss-weld-spi.jar=weld-spi.jar -jboss-weld-environment-common.jar=weld-environment-common.jar -jboss-weld-se-core.jar=weld-se-core.jar -jboss-weld-core-impl.jar=weld-core-impl.jar -jboss-logging.jar=jboss-logging.jar diff --git a/jpa/eclipselink.jpa.test.jse/antbuild.xml b/jpa/eclipselink.jpa.test.jse/antbuild.xml index fa287e7d5bc..02ba1c9903f 100644 --- a/jpa/eclipselink.jpa.test.jse/antbuild.xml +++ b/jpa/eclipselink.jpa.test.jse/antbuild.xml @@ -1,6 +1,6 @@