返回 DeepSeek-Reasonix
sync.go
根目录 / internal / event / sync.go
1 package event
2
3 import (
4 "sync"
5
6 "reasonix/internal/evidence"
7 "reasonix/internal/nilutil"
8 )
9
10 // Sync wraps a Sink so concurrent Emit calls are serialized. The base Sink
11 // contract assumes serial emission — the agent's run loop emits one event at a
12 // time. Background jobs (internal/jobs) emit from their own goroutines, which can
13 // overlap a running turn's emission; wrapping the session sink once in Sync keeps
14 // the serial-Emit invariant every sink relies on (an SSE writer, a webview
15 // EventsEmit, a TUI channel) without each having to lock. A nil sink yields
16 // Discard.
17 func Sync(s Sink) Sink {
18 if nilutil.IsNil(s) {
19 return Discard
20 }
21 return &syncSink{inner: s}
22 }
23
24 type syncSink struct {
25 mu sync.Mutex
26 inner Sink
27 }
28
29 func (s *syncSink) Emit(e Event) {
30 s.mu.Lock()
31 defer s.mu.Unlock()
32 s.inner.Emit(e)
33 }
34
35 func (s *syncSink) RecordReadinessAudit(a evidence.ReadinessAudit) {
36 s.mu.Lock()
37 defer s.mu.Unlock()
38 if rs, ok := s.inner.(ReadinessAuditSink); ok {
39 rs.RecordReadinessAudit(a)
40 }
41 }
42
43 func (s *syncSink) RecordTurnCompletion() {
44 s.mu.Lock()
45 defer s.mu.Unlock()
46 if ts, ok := s.inner.(TurnCompletionSink); ok {
47 ts.RecordTurnCompletion()
48 }
49 }
50
51 func (s *syncSink) RecordProtocolRecovery(a ProtocolRecoveryAudit) {
52 s.mu.Lock()
53 defer s.mu.Unlock()
54 if rs, ok := s.inner.(ProtocolRecoveryAuditSink); ok {
55 rs.RecordProtocolRecovery(a)
56 }
57 }
58
58 lines GO