返回 DeepSeek-Reasonix
broadcaster.go
根目录 / internal / serve / broadcaster.go
1 package serve
2
3 import (
4 "encoding/json"
5 "sync"
6
7 "reasonix/internal/event"
8 "reasonix/internal/eventwire"
9 )
10
11 // Broadcaster is the event.Sink the controller emits to in server mode. It
12 // marshals each event once and fans it out to every connected SSE subscriber.
13 // A slow subscriber's buffer is allowed to drop rather than back-pressure the
14 // agent goroutine — a browser that can't keep up loses intermediate frames, not
15 // the whole session (it can refetch /history).
16 type Broadcaster struct {
17 mu sync.Mutex
18 subs map[chan []byte]struct{}
19 }
20
21 // NewBroadcaster returns an empty Broadcaster ready to accept subscribers.
22 func NewBroadcaster() *Broadcaster {
23 return &Broadcaster{subs: map[chan []byte]struct{}{}}
24 }
25
26 // Emit marshals the event to JSON and delivers it to every subscriber. Drops to
27 // a subscriber whose buffer is full rather than blocking. A marshal failure is
28 // dropped silently — one bad event shouldn't stall the stream.
29 func (b *Broadcaster) Emit(e event.Event) {
30 data, err := json.Marshal(eventwire.ToWire(e))
31 if err != nil {
32 return
33 }
34 b.mu.Lock()
35 defer b.mu.Unlock()
36 for ch := range b.subs {
37 select {
38 case ch <- data:
39 default: // subscriber is behind; drop this frame for it
40 }
41 }
42 }
43
44 // EmitTo delivers an event only to the supplied subscriber. It is used for
45 // connection-local recovery frames, such as replaying a prompt to a browser
46 // that attached after the original event was emitted. Normal runtime events
47 // should continue to use Emit so every subscriber receives them.
48 func (b *Broadcaster) EmitTo(target <-chan []byte, e event.Event) {
49 data, err := json.Marshal(eventwire.ToWire(e))
50 if err != nil {
51 return
52 }
53 b.mu.Lock()
54 defer b.mu.Unlock()
55 for ch := range b.subs {
56 if (<-chan []byte)(ch) != target {
57 continue
58 }
59 select {
60 case ch <- data:
61 default: // subscriber is behind; drop this frame rather than blocking.
62 }
63 return
64 }
65 }
66
67 // Subscribe registers a new SSE client and returns its channel plus an
68 // unsubscribe func the handler must call (defer) when the client disconnects.
69 func (b *Broadcaster) Subscribe() (<-chan []byte, func()) {
70 ch := make(chan []byte, 64)
71 b.mu.Lock()
72 b.subs[ch] = struct{}{}
73 b.mu.Unlock()
74 return ch, func() {
75 b.mu.Lock()
76 if _, ok := b.subs[ch]; ok {
77 delete(b.subs, ch)
78 close(ch)
79 }
80 b.mu.Unlock()
81 }
82 }
83
84 // Subscribers reports the current connection count (for diagnostics/tests).
85 func (b *Broadcaster) Subscribers() int {
86 b.mu.Lock()
87 defer b.mu.Unlock()
88 return len(b.subs)
89 }
90
90 lines GO