Package ai.mindconnect.adminui.service
Class StreamBus
java.lang.Object
ai.mindconnect.adminui.service.StreamBus
Per-channel multiplexing fan-out used by
ActiveStreams. One
instance is created per live SSE stream and lives on the channel's
ActiveStreams.Handle. Producers call publish(java.lang.String, java.lang.String) to push an
event; every currently-attached SseEmitter sees a copy.
Each event gets a monotonically-increasing sequence number, and the
last BUFFER_SIZE events are kept in a ring buffer. A client
that reconnects with a lastSeq parameter via attach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter, long)
receives every event in the buffer with seq > lastSeq before
being subscribed to the live feed — closing the small race window
between a disconnect and a reconnect (typically a few seconds on F5).
Thread-safety:
publishis synchronized so seq assignment and the buffer/subscriber writes are atomic.- The subscriber list is a
CopyOnWriteArrayList— fine for a low number of concurrent subscribers (1-3 in practice; one tab streaming, maybe one observer tab). attach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter, long)synchronizes againstpublishso a freshly attached subscriber can't miss an event that's in flight while the buffer-replay loop runs.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordOne past event held in the ring buffer. -
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidattach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter, long lastSeq) Subscribesemitterto the live feed.voidcloseAll()Completes every attached subscriber and clears the list.voiddetach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter) Drops an emitter from the subscriber list.longlastSeq()Last published seq, for clients that need to track "where am I".voidRecords the event in the ring buffer and broadcasts it to every currently attached subscriber.
-
Field Details
-
BUFFER_SIZE
public static final int BUFFER_SIZERing-buffer depth. Sized for a chat-turn's worth of token-by-token patches plus task-card state changes — typical turn produces well under 200 events. Bumping this is cheap (a few KB per stream).- See Also:
-
-
Constructor Details
-
StreamBus
public StreamBus()
-
-
Method Details
-
publish
Records the event in the ring buffer and broadcasts it to every currently attached subscriber. Failures on a single subscriber drop that subscriber from the list — its emitter is already broken at that point, no benefit in retrying. -
attach
public void attach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter, long lastSeq) Subscribesemitterto the live feed. IflastSeqis non-negative, every buffered event with seq > lastSeq is replayed synchronously before the emitter is added to the subscriber list — the holding lock guarantees no live event interleaves.Pass
-1aslastSeqto replay everything in the buffer (initial attach from a reconnecting client that has no seq yet). PassLong.MAX_VALUEto skip replay entirely (the stream's own producer thread doesn't need replay). -
detach
public void detach(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter) Drops an emitter from the subscriber list. No-op if not present. -
lastSeq
public long lastSeq()Last published seq, for clients that need to track "where am I". -
closeAll
public void closeAll()Completes every attached subscriber and clears the list. Called by the channel's owner after the producer is fully done (after thedoneevent has been published) so reconnect-tabs see a clean stream end rather than hanging on an open SSE forever.
-