From f53e12dfde94fc4460edc1768fa75e5bce7aca9c Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Sat, 22 Aug 2026 14:03:43 -0600 Subject: [PATCH] Fixes flaky remove_listener subscriber test The test called add_listener/2 right after start_early!, racing the initialize burst's messages into the subscriber's mailbox - sends from different processes have no cross-sender ordering guarantee, so the listener occasionally caught burst messages that later tripped refute_receive (7 of 100 runs failed under CPU load; 0 of 100 after). The listener is now registered at start_link time, so it deterministically sees the whole burst. The test asserts seq 0..5 arrive, removes the listener with the synchronous call, drives the event, and ends on refute_received - the subscriber's final stats reply is ordered after any fan-out send, so no timeout is needed. Refs: sui-hmm --- test/statifier_ui/trace/subscriber_test.exs | 24 +++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/statifier_ui/trace/subscriber_test.exs b/test/statifier_ui/trace/subscriber_test.exs index a83dea3..44cf4ae 100644 --- a/test/statifier_ui/trace/subscriber_test.exs +++ b/test/statifier_ui/trace/subscriber_test.exs @@ -214,15 +214,31 @@ defmodule StatifierUI.Trace.SubscriberTest do test "remove_listener/2 stops delivery to that pid" do machine = SessionCase.compile!(@two_state) - {sub, session} = SessionCase.start_early!(machine, "sess_remove_listen") - :ok = Subscriber.add_listener(sub, self()) - SessionCase.wait_for_seq(sub, 5) + # The listener is registered at start_link time, not via + # add_listener/2: an add_listener call races the initialize burst's + # delivery into the subscriber's mailbox (different senders, no + # ordering guarantee), so the listener could catch a stray burst + # message and trip the refutation below (sui-hmm). + {sub, session} = + SessionCase.start_early!(machine, "sess_remove_listen", listeners: [self()]) + + # Delivery is live before removal: the whole burst arrives here. + SessionCase.wait_for_seq(sub, 6) + received = collect_listener_messages("sess_remove_listen", 6) + assert Enum.map(received, & &1.seq) == Enum.to_list(0..5) + + # remove_listener/2 is synchronous and the driven event is sent only + # after it returns, so every subsequent message is fanned out to an + # already-empty listener set. :ok = Subscriber.remove_listener(sub, self()) drive_and_wait(sub, session) - refute_receive {:statifier_ui, "sess_remove_listen", _message}, 100 + # drive_and_wait's final stats call returned after the subscriber + # processed the last message; any fan-out it did was sent before that + # reply, so an unwanted message would already be in this mailbox. + refute_received {:statifier_ui, "sess_remove_listen", _message} end end