From 91a92e891caf463907380d81fd4b357866c62b83 Mon Sep 17 00:00:00 2001 From: Dan Plyukhin Date: Wed, 22 Jul 2026 12:52:19 -0400 Subject: [PATCH 1/4] Add tests to reproduce virtual thread starvation --- .github/workflows/ci.yml | 2 +- temporal-sdk/build.gradle | 20 +++++++- .../sync/PotentialDeadlockException.java | 17 +++++++ .../internal/sync/WorkflowThreadContext.java | 13 ++--- .../sync/DeterministicRunnerTest.java | 33 +++++++++++++ .../ThreadStarvationVirtualThreadTest.java | 49 +++++++++++++++++++ 6 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e473036051..18adf367fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: USER: unittest TEMPORAL_SERVICE_ADDRESS: localhost:7233 USE_EXTERNAL_SERVICE: true - run: ./gradlew --no-daemon :temporal-sdk:virtualThreadTests -x spotlessCheck -x spotlessApply -x spotlessJava -PtestJavaVersion=21 + run: ./gradlew --no-daemon :temporal-sdk:virtualThreadTests :temporal-sdk:virtualThreadStarvationTests -x spotlessCheck -x spotlessApply -x spotlessJava -PtestJavaVersion=21 - name: Publish Test Report uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6 diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index d7b090e5b5..9534e1e6ff 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -256,6 +256,23 @@ testing { } } + virtualThreadStarvationTests(JvmTestSuite) { + targets { + all { + testTask.configure { + jvmArgs '-Djdk.virtualThreadScheduler.parallelism=1', + '-Djdk.virtualThreadScheduler.maxPoolSize=1' + if (project.hasProperty("testJavaVersion")) { + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(project.property("testJavaVersion") as int) + } + } + shouldRunAfter(virtualThreadTests) + } + } + } + } + // Run the same test as the normal test task with virtual threads testsWithVirtualThreads(JvmTestSuite) { // Use the same source and resources as the main test set @@ -287,4 +304,5 @@ testing { tasks.named('check') { dependsOn(testing.suites.jackson3Tests) dependsOn(testing.suites.virtualThreadTests) -} \ No newline at end of file + dependsOn(testing.suites.virtualThreadStarvationTests) +} diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java index 4f907783b0..e696b0fd6d 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java @@ -43,6 +43,23 @@ public class PotentialDeadlockException extends RuntimeException { this.detectionTimestamp = detectionTimestamp; } + /** + * @param workflowThreadContext context of the thread that is in a potential deadlock state + * @param detectionTimestamp a timestamp the deadlock was detected + * @param timeoutMillis configured deadlock detection timeout in milliseconds + */ + PotentialDeadlockException( + WorkflowThreadContext workflowThreadContext, + long detectionTimestamp, + long timeoutMillis) { + super( + "[TMPRL1101] Potential deadlock detected. Workflow thread could not start executing within " + + formatTimeout(timeoutMillis) + + "."); + this.workflowThreadContext = workflowThreadContext; + this.detectionTimestamp = detectionTimestamp; + } + private static String formatTimeout(long timeoutMillis) { if (timeoutMillis % 1000 == 0) { return timeoutMillis / 1000 + "s"; diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java index 49dd9f9468..0148297169 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java @@ -10,12 +10,8 @@ import java.util.concurrent.locks.Lock; import java.util.function.Supplier; import javax.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; class WorkflowThreadContext { - private static final Logger log = LoggerFactory.getLogger(WorkflowThreadContext.class); - // Shared runner lock private final Lock runnerLock; private final WorkflowThreadScheduler scheduler; @@ -241,13 +237,10 @@ public boolean runUntilBlocked(long deadlockDetectionTimeoutMs) { throw new PotentialDeadlockException( currentThread.getName(), this, detectionTimestamp, deadlockDetectionTimeoutMs); } else { - // This should never happen. - // We clear currentThread only after setting the status to DONE. - // And we check for it by the status condition check after waking up on the condition - // and acquiring the lock back - log.warn("Illegal State: WorkflowThreadContext has no currentThread in {} state", status); + // We clear currentThread only after setting the status to DONE, so this case should + // only happen if the WorkflowThread is starving. throw new PotentialDeadlockException( - "UnknownThread", this, detectionTimestamp, deadlockDetectionTimeoutMs); + this, detectionTimestamp, deadlockDetectionTimeoutMs); } } Preconditions.checkState( diff --git a/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java b/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java index ba3a0eb332..02f306fd7d 100644 --- a/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java +++ b/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java @@ -34,7 +34,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -820,6 +822,37 @@ public void testRejectedExecutionError() { } } + @Test + public void testThreadStarvationBeforeWorkflowThreadStarts() throws InterruptedException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + CountDownLatch executorThreadOccupied = new CountDownLatch(1); + CountDownLatch releaseExecutorThread = new CountDownLatch(1); + executor.submit( + () -> { + executorThreadOccupied.countDown(); + releaseExecutorThread.await(); + return null; + }); + executorThreadOccupied.await(); + + DeterministicRunner runner = + new DeterministicRunnerImpl( + executor::submit, + DummySyncWorkflowContext.newDummySyncWorkflowContext(), + () -> fail("workflow code should not start while the executor thread is occupied")); + + try { + PotentialDeadlockException e = + Assert.assertThrows( + PotentialDeadlockException.class, () -> runner.runUntilAllBlocked(10)); + assertTrue(e.getMessage().contains("could not start executing within 10ms")); + } finally { + executor.shutdownNow(); + releaseExecutorThread.countDown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } + } + @Test public void testCloseBlockedUntilDone() throws InterruptedException { final int THREAD_SLEEP_MS = 2000; diff --git a/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java b/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java new file mode 100644 index 0000000000..a9baa4fec5 --- /dev/null +++ b/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java @@ -0,0 +1,49 @@ +package io.temporal.internal.sync; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Test; + +public class ThreadStarvationVirtualThreadTest { + + @Test(timeout = 10_000) + public void workflowThreadDoesNotStartWhenAllCarriersAreBusy() throws Exception { + CountDownLatch carrierOccupied = new CountDownLatch(1); + AtomicBoolean releaseCarrier = new AtomicBoolean(); + Thread carrierBlocker = + Thread.ofVirtual() + .name("carrier-blocker") + .start( + () -> { + carrierOccupied.countDown(); + while (!releaseCarrier.get()) { + Thread.onSpinWait(); + } + }); + carrierOccupied.await(); + + ExecutorService workflowThreadExecutor = Executors.newVirtualThreadPerTaskExecutor(); + DeterministicRunner runner = + DeterministicRunner.newRunner( + workflowThreadExecutor::submit, + DummySyncWorkflowContext.newDummySyncWorkflowContext(), + () -> { + throw new AssertionError("workflow code should not start while the carrier is busy"); + }); + + try { + PotentialDeadlockException e = + assertThrows(PotentialDeadlockException.class, () -> runner.runUntilAllBlocked(100)); + assertTrue(e.getMessage().contains("could not start executing within 100ms")); + } finally { + workflowThreadExecutor.shutdownNow(); + releaseCarrier.set(true); + carrierBlocker.join(); + } + } +} From 4d08834b5c5ebc766881c1d6e64ce28689432efc Mon Sep 17 00:00:00 2001 From: Dan Plyukhin Date: Thu, 23 Jul 2026 17:38:06 -0400 Subject: [PATCH 2/4] Update docs for deadlock detection timeout --- .../java/io/temporal/internal/sync/DeterministicRunner.java | 4 ++-- .../main/java/io/temporal/internal/sync/WorkflowThread.java | 4 ++-- .../io/temporal/internal/sync/WorkflowThreadContext.java | 4 ++-- .../src/main/java/io/temporal/worker/WorkerOptions.java | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java index efafe230af..2147b518d4 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java @@ -52,8 +52,8 @@ static DeterministicRunner newRunner( * completed or blocked. * * @throws Throwable if one of the threads didn't handle an exception. - * @param deadlockDetectionTimeout the maximum time in milliseconds a thread can run without - * calling yield. + * @param deadlockDetectionTimeout the maximum time in milliseconds after a thread is + * scheduled until it yields or completes. */ void runUntilAllBlocked(long deadlockDetectionTimeout); diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java index fecd98e74f..688abfce7b 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java @@ -65,8 +65,8 @@ static WorkflowThread newThread(Runnable runnable, boolean detached, String name SyncWorkflowContext getWorkflowContext(); /** - * @param deadlockDetectionTimeoutMs maximum time in milliseconds the thread can run before - * calling yield. + * @param deadlockDetectionTimeoutMs the maximum time in milliseconds after a thread is + * scheduled until it yields or completes. * @return true if coroutine made some progress. */ boolean runUntilBlocked(long deadlockDetectionTimeoutMs); diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java index 0148297169..446db368c9 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThreadContext.java @@ -210,8 +210,8 @@ public String getYieldReason() { } /** - * @param deadlockDetectionTimeoutMs maximum time in milliseconds the thread can run before - * calling yield. Discarded if {@code TEMPORAL_DEBUG} env variable is set. + * @param deadlockDetectionTimeoutMs the maximum time in milliseconds after a thread is scheduled + * until it yields or completes. Discarded if {@code TEMPORAL_DEBUG} env variable is set. * @return true if thread made some progress. Which is await was unblocked and some code after it * * was executed. */ diff --git a/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java b/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java index 3da57ef81c..56db2bc187 100644 --- a/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java +++ b/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java @@ -327,9 +327,9 @@ public Builder setLocalActivityWorkerOnly(boolean localActivityWorkerOnly) { /** * @param defaultDeadlockDetectionTimeoutMs time period in ms that will be used to detect * workflows deadlock. Default is 1000ms, which is chosen if set to zero. - *

Specifies an amount of time in milliseconds that workflow tasks are allowed to execute - * without interruption. If workflow task runs longer than specified interval without - * yielding (like calling an Activity), it will fail automatically. + *

Specifies a time interval in milliseconds within which a workflow task must + * yield (like calling an Activity) or complete. If a workflow task runs longer than + * the specified interval or takes too long to begin running, it will fail automatically. * @return {@code this} * @see io.temporal.internal.sync.PotentialDeadlockException */ From b29235662751dbb446f0de85452467cad1ea990c Mon Sep 17 00:00:00 2001 From: Dan Plyukhin Date: Fri, 24 Jul 2026 11:57:10 -0400 Subject: [PATCH 3/4] Added comment and removed redundant virtual thread starvation test --- .github/workflows/ci.yml | 2 +- temporal-sdk/build.gradle | 18 ------- .../sync/DeterministicRunnerTest.java | 2 + .../ThreadStarvationVirtualThreadTest.java | 49 ------------------- 4 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18adf367fa..e473036051 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: USER: unittest TEMPORAL_SERVICE_ADDRESS: localhost:7233 USE_EXTERNAL_SERVICE: true - run: ./gradlew --no-daemon :temporal-sdk:virtualThreadTests :temporal-sdk:virtualThreadStarvationTests -x spotlessCheck -x spotlessApply -x spotlessJava -PtestJavaVersion=21 + run: ./gradlew --no-daemon :temporal-sdk:virtualThreadTests -x spotlessCheck -x spotlessApply -x spotlessJava -PtestJavaVersion=21 - name: Publish Test Report uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6 diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index 9534e1e6ff..5ca689b9ce 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -256,23 +256,6 @@ testing { } } - virtualThreadStarvationTests(JvmTestSuite) { - targets { - all { - testTask.configure { - jvmArgs '-Djdk.virtualThreadScheduler.parallelism=1', - '-Djdk.virtualThreadScheduler.maxPoolSize=1' - if (project.hasProperty("testJavaVersion")) { - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(project.property("testJavaVersion") as int) - } - } - shouldRunAfter(virtualThreadTests) - } - } - } - } - // Run the same test as the normal test task with virtual threads testsWithVirtualThreads(JvmTestSuite) { // Use the same source and resources as the main test set @@ -304,5 +287,4 @@ testing { tasks.named('check') { dependsOn(testing.suites.jackson3Tests) dependsOn(testing.suites.virtualThreadTests) - dependsOn(testing.suites.virtualThreadStarvationTests) } diff --git a/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java b/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java index 02f306fd7d..a2d9cadc66 100644 --- a/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java +++ b/temporal-sdk/src/test/java/io/temporal/internal/sync/DeterministicRunnerTest.java @@ -842,6 +842,8 @@ public void testThreadStarvationBeforeWorkflowThreadStarts() throws InterruptedE () -> fail("workflow code should not start while the executor thread is occupied")); try { + // Try starting the root workflow thread, but it won't run because the executor is tied up. + // We should detect this and throw a PotentialDeadlockException. PotentialDeadlockException e = Assert.assertThrows( PotentialDeadlockException.class, () -> runner.runUntilAllBlocked(10)); diff --git a/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java b/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java deleted file mode 100644 index a9baa4fec5..0000000000 --- a/temporal-sdk/src/virtualThreadStarvationTests/java/io/temporal/internal/sync/ThreadStarvationVirtualThreadTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.temporal.internal.sync; - -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; - -public class ThreadStarvationVirtualThreadTest { - - @Test(timeout = 10_000) - public void workflowThreadDoesNotStartWhenAllCarriersAreBusy() throws Exception { - CountDownLatch carrierOccupied = new CountDownLatch(1); - AtomicBoolean releaseCarrier = new AtomicBoolean(); - Thread carrierBlocker = - Thread.ofVirtual() - .name("carrier-blocker") - .start( - () -> { - carrierOccupied.countDown(); - while (!releaseCarrier.get()) { - Thread.onSpinWait(); - } - }); - carrierOccupied.await(); - - ExecutorService workflowThreadExecutor = Executors.newVirtualThreadPerTaskExecutor(); - DeterministicRunner runner = - DeterministicRunner.newRunner( - workflowThreadExecutor::submit, - DummySyncWorkflowContext.newDummySyncWorkflowContext(), - () -> { - throw new AssertionError("workflow code should not start while the carrier is busy"); - }); - - try { - PotentialDeadlockException e = - assertThrows(PotentialDeadlockException.class, () -> runner.runUntilAllBlocked(100)); - assertTrue(e.getMessage().contains("could not start executing within 100ms")); - } finally { - workflowThreadExecutor.shutdownNow(); - releaseCarrier.set(true); - carrierBlocker.join(); - } - } -} From e3961a9110ad9fba242c1d731a7e80f4be102ce7 Mon Sep 17 00:00:00 2001 From: Dan Plyukhin Date: Fri, 24 Jul 2026 12:27:26 -0400 Subject: [PATCH 4/4] Spotless --- temporal-sdk/build.gradle | 2 +- .../java/io/temporal/internal/sync/DeterministicRunner.java | 4 ++-- .../temporal/internal/sync/PotentialDeadlockException.java | 4 +--- .../main/java/io/temporal/internal/sync/WorkflowThread.java | 4 ++-- .../src/main/java/io/temporal/worker/WorkerOptions.java | 6 +++--- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index 5ca689b9ce..d7b090e5b5 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -287,4 +287,4 @@ testing { tasks.named('check') { dependsOn(testing.suites.jackson3Tests) dependsOn(testing.suites.virtualThreadTests) -} +} \ No newline at end of file diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java index 2147b518d4..cebe4babd7 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/DeterministicRunner.java @@ -52,8 +52,8 @@ static DeterministicRunner newRunner( * completed or blocked. * * @throws Throwable if one of the threads didn't handle an exception. - * @param deadlockDetectionTimeout the maximum time in milliseconds after a thread is - * scheduled until it yields or completes. + * @param deadlockDetectionTimeout the maximum time in milliseconds after a thread is scheduled + * until it yields or completes. */ void runUntilAllBlocked(long deadlockDetectionTimeout); diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java index e696b0fd6d..2e952d504b 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/PotentialDeadlockException.java @@ -49,9 +49,7 @@ public class PotentialDeadlockException extends RuntimeException { * @param timeoutMillis configured deadlock detection timeout in milliseconds */ PotentialDeadlockException( - WorkflowThreadContext workflowThreadContext, - long detectionTimestamp, - long timeoutMillis) { + WorkflowThreadContext workflowThreadContext, long detectionTimestamp, long timeoutMillis) { super( "[TMPRL1101] Potential deadlock detected. Workflow thread could not start executing within " + formatTimeout(timeoutMillis) diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java index 688abfce7b..c79318d559 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowThread.java @@ -65,8 +65,8 @@ static WorkflowThread newThread(Runnable runnable, boolean detached, String name SyncWorkflowContext getWorkflowContext(); /** - * @param deadlockDetectionTimeoutMs the maximum time in milliseconds after a thread is - * scheduled until it yields or completes. + * @param deadlockDetectionTimeoutMs the maximum time in milliseconds after a thread is scheduled + * until it yields or completes. * @return true if coroutine made some progress. */ boolean runUntilBlocked(long deadlockDetectionTimeoutMs); diff --git a/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java b/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java index 56db2bc187..aa61c6fa4c 100644 --- a/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java +++ b/temporal-sdk/src/main/java/io/temporal/worker/WorkerOptions.java @@ -327,9 +327,9 @@ public Builder setLocalActivityWorkerOnly(boolean localActivityWorkerOnly) { /** * @param defaultDeadlockDetectionTimeoutMs time period in ms that will be used to detect * workflows deadlock. Default is 1000ms, which is chosen if set to zero. - *

Specifies a time interval in milliseconds within which a workflow task must - * yield (like calling an Activity) or complete. If a workflow task runs longer than - * the specified interval or takes too long to begin running, it will fail automatically. + *

Specifies a time interval in milliseconds within which a workflow task must yield + * (like calling an Activity) or complete. If a workflow task runs longer than the specified + * interval or takes too long to begin running, it will fail automatically. * @return {@code this} * @see io.temporal.internal.sync.PotentialDeadlockException */