The GitHub Actions job "Required Checks" on texera.git/gh-readonly-queue/main/pr-7323-76ab7ae41270e21939d95bb65a230a1e50a00ea9 has succeeded. Run started by GitHub user aglinxinyuan (triggered by aglinxinyuan).
Head commit for run: 12e20fdb42b153bad3c024a923e3968970679b00 / Xinyuan Lin <[email protected]> test(amber): add ClusterListenerSpec against a single-node cluster (#7323) ### What changes were proposed in this PR? `ClusterListener` was the **last file in the engine at 0%**. It is worth covering rather than skipping, because the count it maintains is what the frontend's cluster badge renders: `updateClusterStatus` recomputes `numWorkerNodesInCluster` on every membership event and pushes a `ClusterStatusUpdateEvent` to every open session. A listener that stops subscribing, or stops fanning out, leaves every client showing a stale node count with nothing failing. **The suite runs a real single-node cluster.** `AmberRuntime.pekkoConfig` selects the cluster provider with artery on port 0, so joining the node to itself makes it the leader and produces genuine `MemberUp` events. That is not a convenience: `Member` is `private[cluster]` and cannot be synthesized, so a real join is the only way to reach the event path at all. Three tests — the member-address reply, the recompute-and-fan-out on a membership event, and the catch-all arm. **The catch-all test goes through `TestActorRef.receive`, not `!`.** This one is worth explaining, because the obvious version does not work: > An earlier draft sent the stray message with `!` and asserted the listener still answered. It stayed **green** with the catch-all replaced by a `throw` — supervision restarts the actor, and a restarted listener answers the next request exactly like one that never failed. `TestActorRef.receive` invokes receive directly and lets the exception reach the caller, so "did not throw" means what it says. The subscribe path is mutation-checked the same way: dropping `cluster.subscribe` from `preStart` turns the fan-out test red. **Two ordering hazards are handled explicitly, both found by failures rather than by reasoning:** 1. Listeners are stopped at the end of each case. One left running stays subscribed and keeps iterating `SessionState.getAllSessionStates` on every membership event — an earlier draft died with `ConcurrentModificationException` as soon as a later case registered a session. 2. The mock session is removed inside the case that created it. ScalaMock scopes expectations per test while the `SessionState` registry is JVM-global, so a leftover session gets called by a later listener against an expired mock (`Unexpected call: Session.getAsyncRemote`). **Both of those stem from a real production race, which this PR does not attempt to fix.** `SessionState`'s registry is a plain unsynchronized `mutable.HashMap`: ```scala private val sessionIdToSessionState = new mutable.HashMap[String, SessionState]() def getAllSessionStates: Iterable[SessionState] = sessionIdToSessionState.values ``` and `updateClusterStatus` iterates it from the cluster-event thread while websocket open/close mutate it from container threads. A node joining or leaving while a user opens a tab can throw inside the listener. Happy to open a separate issue for it. Left uncovered deliberately: the `MemberRemoved` recovery arm, which walks `WorkflowService.getAllWorkflowServices` and calls `notifyNodeFailure` or `forcefullyStop` on each live execution — that needs real Amber clients, i.e. integration scope. No production file is touched. ### Any related issues, documentation, discussions? Closes #7321 ### How was this PR tested? ``` sbt "WorkflowExecutionService/testOnly org.apache.texera.amber.clustering.ClusterListenerSpec" ``` Run **three times consecutively** in one invocation, because the first draft was order-dependent and I wanted the fix demonstrated rather than assumed: ``` [info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0 [info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0 [info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0 ``` `Test/scalafmtCheck` and `Test/scalafix --check` both `[success]`. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 5) --------- Signed-off-by: Xinyuan Lin <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> Report URL: https://github.com/apache/texera/actions/runs/31145863782 With regards, GitHub Actions via GitBox
