返回 DeepSeek-Reasonix
mirror_queue.go
根目录 / internal / eventwire / mirror_queue.go
1 package eventwire
2
3 import (
4 "encoding/json"
5
6 "reasonix/internal/event"
7 )
8
9 const (
10 // MirrorQueueMaxFrames and MirrorQueueMaxBytes bound an external writer's
11 // in-memory outage buffer. Mirrors deliberately do not spool model events to
12 // disk: durable history is the recovery source for high-volume deltas.
13 MirrorQueueMaxFrames = 4096
14 MirrorQueueMaxBytes = 16 << 20
15
16 // MirrorQueuePriorityReserve keeps a tail of the frame budget available for
17 // lifecycle and ownership facts after recoverable deltas start dropping.
18 MirrorQueuePriorityReserve = 64
19
20 MirrorBatchMaxFrames = 512
21 MirrorBatchMaxBytes = 8 << 20
22 )
23
24 type mirrorQueueFrame struct {
25 event Event
26 size int
27 }
28
29 // MirrorQueue is a bounded, non-blocking queue for frames forwarded by an
30 // external session writer. It is not internally synchronized; callers use the
31 // same lock that protects their active mirror binding.
32 type MirrorQueue struct {
33 frames []mirrorQueueFrame
34 bytes int
35 }
36
37 // WireKindIsRecoverable identifies high-volume deltas that a disconnected
38 // reader can reconstruct from durable session history.
39 func WireKindIsRecoverable(kind string) bool {
40 switch kind {
41 case "reasoning", "text", "tool_progress", "stream_attempt":
42 return true
43 default:
44 return false
45 }
46 }
47
48 // EventMustReachMirror identifies terminal/routing truth that must survive
49 // ordinary queue saturation. In a degenerate all-terminal flood, the newest
50 // truth replaces the oldest because no finite queue can retain both forever.
51 func EventMustReachMirror(frame Event) bool {
52 switch frame.Kind {
53 case "turn_done", "session_changed":
54 return true
55 case "notice":
56 switch frame.Code {
57 case event.NoticeCodeBackgroundJobFinished,
58 event.NoticeCodeSessionTakenOver,
59 event.NoticeCodeSessionReclaimRequested,
60 event.NoticeCodeSessionReclaimed:
61 return true
62 }
63 }
64 return false
65 }
66
67 // FrameMustReachMirror is EventMustReachMirror for an already-marshaled frame.
68 func FrameMustReachMirror(data []byte) bool {
69 var frame Event
70 return json.Unmarshal(data, &frame) == nil && EventMustReachMirror(frame)
71 }
72
73 // Push appends one frame if it fits the bounded recovery policy. It never
74 // blocks. A must-reach frame evicts recoverable traffic first and, only when
75 // the queue contains terminal truth exclusively, replaces the oldest frame.
76 func (q *MirrorQueue) Push(frame Event) bool {
77 if q == nil {
78 return false
79 }
80 data, err := json.Marshal(frame)
81 if err != nil || len(data) > MirrorQueueMaxBytes {
82 return false
83 }
84 entry := mirrorQueueFrame{event: frame, size: len(data)}
85 if WireKindIsRecoverable(frame.Kind) && len(q.frames) >= MirrorQueueMaxFrames-MirrorQueuePriorityReserve {
86 return false
87 }
88 if q.fits(entry) {
89 q.append(entry)
90 return true
91 }
92 if !EventMustReachMirror(frame) {
93 return false
94 }
95 for !q.fits(entry) {
96 if !q.evictFirst(func(candidate mirrorQueueFrame) bool {
97 return WireKindIsRecoverable(candidate.event.Kind)
98 }) && !q.evictFirst(func(candidate mirrorQueueFrame) bool {
99 return !EventMustReachMirror(candidate.event)
100 }) && !q.evictAt(0) {
101 return false
102 }
103 }
104 q.append(entry)
105 return true
106 }
107
108 // Prepend puts a failed batch back ahead of frames emitted while the request
109 // was in flight. When bounding is required, newer recoverable frames are
110 // evicted before any failed frame so retry order remains stable.
111 func (q *MirrorQueue) Prepend(frames []Event) {
112 if q == nil || len(frames) == 0 {
113 return
114 }
115 prefix := make([]mirrorQueueFrame, 0, len(frames))
116 for _, frame := range frames {
117 data, err := json.Marshal(frame)
118 if err == nil && len(data) <= MirrorQueueMaxBytes {
119 prefix = append(prefix, mirrorQueueFrame{event: frame, size: len(data)})
120 }
121 }
122 if len(prefix) == 0 {
123 return
124 }
125 combined := make([]mirrorQueueFrame, 0, len(prefix)+len(q.frames))
126 combined = append(combined, prefix...)
127 combined = append(combined, q.frames...)
128 q.frames = combined
129 q.recount()
130 for q.overLimit() {
131 if !q.evictFromEnd(len(prefix), func(candidate mirrorQueueFrame) bool {
132 return WireKindIsRecoverable(candidate.event.Kind)
133 }) && !q.evictFromEnd(0, func(candidate mirrorQueueFrame) bool {
134 return WireKindIsRecoverable(candidate.event.Kind)
135 }) && !q.evictFromEnd(len(prefix), func(candidate mirrorQueueFrame) bool {
136 return !EventMustReachMirror(candidate.event)
137 }) && !q.evictFromEnd(0, func(candidate mirrorQueueFrame) bool {
138 return !EventMustReachMirror(candidate.event)
139 }) && !q.evictAt(0) {
140 break
141 }
142 }
143 }
144
145 // Take removes up to max frames from the front of the queue.
146 func (q *MirrorQueue) Take(max int) []Event {
147 if q == nil || max <= 0 || len(q.frames) == 0 {
148 return nil
149 }
150 if max > len(q.frames) {
151 max = len(q.frames)
152 }
153 out := make([]Event, max)
154 for i := range max {
155 out[i] = q.frames[i].event
156 }
157 q.frames = append([]mirrorQueueFrame(nil), q.frames[max:]...)
158 q.recount()
159 return out
160 }
161
162 func (q *MirrorQueue) Len() int {
163 if q == nil {
164 return 0
165 }
166 return len(q.frames)
167 }
168
169 func (q *MirrorQueue) Bytes() int {
170 if q == nil {
171 return 0
172 }
173 return q.bytes
174 }
175
176 func (q *MirrorQueue) Reset() {
177 if q == nil {
178 return
179 }
180 q.frames = nil
181 q.bytes = 0
182 }
183
184 func (q *MirrorQueue) fits(entry mirrorQueueFrame) bool {
185 return len(q.frames) < MirrorQueueMaxFrames && q.bytes+entry.size <= MirrorQueueMaxBytes
186 }
187
188 func (q *MirrorQueue) append(entry mirrorQueueFrame) {
189 q.frames = append(q.frames, entry)
190 q.bytes += entry.size
191 }
192
193 func (q *MirrorQueue) overLimit() bool {
194 return len(q.frames) > MirrorQueueMaxFrames || q.bytes > MirrorQueueMaxBytes
195 }
196
197 func (q *MirrorQueue) recount() {
198 q.bytes = 0
199 for _, frame := range q.frames {
200 q.bytes += frame.size
201 }
202 }
203
204 func (q *MirrorQueue) evictFirst(match func(mirrorQueueFrame) bool) bool {
205 for i, frame := range q.frames {
206 if match(frame) {
207 return q.evictAt(i)
208 }
209 }
210 return false
211 }
212
213 func (q *MirrorQueue) evictFromEnd(start int, match func(mirrorQueueFrame) bool) bool {
214 if start < 0 {
215 start = 0
216 }
217 for i := len(q.frames) - 1; i >= start; i-- {
218 if match(q.frames[i]) {
219 return q.evictAt(i)
220 }
221 }
222 return false
223 }
224
225 func (q *MirrorQueue) evictAt(index int) bool {
226 if index < 0 || index >= len(q.frames) {
227 return false
228 }
229 q.bytes -= q.frames[index].size
230 copy(q.frames[index:], q.frames[index+1:])
231 q.frames = q.frames[:len(q.frames)-1]
232 return true
233 }
234
235 // MarshalMirrorBatch finds the largest prefix whose marshaled HTTP request is
236 // no larger than maxBytes. The returned remainder must be prepended
237 // immediately; it still follows the returned batch in wire order.
238 func MarshalMirrorBatch(frames []Event, maxBytes int, marshal func([]Event) ([]byte, error)) (batch, remainder []Event, payload []byte, err error) {
239 if maxBytes <= 0 || marshal == nil {
240 return nil, append([]Event(nil), frames...), nil, nil
241 }
242 emptyPayload, err := marshal(frames[:0])
243 if err != nil {
244 return nil, append([]Event(nil), frames...), nil, err
245 }
246 if len(emptyPayload) > maxBytes {
247 return nil, append([]Event(nil), frames...), nil, nil
248 }
249 if len(frames) == 0 {
250 return nil, nil, emptyPayload, nil
251 }
252
253 firstPayload, err := marshal(frames[:1])
254 if err != nil {
255 return nil, append([]Event(nil), frames...), nil, err
256 }
257 if len(firstPayload) > maxBytes {
258 return nil, append([]Event(nil), frames...), emptyPayload, nil
259 }
260
261 low, high := 1, len(frames)
262 payload = firstPayload
263 for low < high {
264 mid := low + (high-low+1)/2
265 candidate, marshalErr := marshal(frames[:mid])
266 if marshalErr != nil {
267 return nil, append([]Event(nil), frames...), nil, marshalErr
268 }
269 if len(candidate) <= maxBytes {
270 low = mid
271 payload = candidate
272 } else {
273 high = mid - 1
274 }
275 }
276 return append([]Event(nil), frames[:low]...), append([]Event(nil), frames[low:]...), payload, nil
277 }
278
278 lines GO