test(task): de-flake the submitOneShot enrolled-latch ordering test

This commit is contained in:
matevip 2026-05-16 14:51:57 +08:00
parent f56f4b059a
commit dca71b5a43

View File

@ -264,28 +264,30 @@ class AsyncTaskServiceOneShotTest {
ConcurrentHashMap<String, ?> activePolls = getInternalMap("activePolls"); ConcurrentHashMap<String, ?> activePolls = getInternalMap("activePolls");
ConcurrentHashMap<String, ?> pollTaskToConv = getInternalMap("pollTaskToConv"); ConcurrentHashMap<String, ?> pollTaskToConv = getInternalMap("pollTaskToConv");
AtomicReference<String> taskIdRef = new AtomicReference<>();
AtomicBoolean observedActive = new AtomicBoolean(); AtomicBoolean observedActive = new AtomicBoolean();
AtomicBoolean observedConvLink = new AtomicBoolean(); AtomicBoolean observedConvLink = new AtomicBoolean();
AtomicReference<String> observedKey = new AtomicReference<>();
CountDownLatch probed = new CountDownLatch(1); CountDownLatch probed = new CountDownLatch(1);
// The Callable acts as a probe: if the latch invariant holds, both // The Callable acts as a probe: if the latch invariant holds, the
// bookkeeping maps already contain this taskId by the time the body // calling thread has already put this task into both bookkeeping
// runs (calling thread put before countDown, worker awaited // maps by the time the worker runs (put happens before countDown,
// countDown before reaching here). If the latch were removed, the // the worker awaits countDown). The probe cannot read its own taskId
// worker could race ahead, observe empty maps, and finish then the // submitOneShot only hands it back to the caller *after* releasing
// calling thread's put would leak a ghost entry. // the latch so it instead checks that each map holds exactly the
// one entry this single-task test created, and records the enrolled
// key for an after-the-fact identity check. If the latch were
// removed, the worker could race ahead and observe empty maps.
Callable<String> work = () -> { Callable<String> work = () -> {
String tid = taskIdRef.get(); observedActive.set(activePolls.size() == 1);
observedActive.set(tid != null && activePolls.containsKey(tid)); observedConvLink.set(pollTaskToConv.size() == 1);
observedConvLink.set(tid != null && pollTaskToConv.containsKey(tid)); observedKey.set(activePolls.keySet().stream().findFirst().orElse(null));
probed.countDown(); probed.countDown();
return "ok"; return "ok";
}; };
AsyncTaskEntity entity = service.submitOneShot( AsyncTaskEntity entity = service.submitOneShot(
"agent_delegate", "conv-latch", null, "{}", "user-1", work); "agent_delegate", "conv-latch", null, "{}", "user-1", work);
taskIdRef.set(entity.getTaskId());
assertThat(probed.await(5, TimeUnit.SECONDS)) assertThat(probed.await(5, TimeUnit.SECONDS))
.as("probe must run within timeout") .as("probe must run within timeout")
@ -293,11 +295,14 @@ class AsyncTaskServiceOneShotTest {
awaitDone(entity.getTaskId(), 5_000); awaitDone(entity.getTaskId(), 5_000);
assertThat(observedActive) assertThat(observedActive)
.as("activePolls must contain taskId when work.call() begins") .as("activePolls must hold the task when work.call() begins")
.isTrue(); .isTrue();
assertThat(observedConvLink) assertThat(observedConvLink)
.as("pollTaskToConv must contain taskId when work.call() begins") .as("pollTaskToConv must hold the task when work.call() begins")
.isTrue(); .isTrue();
assertThat(observedKey)
.as("the key enrolled in activePolls must be this task's id")
.hasValue(entity.getTaskId());
assertActiveMapsEmpty(); assertActiveMapsEmpty();
} }