Skip to content

Commit 12bd1f0

Browse files
gnodetclaude
authored andcommitted
CAMEL-24063: Revert Awaitility-based DirectProducerBlockingTest to race-tolerant design
The Awaitility-based TIMED_WAITING approach (introduced in CAMEL-19549, Jun 25/29) imposed a strict thread-ordering dependency that introduced flakiness where the original code had none (stable for 6 years). Replace with ScheduledExecutorService.schedule() — same 200ms delay semantics as the original Thread.sleep(200), without using Thread.sleep: - If resume fires before sendBody starts blocking: route is already resumed, sendBody finds the consumer immediately (pass) - If resume fires while sendBody is blocking: sendBody gets unblocked (pass) Both race outcomes produce a passing test. Restore timeout=1000 (800ms of headroom is plenty). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9b0dfb1 commit 12bd1f0

1 file changed

Lines changed: 17 additions & 28 deletions

File tree

core/camel-core/src/test/java/org/apache/camel/component/direct/DirectProducerBlockingTest.java

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
*/
1717
package org.apache.camel.component.direct;
1818

19-
import java.util.concurrent.ExecutorService;
2019
import java.util.concurrent.Executors;
20+
import java.util.concurrent.ScheduledExecutorService;
2121
import java.util.concurrent.TimeUnit;
2222

2323
import org.apache.camel.CamelExchangeException;
@@ -28,7 +28,6 @@
2828
import org.junit.jupiter.api.Test;
2929
import org.junit.jupiter.api.Timeout;
3030

31-
import static org.awaitility.Awaitility.await;
3231
import static org.junit.jupiter.api.Assertions.assertThrows;
3332
import static org.junit.jupiter.api.Assertions.assertTrue;
3433

@@ -74,36 +73,26 @@ public void testProducerBlocksWithNoConsumers() throws Exception {
7473

7574
@Test
7675
public void testProducerBlocksResumeTest() throws Exception {
77-
getMockEndpoint("mock:result").expectedMessageCount(1);
78-
7976
context.getRouteController().suspendRoute("foo");
8077

81-
Thread mainThread = Thread.currentThread();
82-
ExecutorService executor = Executors.newSingleThreadExecutor();
83-
executor.submit(new Runnable() {
84-
@Override
85-
public void run() {
86-
try {
87-
// Wait for the main thread to enter TIMED_WAITING state
88-
// (blocked on condition in DirectComponent.getConsumer).
89-
// Use a generous timeout — on slow CI the thread state
90-
// detection can take longer than 2 s.
91-
await().atMost(10, TimeUnit.SECONDS)
92-
.pollInterval(10, TimeUnit.MILLISECONDS)
93-
.until(() -> mainThread.getState() == Thread.State.TIMED_WAITING);
94-
95-
log.info("Resuming consumer");
96-
context.getRouteController().resumeRoute("foo");
97-
} catch (Exception e) {
98-
log.error("Error in background thread", e);
99-
}
78+
// Schedule route resume after 200ms. This is race-tolerant by design:
79+
// - If resume fires before sendBody starts blocking: route is already
80+
// resumed, sendBody finds the consumer immediately and succeeds
81+
// - If resume fires while sendBody is blocking: sendBody gets unblocked
82+
// Either outcome produces a passing test.
83+
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
84+
executor.schedule(() -> {
85+
try {
86+
log.info("Resuming consumer");
87+
context.getRouteController().resumeRoute("foo");
88+
} catch (Exception e) {
89+
log.error("Error resuming route", e);
10090
}
101-
});
91+
}, 200, TimeUnit.MILLISECONDS);
92+
93+
getMockEndpoint("mock:result").expectedMessageCount(1);
10294

103-
// This call will block until the route is resumed by the background thread.
104-
// Use a generous timeout so the background thread has enough headroom to
105-
// detect the TIMED_WAITING state and resume the route even under CI load.
106-
template.sendBody("direct:suspended?block=true&timeout=10000", "hello world");
95+
template.sendBody("direct:suspended?block=true&timeout=1000", "hello world");
10796

10897
assertMockEndpointsSatisfied();
10998

0 commit comments

Comments
 (0)