This is an automated email from the ASF dual-hosted git repository.
mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new 8911dbe619 Fixed: Timing-dependent deadlock-retry test in service
engine (OFBIZ-13585)
8911dbe619 is described below
commit 8911dbe6193cc712c544be0414508ddde7054bb9
Author: Mridul Pathak <[email protected]>
AuthorDate: Mon Sep 14 14:13:10 2026 +0530
Fixed: Timing-dependent deadlock-retry test in service engine (OFBIZ-13585)
testServiceDeadLockRetryThreadA/ThreadB relied on a fixed Thread.sleep(100)
to force two concurrent threads into a circular-wait deadlock, which is
inherently timing-dependent, and a database version change shifted internal
timings enough that the two threads no longer reliably collided, causing
service-dead-lock-retry-assert-data to fail intermittently.
Replaced the sleep with two CountDownLatch(1) instances, one per thread:
each thread signals its own latch once it holds its lock, then waits on the
other thread's latch before attempting the second lock, guaranteeing the
circular wait deterministically. A latch is used rather than a barrier-style
rendezvous because the service engine's own deadlock-retry loop retries only
the losing thread's service call in isolation after a real deadlock, not both
threads together, and a primitive r [...]
Verified with 20 repeated runs of the service component test suite.
---
.../service/test/ServiceEngineTestServices.java | 37 +++++++++++++++++-----
1 file changed, 29 insertions(+), 8 deletions(-)
diff --git
a/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTestServices.java
b/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTestServices.java
index 8ca661fc5b..bd2b174c55 100644
---
a/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTestServices.java
+++
b/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTestServices.java
@@ -22,6 +22,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilMisc;
@@ -42,10 +44,19 @@ public class ServiceEngineTestServices {
private static final String MODULE =
ServiceEngineTestServices.class.getName();
private static final String RESOURCE = "ServiceErrorUiLabels";
+ // Real synchronization for the deadlock-retry test below: each thread
signals its own latch and
+ // waits on the other's, guaranteeing the circular wait instead of relying
on a fixed sleep. A
+ // latch (not a barrier) is required because a retried thread must not
wait on a rendezvous again
+ // - a latch already at zero returns immediately, a barrier would block
for a partner that already left.
+ private static volatile CountDownLatch deadLockRetryLatchA;
+ private static volatile CountDownLatch deadLockRetryLatchB;
+
public static Map<String, Object> testServiceDeadLockRetry(DispatchContext
dctx, Map<String, ? extends Object> context) {
Locale locale = (Locale) context.get("locale");
LocalDispatcher dispatcher = dctx.getDispatcher();
try {
+ deadLockRetryLatchA = new CountDownLatch(1);
+ deadLockRetryLatchB = new CountDownLatch(1);
// NOTE using persist=false so that the lock retry will have to
fix the problem instead of the job poller picking it up again
GenericResultWaiter threadAWaiter =
dispatcher.runAsyncWait("testServiceDeadLockRetryThreadA", null, false);
GenericResultWaiter threadBWaiter =
dispatcher.runAsyncWait("testServiceDeadLockRetryThreadB", null, false);
@@ -82,11 +93,16 @@ public class ServiceEngineTestServices {
testingTypeA.set("description", "New description for SVCLRT_A");
testingTypeA.store();
- // wait at least long enough for the other method to have locked
resource B
- Debug.logInfo("In testServiceDeadLockRetryThreadA just updated
SVCLRT_A, beginning wait", MODULE);
- Thread.sleep(100);
+ // signal SVCLRT_A is locked, then wait for thread B to lock
SVCLRT_B, guaranteeing the circular wait
+ Debug.logInfo("In testServiceDeadLockRetryThreadA just updated
SVCLRT_A, waiting for thread B", MODULE);
+ deadLockRetryLatchA.countDown();
+ if (!deadLockRetryLatchB.await(30, TimeUnit.SECONDS)) {
+ Debug.logError("Timed out waiting for thread B to lock
SVCLRT_B", MODULE);
+ return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ServiceTestEntityEngineWaitInterruptedExceptionThreadA",
+ UtilMisc.toMap("errorString", "Timed out waiting for
thread B to lock SVCLRT_B"), locale));
+ }
- Debug.logInfo("In testServiceDeadLockRetryThreadA done with wait,
updating SVCLRT_B", MODULE);
+ Debug.logInfo("In testServiceDeadLockRetryThreadA done waiting,
updating SVCLRT_B", MODULE);
GenericValue testingTypeB =
EntityQuery.use(delegator).from("TestingType").where("testingTypeId",
"SVCLRT_B").queryOne();
testingTypeB.set("description", "New description for SVCLRT_B");
testingTypeB.store();
@@ -118,11 +134,16 @@ public class ServiceEngineTestServices {
testingTypeB.set("description", "New description for SVCLRT_B");
testingTypeB.store();
- // wait at least long enough for the other method to have locked
resource B
- Debug.logInfo("In testServiceDeadLockRetryThreadB just updated
SVCLRT_B, beginning wait", MODULE);
- Thread.sleep(100);
+ // signal SVCLRT_B is locked, then wait for thread A to lock
SVCLRT_A, guaranteeing the circular wait
+ Debug.logInfo("In testServiceDeadLockRetryThreadB just updated
SVCLRT_B, waiting for thread A", MODULE);
+ deadLockRetryLatchB.countDown();
+ if (!deadLockRetryLatchA.await(30, TimeUnit.SECONDS)) {
+ Debug.logError("Timed out waiting for thread A to lock
SVCLRT_A", MODULE);
+ return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ServiceTestEntityEngineWaitInterruptedExceptionThreadB",
+ UtilMisc.toMap("errorString", "Timed out waiting for
thread A to lock SVCLRT_A"), locale));
+ }
- Debug.logInfo("In testServiceDeadLockRetryThreadB done with wait,
updating SVCLRT_A", MODULE);
+ Debug.logInfo("In testServiceDeadLockRetryThreadB done waiting,
updating SVCLRT_A", MODULE);
GenericValue testingTypeA =
EntityQuery.use(delegator).from("TestingType").where("testingTypeId",
"SVCLRT_A").queryOne();
testingTypeA.set("description", "New description for SVCLRT_A");
testingTypeA.store();