unbridled-41 opened a new pull request, #5450:
URL: https://github.com/apache/rocketmq-dashboard/pull/5450

   ## Problem / Evidence
   
   `AiRunServiceTest.stopShouldAbortALiveRunAndLeaveItStoppedWithUserStopTest` 
fails intermittently in CI, on branches that do not touch this code at all:
   
   ```
   [ERROR] 
AiRunServiceTest.stopShouldAbortALiveRunAndLeaveItStoppedWithUserStopTest:327
           Expecting Optional to contain a value but it was empty.
   ```
   
   Observed on three PRs of this batch that only change unrelated areas — #5436 
(LLM key persistence), #5441 (DLQ resend confinement) and #5447 (alert 
threshold unit) — so it is a pre-existing flake, not a regression. The 
assertion under test (pre-fix):
   
   ```java
   RmqAiRun stopped = service.stop(RUN_ID);
   assertThat(registry.handle(RUN_ID))          // line 326
           .get()                               // line 327 — the failure
           .satisfies(handle -> 
assertThat(handle.abortReason()).contains(AbortReason.USER_STOP));
   ```
   
   ## Root cause
   
   `service.stop(RUN_ID)` → `AgentRunRegistry.stop` → `AgentRunHandle.stop` → 
`awaitStop()` → `cancelWorker()`, i.e. it interrupts the worker, and the 
worker's own finalisation is what removes the registration: 
`AiRunExecutor.execute`'s `finally` calls `registry.finish(run.getId())` 
(`AiRunExecutor.java:301`), which does `runs.remove(runId)`. The test then 
looks the run up **again**, so it races with that removal.
   
   Measured on this machine with a temporary probe (removed again): the run 
reaches `STOPPED`, and the registration is gone **2 ms** later. Locally the 
assertion usually wins the 2 ms window (the test passed 5/5 and 10/10 here); on 
a loaded CI runner it loses, which is exactly the recorded failure. Roughly one 
in ten of this batch's `Backend Tests` runs was red because of it.
   
   ## Fix
   
   Capture the handle **before** stopping and assert the reason on that 
reference — the same intent, without the race:
   
   ```java
   AgentRunHandle handle = registry.handle(RUN_ID).orElseThrow();
   RmqAiRun stopped = service.stop(RUN_ID);
   assertThat(handle.abortReason()).contains(AbortReason.USER_STOP);
   ```
   
   The user-visible attribution stays asserted through the run row, unchanged: 
`awaitRunStatus(STOPPED)` and `lastRun().getStopReason() == USER_STOP`.
   
   ## Priority & scoring
   
   PRIORITY 55 = 影响 14 (an unrelated flake turns ~1 in 10 CI runs red, sending 
contributors after phantom failures and blocking merges) + 波及 10 (every backend 
CI run) + 可复现 11 (intermittent, mechanism demonstrated above) + 维护价值 20 (a CI 
signal contributors can trust).
   
   FIX_CONFIDENCE 85: two lines, no behavioural assertion dropped; stability 
measured with 10 consecutive runs of the class.
   
   ## Tests
   
   ```
   cd server && mvn -o test 
-Dtest='org.apache.rocketmq.studio.ops.ai.conversation.*Test'
   ```
   
   - Green: `Tests run: 201, Failures: 0, Errors: 0, Skipped: 0` (includes 
`AgentRunHandleStopTest`, which still pins the handle-level single-stop 
semantics).
   - Stability: `AiRunServiceTest` run 10 times in a row → `Tests run: 22, 
Failures: 0, Errors: 0, Skipped: 0` each time.
   - Mechanism proof (temporary probe, not committed): after the run reaches 
`STOPPED`, `registry.handle(RUN_ID)` is still present immediately and 
disappears within 2 ms — the window the old assertion raced.
   - `mvn -o checkstyle:check` passes.
   
   ## Risk
   
   None for production code: only the test's assertion target changes. The 
assertion it makes (the stop is attributed to the user, and the run ends 
STOPPED/USER_STOP) is preserved.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to