/home/noah/src/trueno/src/brick/patterns.rs
Line | Count | Source |
1 | | //! Async and Buffer Pattern Catalog |
2 | | //! |
3 | | //! Utility patterns from Phase 12 (E.10) for async operations, |
4 | | //! buffer management, and flow control. |
5 | | //! |
6 | | //! # Patterns Included |
7 | | //! |
8 | | //! - **LCP-12**: AsyncResult - async compute with sync fallback |
9 | | //! - **AWP-11**: BoundedQueue - bounded message queue with back-pressure |
10 | | //! - **AWP-13**: ReserveStrategy - buffer reservation strategies |
11 | | //! - **LCP-08**: GraphReuseCounter - graph reuse tracking |
12 | | //! - **AWP-03**: DualWakerState - dual-waker backpressure |
13 | | //! - **AWP-04**: StreamCapacity - HTTP/2 flow control |
14 | | //! - **AWP-09**: WakeSkipState - smart wake skip optimization |
15 | | |
16 | | use std::collections::VecDeque; |
17 | | |
18 | | // ============================================================================ |
19 | | // LCP-12: Async Compute with Sync Fallback |
20 | | // ============================================================================ |
21 | | |
22 | | /// Result of an async operation with fallback capability. |
23 | | #[derive(Debug, Clone)] |
24 | | pub enum AsyncResult<T, E> { |
25 | | /// Operation completed asynchronously |
26 | | Async(T), |
27 | | /// Operation completed synchronously (fallback) |
28 | | Sync(T), |
29 | | /// Operation failed |
30 | | Error(E), |
31 | | } |
32 | | |
33 | | impl<T, E> AsyncResult<T, E> { |
34 | | /// Check if result was obtained asynchronously. |
35 | | #[must_use] |
36 | 0 | pub fn is_async(&self) -> bool { |
37 | 0 | matches!(self, AsyncResult::Async(_)) |
38 | 0 | } |
39 | | |
40 | | /// Check if result was obtained synchronously (fallback). |
41 | | #[must_use] |
42 | 0 | pub fn is_sync(&self) -> bool { |
43 | 0 | matches!(self, AsyncResult::Sync(_)) |
44 | 0 | } |
45 | | |
46 | | /// Check if operation failed. |
47 | | #[must_use] |
48 | 0 | pub fn is_error(&self) -> bool { |
49 | 0 | matches!(self, AsyncResult::Error(_)) |
50 | 0 | } |
51 | | |
52 | | /// Get the result value, regardless of async/sync. |
53 | 0 | pub fn into_result(self) -> Result<T, E> { |
54 | 0 | match self { |
55 | 0 | AsyncResult::Async(v) | AsyncResult::Sync(v) => Ok(v), |
56 | 0 | AsyncResult::Error(e) => Err(e), |
57 | | } |
58 | 0 | } |
59 | | |
60 | | /// Map the success value. |
61 | 0 | pub fn map<U>(self, f: impl FnOnce(T) -> U) -> AsyncResult<U, E> { |
62 | 0 | match self { |
63 | 0 | AsyncResult::Async(v) => AsyncResult::Async(f(v)), |
64 | 0 | AsyncResult::Sync(v) => AsyncResult::Sync(f(v)), |
65 | 0 | AsyncResult::Error(e) => AsyncResult::Error(e), |
66 | | } |
67 | 0 | } |
68 | | } |
69 | | |
70 | | // ============================================================================ |
71 | | // AWP-11: Bounded Message Queue |
72 | | // ============================================================================ |
73 | | |
74 | | /// Bounded message queue with back-pressure. |
75 | | /// |
76 | | /// # Example |
77 | | /// ```rust |
78 | | /// use trueno::brick::BoundedQueue; |
79 | | /// |
80 | | /// let mut queue: BoundedQueue<i32> = BoundedQueue::new(3); |
81 | | /// |
82 | | /// assert!(queue.try_push(1).is_ok()); |
83 | | /// assert!(queue.try_push(2).is_ok()); |
84 | | /// assert!(queue.try_push(3).is_ok()); |
85 | | /// assert!(queue.try_push(4).is_err()); // Queue full |
86 | | /// |
87 | | /// assert_eq!(queue.pop(), Some(1)); |
88 | | /// assert!(queue.try_push(4).is_ok()); // Space available |
89 | | /// ``` |
90 | | #[derive(Debug)] |
91 | | pub struct BoundedQueue<T> { |
92 | | items: VecDeque<T>, |
93 | | capacity: usize, |
94 | | } |
95 | | |
96 | | impl<T> BoundedQueue<T> { |
97 | | /// Create a new bounded queue. |
98 | 0 | pub fn new(capacity: usize) -> Self { |
99 | 0 | Self { |
100 | 0 | items: VecDeque::with_capacity(capacity), |
101 | 0 | capacity, |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /// Try to push an item. Returns error if queue is full. |
106 | 0 | pub fn try_push(&mut self, item: T) -> Result<(), T> { |
107 | 0 | if self.items.len() >= self.capacity { |
108 | 0 | Err(item) |
109 | | } else { |
110 | 0 | self.items.push_back(item); |
111 | 0 | Ok(()) |
112 | | } |
113 | 0 | } |
114 | | |
115 | | /// Pop an item from the front. |
116 | 0 | pub fn pop(&mut self) -> Option<T> { |
117 | 0 | self.items.pop_front() |
118 | 0 | } |
119 | | |
120 | | /// Peek at the front item. |
121 | | #[must_use] |
122 | 0 | pub fn peek(&self) -> Option<&T> { |
123 | 0 | self.items.front() |
124 | 0 | } |
125 | | |
126 | | /// Get the number of items in the queue. |
127 | | #[must_use] |
128 | 0 | pub fn len(&self) -> usize { |
129 | 0 | self.items.len() |
130 | 0 | } |
131 | | |
132 | | /// Check if the queue is empty. |
133 | | #[must_use] |
134 | 0 | pub fn is_empty(&self) -> bool { |
135 | 0 | self.items.is_empty() |
136 | 0 | } |
137 | | |
138 | | /// Check if the queue is full. |
139 | | #[must_use] |
140 | 0 | pub fn is_full(&self) -> bool { |
141 | 0 | self.items.len() >= self.capacity |
142 | 0 | } |
143 | | |
144 | | /// Get the capacity. |
145 | | #[must_use] |
146 | 0 | pub fn capacity(&self) -> usize { |
147 | 0 | self.capacity |
148 | 0 | } |
149 | | |
150 | | /// Get remaining capacity. |
151 | | #[must_use] |
152 | 0 | pub fn remaining(&self) -> usize { |
153 | 0 | self.capacity.saturating_sub(self.items.len()) |
154 | 0 | } |
155 | | |
156 | | /// Clear all items. |
157 | 0 | pub fn clear(&mut self) { |
158 | 0 | self.items.clear(); |
159 | 0 | } |
160 | | } |
161 | | |
162 | | impl<T> Default for BoundedQueue<T> { |
163 | 0 | fn default() -> Self { |
164 | 0 | Self::new(16) |
165 | 0 | } |
166 | | } |
167 | | |
168 | | // ============================================================================ |
169 | | // AWP-13: Buffer Reserve Strategy |
170 | | // ============================================================================ |
171 | | |
172 | | /// Strategy for buffer reservation. |
173 | | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
174 | | pub enum ReserveStrategy { |
175 | | /// Reserve exact amount needed |
176 | | Exact, |
177 | | /// Reserve with 50% growth headroom |
178 | | Grow50, |
179 | | /// Reserve with 100% growth headroom (double) |
180 | | Double, |
181 | | /// Reserve to next power of two |
182 | | PowerOfTwo, |
183 | | } |
184 | | |
185 | | /// Reserve buffer capacity according to strategy. |
186 | | /// |
187 | | /// # Example |
188 | | /// ```rust |
189 | | /// use trueno::brick::{reserve_capacity, ReserveStrategy}; |
190 | | /// |
191 | | /// assert_eq!(reserve_capacity(100, ReserveStrategy::Exact), 100); |
192 | | /// assert_eq!(reserve_capacity(100, ReserveStrategy::Grow50), 150); |
193 | | /// assert_eq!(reserve_capacity(100, ReserveStrategy::Double), 200); |
194 | | /// assert_eq!(reserve_capacity(100, ReserveStrategy::PowerOfTwo), 128); |
195 | | /// ``` |
196 | | #[must_use] |
197 | 0 | pub fn reserve_capacity(needed: usize, strategy: ReserveStrategy) -> usize { |
198 | 0 | match strategy { |
199 | 0 | ReserveStrategy::Exact => needed, |
200 | 0 | ReserveStrategy::Grow50 => needed + needed / 2, |
201 | 0 | ReserveStrategy::Double => needed * 2, |
202 | 0 | ReserveStrategy::PowerOfTwo => needed.next_power_of_two(), |
203 | | } |
204 | 0 | } |
205 | | |
206 | | /// Buffer with configurable reserve strategy. |
207 | | #[derive(Debug)] |
208 | | pub struct StrategicBuffer { |
209 | | data: Vec<u8>, |
210 | | strategy: ReserveStrategy, |
211 | | } |
212 | | |
213 | | impl StrategicBuffer { |
214 | | /// Create a new buffer with the given strategy. |
215 | 0 | pub fn new(strategy: ReserveStrategy) -> Self { |
216 | 0 | Self { |
217 | 0 | data: Vec::new(), |
218 | 0 | strategy, |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | /// Create with initial capacity. |
223 | 0 | pub fn with_capacity(capacity: usize, strategy: ReserveStrategy) -> Self { |
224 | 0 | Self { |
225 | 0 | data: Vec::with_capacity(reserve_capacity(capacity, strategy)), |
226 | 0 | strategy, |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | /// Ensure capacity for additional bytes. |
231 | 0 | pub fn reserve(&mut self, additional: usize) { |
232 | 0 | let needed = self.data.len() + additional; |
233 | 0 | if needed > self.data.capacity() { |
234 | 0 | let new_cap = reserve_capacity(needed, self.strategy); |
235 | 0 | self.data.reserve(new_cap - self.data.capacity()); |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | | /// Write bytes to the buffer. |
240 | 0 | pub fn write(&mut self, bytes: &[u8]) { |
241 | 0 | self.reserve(bytes.len()); |
242 | 0 | self.data.extend_from_slice(bytes); |
243 | 0 | } |
244 | | |
245 | | /// Get the data. |
246 | | #[must_use] |
247 | 0 | pub fn as_slice(&self) -> &[u8] { |
248 | 0 | &self.data |
249 | 0 | } |
250 | | |
251 | | /// Get current length. |
252 | | #[must_use] |
253 | 0 | pub fn len(&self) -> usize { |
254 | 0 | self.data.len() |
255 | 0 | } |
256 | | |
257 | | /// Check if empty. |
258 | | #[must_use] |
259 | 0 | pub fn is_empty(&self) -> bool { |
260 | 0 | self.data.is_empty() |
261 | 0 | } |
262 | | |
263 | | /// Get capacity. |
264 | | #[must_use] |
265 | 0 | pub fn capacity(&self) -> usize { |
266 | 0 | self.data.capacity() |
267 | 0 | } |
268 | | |
269 | | /// Clear the buffer. |
270 | 0 | pub fn clear(&mut self) { |
271 | 0 | self.data.clear(); |
272 | 0 | } |
273 | | } |
274 | | |
275 | | impl Default for StrategicBuffer { |
276 | 0 | fn default() -> Self { |
277 | 0 | Self::new(ReserveStrategy::Double) |
278 | 0 | } |
279 | | } |
280 | | |
281 | | // ============================================================================ |
282 | | // LCP-08: Graph Reuse Counter |
283 | | // ============================================================================ |
284 | | |
285 | | /// Counter for tracking graph reuse in inference optimization. |
286 | | /// |
287 | | /// Tracks how many times a computation graph has been reused, |
288 | | /// enabling optimization decisions like caching or recompilation. |
289 | | #[derive(Debug, Clone, Default)] |
290 | | pub struct GraphReuseCounter { |
291 | | /// Number of times this graph has been executed |
292 | | reuse_count: u64, |
293 | | /// Threshold for considering graph "hot" |
294 | | hot_threshold: u64, |
295 | | /// Whether to enable caching |
296 | | cache_enabled: bool, |
297 | | } |
298 | | |
299 | | impl GraphReuseCounter { |
300 | | /// Create a new counter with hot threshold. |
301 | 0 | pub fn new(hot_threshold: u64) -> Self { |
302 | 0 | Self { |
303 | 0 | reuse_count: 0, |
304 | 0 | hot_threshold, |
305 | 0 | cache_enabled: false, |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | /// Record a graph execution. |
310 | 0 | pub fn record_use(&mut self) { |
311 | 0 | self.reuse_count += 1; |
312 | 0 | if self.reuse_count >= self.hot_threshold { |
313 | 0 | self.cache_enabled = true; |
314 | 0 | } |
315 | 0 | } |
316 | | |
317 | | /// Check if graph is considered "hot" (heavily reused). |
318 | | #[must_use] |
319 | 0 | pub fn is_hot(&self) -> bool { |
320 | 0 | self.reuse_count >= self.hot_threshold |
321 | 0 | } |
322 | | |
323 | | /// Check if caching should be enabled. |
324 | | #[must_use] |
325 | 0 | pub fn should_cache(&self) -> bool { |
326 | 0 | self.cache_enabled |
327 | 0 | } |
328 | | |
329 | | /// Get the current reuse count. |
330 | | #[must_use] |
331 | 0 | pub fn count(&self) -> u64 { |
332 | 0 | self.reuse_count |
333 | 0 | } |
334 | | |
335 | | /// Reset the counter. |
336 | 0 | pub fn reset(&mut self) { |
337 | 0 | self.reuse_count = 0; |
338 | 0 | self.cache_enabled = false; |
339 | 0 | } |
340 | | } |
341 | | |
342 | | // ============================================================================ |
343 | | // AWP-03: Dual-Waker Payload Backpressure |
344 | | // ============================================================================ |
345 | | |
346 | | /// Dual-waker state for async backpressure. |
347 | | /// |
348 | | /// Tracks two wakers: one for the producer, one for the consumer. |
349 | | /// Enables efficient producer/consumer coordination. |
350 | | #[derive(Debug, Default)] |
351 | | pub struct DualWakerState { |
352 | | /// Producer is waiting |
353 | | producer_waiting: bool, |
354 | | /// Consumer is waiting |
355 | | consumer_waiting: bool, |
356 | | /// Buffer fill level (0-100%) |
357 | | fill_percent: u8, |
358 | | /// High watermark for backpressure (%) |
359 | | high_watermark: u8, |
360 | | /// Low watermark for resume (%) |
361 | | low_watermark: u8, |
362 | | } |
363 | | |
364 | | impl DualWakerState { |
365 | | /// Create new state with watermarks. |
366 | 0 | pub fn new(low_watermark: u8, high_watermark: u8) -> Self { |
367 | 0 | Self { |
368 | 0 | producer_waiting: false, |
369 | 0 | consumer_waiting: false, |
370 | 0 | fill_percent: 0, |
371 | 0 | high_watermark: high_watermark.min(100), |
372 | 0 | low_watermark: low_watermark.min(high_watermark), |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /// Update fill level and determine who should wake. |
377 | 0 | pub fn update_fill(&mut self, fill_percent: u8) -> WakeDecision { |
378 | 0 | let old_fill = self.fill_percent; |
379 | 0 | self.fill_percent = fill_percent.min(100); |
380 | | |
381 | | // Crossed high watermark going up - pause producer |
382 | 0 | if old_fill < self.high_watermark && self.fill_percent >= self.high_watermark { |
383 | 0 | return WakeDecision::PauseProducer; |
384 | 0 | } |
385 | | |
386 | | // Crossed low watermark going down - resume producer |
387 | 0 | if old_fill > self.low_watermark && self.fill_percent <= self.low_watermark { |
388 | 0 | return WakeDecision::WakeProducer; |
389 | 0 | } |
390 | | |
391 | | // Data available - wake consumer if waiting |
392 | 0 | if self.fill_percent > 0 && self.consumer_waiting { |
393 | 0 | return WakeDecision::WakeConsumer; |
394 | 0 | } |
395 | | |
396 | 0 | WakeDecision::None |
397 | 0 | } |
398 | | |
399 | | /// Producer is now waiting. |
400 | 0 | pub fn producer_wait(&mut self) { |
401 | 0 | self.producer_waiting = true; |
402 | 0 | } |
403 | | |
404 | | /// Consumer is now waiting. |
405 | 0 | pub fn consumer_wait(&mut self) { |
406 | 0 | self.consumer_waiting = true; |
407 | 0 | } |
408 | | |
409 | | /// Producer was woken. |
410 | 0 | pub fn producer_woke(&mut self) { |
411 | 0 | self.producer_waiting = false; |
412 | 0 | } |
413 | | |
414 | | /// Consumer was woken. |
415 | 0 | pub fn consumer_woke(&mut self) { |
416 | 0 | self.consumer_waiting = false; |
417 | 0 | } |
418 | | |
419 | | /// Check if producer should be allowed to produce. |
420 | | #[must_use] |
421 | 0 | pub fn can_produce(&self) -> bool { |
422 | 0 | self.fill_percent < self.high_watermark |
423 | 0 | } |
424 | | |
425 | | /// Check if consumer has data to consume. |
426 | | #[must_use] |
427 | 0 | pub fn can_consume(&self) -> bool { |
428 | 0 | self.fill_percent > 0 |
429 | 0 | } |
430 | | } |
431 | | |
432 | | /// Decision on which waker to invoke. |
433 | | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
434 | | pub enum WakeDecision { |
435 | | /// No action needed |
436 | | None, |
437 | | /// Wake the producer (buffer drained below low watermark) |
438 | | WakeProducer, |
439 | | /// Wake the consumer (data available) |
440 | | WakeConsumer, |
441 | | /// Pause the producer (buffer above high watermark) |
442 | | PauseProducer, |
443 | | } |
444 | | |
445 | | // ============================================================================ |
446 | | // AWP-04: HTTP/2 Stream Capacity |
447 | | // ============================================================================ |
448 | | |
449 | | /// HTTP/2 flow control window state. |
450 | | /// |
451 | | /// Tracks send and receive window sizes for stream-level flow control. |
452 | | #[derive(Debug, Clone)] |
453 | | pub struct StreamCapacity { |
454 | | /// Connection-level send window |
455 | | connection_send: i32, |
456 | | /// Stream-level send window |
457 | | stream_send: i32, |
458 | | /// Receive window (how much we can receive) |
459 | | receive_window: i32, |
460 | | /// Initial window size |
461 | | initial_window: i32, |
462 | | /// Whether stream is blocked on flow control |
463 | | is_blocked: bool, |
464 | | } |
465 | | |
466 | | impl StreamCapacity { |
467 | | /// Default window size (HTTP/2 spec: 65535). |
468 | | pub const DEFAULT_WINDOW: i32 = 65535; |
469 | | |
470 | | /// Create with default windows. |
471 | 0 | pub fn new() -> Self { |
472 | 0 | Self { |
473 | 0 | connection_send: Self::DEFAULT_WINDOW, |
474 | 0 | stream_send: Self::DEFAULT_WINDOW, |
475 | 0 | receive_window: Self::DEFAULT_WINDOW, |
476 | 0 | initial_window: Self::DEFAULT_WINDOW, |
477 | 0 | is_blocked: false, |
478 | 0 | } |
479 | 0 | } |
480 | | |
481 | | /// Create with custom initial window. |
482 | 0 | pub fn with_initial_window(initial: i32) -> Self { |
483 | 0 | Self { |
484 | 0 | connection_send: initial, |
485 | 0 | stream_send: initial, |
486 | 0 | receive_window: initial, |
487 | 0 | initial_window: initial, |
488 | 0 | is_blocked: false, |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | | /// Reserve capacity for sending. |
493 | 0 | pub fn reserve_send(&mut self, bytes: i32) -> Result<(), FlowControlError> { |
494 | 0 | if bytes < 0 { |
495 | 0 | return Err(FlowControlError::NegativeReservation); |
496 | 0 | } |
497 | | |
498 | 0 | let available = self.available_send(); |
499 | 0 | if bytes > available { |
500 | 0 | self.is_blocked = true; |
501 | 0 | return Err(FlowControlError::InsufficientCapacity { |
502 | 0 | requested: bytes, |
503 | 0 | available, |
504 | 0 | }); |
505 | 0 | } |
506 | | |
507 | 0 | self.stream_send -= bytes; |
508 | 0 | self.connection_send -= bytes; |
509 | 0 | self.is_blocked = false; |
510 | 0 | Ok(()) |
511 | 0 | } |
512 | | |
513 | | /// Release send capacity (after WINDOW_UPDATE). |
514 | 0 | pub fn release_send(&mut self, bytes: i32) { |
515 | 0 | self.stream_send += bytes; |
516 | 0 | self.connection_send += bytes; |
517 | 0 | if self.available_send() > 0 { |
518 | 0 | self.is_blocked = false; |
519 | 0 | } |
520 | 0 | } |
521 | | |
522 | | /// Consume receive window (data received). |
523 | 0 | pub fn consume_receive(&mut self, bytes: i32) { |
524 | 0 | self.receive_window -= bytes; |
525 | 0 | } |
526 | | |
527 | | /// Replenish receive window (sending WINDOW_UPDATE). |
528 | 0 | pub fn replenish_receive(&mut self, bytes: i32) { |
529 | 0 | self.receive_window += bytes; |
530 | 0 | } |
531 | | |
532 | | /// Get available send capacity. |
533 | | #[must_use] |
534 | 0 | pub fn available_send(&self) -> i32 { |
535 | 0 | self.stream_send.min(self.connection_send).max(0) |
536 | 0 | } |
537 | | |
538 | | /// Get available receive capacity. |
539 | | #[must_use] |
540 | 0 | pub fn available_receive(&self) -> i32 { |
541 | 0 | self.receive_window.max(0) |
542 | 0 | } |
543 | | |
544 | | /// Check if stream is blocked on flow control. |
545 | | #[must_use] |
546 | 0 | pub fn is_blocked(&self) -> bool { |
547 | 0 | self.is_blocked |
548 | 0 | } |
549 | | |
550 | | /// Check if receive window needs replenishment. |
551 | | #[must_use] |
552 | 0 | pub fn needs_window_update(&self) -> bool { |
553 | 0 | self.receive_window < self.initial_window / 2 |
554 | 0 | } |
555 | | } |
556 | | |
557 | | impl Default for StreamCapacity { |
558 | 0 | fn default() -> Self { |
559 | 0 | Self::new() |
560 | 0 | } |
561 | | } |
562 | | |
563 | | /// Flow control errors. |
564 | | #[derive(Debug, Clone, PartialEq, Eq)] |
565 | | pub enum FlowControlError { |
566 | | /// Tried to reserve negative bytes |
567 | | NegativeReservation, |
568 | | /// Not enough capacity |
569 | | InsufficientCapacity { |
570 | | requested: i32, |
571 | | available: i32, |
572 | | }, |
573 | | } |
574 | | |
575 | | // ============================================================================ |
576 | | // AWP-09: Smart Payload Wake Skip |
577 | | // ============================================================================ |
578 | | |
579 | | /// Wake skip optimization state. |
580 | | /// |
581 | | /// Tracks whether a wakeup is actually needed or can be skipped |
582 | | /// to avoid unnecessary context switches. |
583 | | #[derive(Debug, Default)] |
584 | | pub struct WakeSkipState { |
585 | | /// Number of items pending |
586 | | pending_items: usize, |
587 | | /// Whether there's a registered waker |
588 | | has_waker: bool, |
589 | | /// Last poll had work to do |
590 | | last_poll_had_work: bool, |
591 | | /// Consecutive empty polls |
592 | | empty_poll_count: u32, |
593 | | /// Threshold for skipping wakes |
594 | | skip_threshold: u32, |
595 | | } |
596 | | |
597 | | impl WakeSkipState { |
598 | | /// Create with skip threshold. |
599 | 0 | pub fn new(skip_threshold: u32) -> Self { |
600 | 0 | Self { |
601 | 0 | pending_items: 0, |
602 | 0 | has_waker: false, |
603 | 0 | last_poll_had_work: false, |
604 | 0 | empty_poll_count: 0, |
605 | 0 | skip_threshold, |
606 | 0 | } |
607 | 0 | } |
608 | | |
609 | | /// Register that a waker exists. |
610 | 0 | pub fn register_waker(&mut self) { |
611 | 0 | self.has_waker = true; |
612 | 0 | } |
613 | | |
614 | | /// Clear waker registration. |
615 | 0 | pub fn clear_waker(&mut self) { |
616 | 0 | self.has_waker = false; |
617 | 0 | } |
618 | | |
619 | | /// Add pending items. |
620 | 0 | pub fn add_pending(&mut self, count: usize) { |
621 | 0 | self.pending_items += count; |
622 | 0 | } |
623 | | |
624 | | /// Remove pending items. |
625 | 0 | pub fn remove_pending(&mut self, count: usize) { |
626 | 0 | self.pending_items = self.pending_items.saturating_sub(count); |
627 | 0 | } |
628 | | |
629 | | /// Record poll result. |
630 | 0 | pub fn record_poll(&mut self, had_work: bool) { |
631 | 0 | self.last_poll_had_work = had_work; |
632 | 0 | if had_work { |
633 | 0 | self.empty_poll_count = 0; |
634 | 0 | } else { |
635 | 0 | self.empty_poll_count += 1; |
636 | 0 | } |
637 | 0 | } |
638 | | |
639 | | /// Determine if wake should be skipped. |
640 | | #[must_use] |
641 | 0 | pub fn should_skip_wake(&self) -> bool { |
642 | | // Skip if: |
643 | | // 1. No waker registered |
644 | | // 2. Already has pending items (will be polled anyway) |
645 | | // 3. Had recent empty polls (probably will be empty again) |
646 | 0 | if !self.has_waker { |
647 | 0 | return true; |
648 | 0 | } |
649 | 0 | if self.pending_items > 0 && self.last_poll_had_work { |
650 | 0 | return true; // Already has work queued |
651 | 0 | } |
652 | 0 | if self.empty_poll_count >= self.skip_threshold { |
653 | 0 | return true; // Likely to be empty again |
654 | 0 | } |
655 | 0 | false |
656 | 0 | } |
657 | | |
658 | | /// Check if wake is needed. |
659 | | #[must_use] |
660 | 0 | pub fn needs_wake(&self) -> bool { |
661 | 0 | !self.should_skip_wake() && self.pending_items > 0 |
662 | 0 | } |
663 | | |
664 | | /// Get pending count. |
665 | | #[must_use] |
666 | 0 | pub fn pending(&self) -> usize { |
667 | 0 | self.pending_items |
668 | 0 | } |
669 | | |
670 | | /// Reset empty poll tracking (after successful wake). |
671 | 0 | pub fn reset_tracking(&mut self) { |
672 | 0 | self.empty_poll_count = 0; |
673 | 0 | } |
674 | | } |
675 | | |
676 | | // ============================================================================ |
677 | | // Tests |
678 | | // ============================================================================ |
679 | | |
680 | | #[cfg(test)] |
681 | | mod tests { |
682 | | use super::*; |
683 | | |
684 | | // ------------------------------------------------------------------------ |
685 | | // AsyncResult Tests (LCP-12) |
686 | | // ------------------------------------------------------------------------ |
687 | | |
688 | | #[test] |
689 | | fn test_async_result_states() { |
690 | | let async_val: AsyncResult<i32, &str> = AsyncResult::Async(42); |
691 | | let sync_val: AsyncResult<i32, &str> = AsyncResult::Sync(42); |
692 | | let err: AsyncResult<i32, &str> = AsyncResult::Error("fail"); |
693 | | |
694 | | assert!(async_val.is_async()); |
695 | | assert!(!async_val.is_sync()); |
696 | | assert!(!async_val.is_error()); |
697 | | |
698 | | assert!(!sync_val.is_async()); |
699 | | assert!(sync_val.is_sync()); |
700 | | assert!(!sync_val.is_error()); |
701 | | |
702 | | assert!(err.is_error()); |
703 | | assert!(!err.is_async()); |
704 | | assert!(!err.is_sync()); |
705 | | |
706 | | assert_eq!(async_val.into_result(), Ok(42)); |
707 | | assert_eq!(sync_val.into_result(), Ok(42)); |
708 | | assert_eq!(err.into_result(), Err("fail")); |
709 | | } |
710 | | |
711 | | #[test] |
712 | | fn test_async_result_map() { |
713 | | let async_val: AsyncResult<i32, &str> = AsyncResult::Async(10); |
714 | | let sync_val: AsyncResult<i32, &str> = AsyncResult::Sync(10); |
715 | | let err: AsyncResult<i32, &str> = AsyncResult::Error("fail"); |
716 | | |
717 | | let mapped_async = async_val.map(|x| x * 2); |
718 | | let mapped_sync = sync_val.map(|x| x * 2); |
719 | | let mapped_err = err.map(|x| x * 2); |
720 | | |
721 | | assert!(matches!(mapped_async, AsyncResult::Async(20))); |
722 | | assert!(matches!(mapped_sync, AsyncResult::Sync(20))); |
723 | | assert!(matches!(mapped_err, AsyncResult::Error("fail"))); |
724 | | } |
725 | | |
726 | | // ------------------------------------------------------------------------ |
727 | | // BoundedQueue Tests (AWP-11) |
728 | | // ------------------------------------------------------------------------ |
729 | | |
730 | | #[test] |
731 | | fn test_bounded_queue_basic() { |
732 | | let mut queue: BoundedQueue<i32> = BoundedQueue::new(5); |
733 | | |
734 | | assert!(queue.is_empty()); |
735 | | assert!(!queue.is_full()); |
736 | | |
737 | | queue.try_push(1).unwrap(); |
738 | | queue.try_push(2).unwrap(); |
739 | | queue.try_push(3).unwrap(); |
740 | | |
741 | | assert_eq!(queue.len(), 3); |
742 | | assert_eq!(queue.pop(), Some(1)); |
743 | | assert_eq!(queue.pop(), Some(2)); |
744 | | assert_eq!(queue.len(), 1); |
745 | | } |
746 | | |
747 | | #[test] |
748 | | fn test_bounded_queue_backpressure() { |
749 | | let mut queue: BoundedQueue<i32> = BoundedQueue::new(3); |
750 | | |
751 | | assert!(queue.try_push(1).is_ok()); |
752 | | assert!(queue.try_push(2).is_ok()); |
753 | | assert!(queue.try_push(3).is_ok()); |
754 | | assert!(queue.is_full()); |
755 | | |
756 | | assert!(queue.try_push(4).is_err()); |
757 | | |
758 | | queue.pop(); |
759 | | assert!(queue.try_push(4).is_ok()); |
760 | | } |
761 | | |
762 | | #[test] |
763 | | fn test_bounded_queue_comprehensive() { |
764 | | let mut queue: BoundedQueue<String> = BoundedQueue::new(3); |
765 | | |
766 | | // Test peek |
767 | | assert!(queue.peek().is_none()); |
768 | | queue.try_push("first".to_string()).unwrap(); |
769 | | assert_eq!(queue.peek(), Some(&"first".to_string())); |
770 | | |
771 | | // Test remaining |
772 | | assert_eq!(queue.remaining(), 2); |
773 | | queue.try_push("second".to_string()).unwrap(); |
774 | | assert_eq!(queue.remaining(), 1); |
775 | | |
776 | | // Test clear |
777 | | queue.clear(); |
778 | | assert!(queue.is_empty()); |
779 | | assert_eq!(queue.remaining(), 3); |
780 | | } |
781 | | |
782 | | // ------------------------------------------------------------------------ |
783 | | // ReserveStrategy Tests (AWP-13) |
784 | | // ------------------------------------------------------------------------ |
785 | | |
786 | | #[test] |
787 | | fn test_reserve_strategy_variants() { |
788 | | assert_eq!(reserve_capacity(100, ReserveStrategy::Exact), 100); |
789 | | assert_eq!(reserve_capacity(100, ReserveStrategy::Grow50), 150); |
790 | | assert_eq!(reserve_capacity(100, ReserveStrategy::Double), 200); |
791 | | assert_eq!(reserve_capacity(100, ReserveStrategy::PowerOfTwo), 128); |
792 | | } |
793 | | |
794 | | #[test] |
795 | | fn test_reserve_capacity_edge_cases() { |
796 | | assert_eq!(reserve_capacity(0, ReserveStrategy::Exact), 0); |
797 | | assert_eq!(reserve_capacity(0, ReserveStrategy::Double), 0); |
798 | | assert_eq!(reserve_capacity(1, ReserveStrategy::PowerOfTwo), 1); |
799 | | assert_eq!(reserve_capacity(3, ReserveStrategy::PowerOfTwo), 4); |
800 | | } |
801 | | |
802 | | #[test] |
803 | | fn test_strategic_buffer() { |
804 | | let mut buf = StrategicBuffer::with_capacity(10, ReserveStrategy::Double); |
805 | | buf.write(b"hello"); |
806 | | assert_eq!(buf.len(), 5); |
807 | | assert_eq!(buf.as_slice(), b"hello"); |
808 | | |
809 | | buf.write(b" world"); |
810 | | assert_eq!(buf.len(), 11); |
811 | | assert_eq!(buf.as_slice(), b"hello world"); |
812 | | |
813 | | buf.clear(); |
814 | | assert!(buf.is_empty()); |
815 | | } |
816 | | |
817 | | // ------------------------------------------------------------------------ |
818 | | // GraphReuseCounter Tests (LCP-08) |
819 | | // ------------------------------------------------------------------------ |
820 | | |
821 | | #[test] |
822 | | fn test_graph_reuse_counter() { |
823 | | let mut counter = GraphReuseCounter::new(3); |
824 | | |
825 | | assert!(!counter.is_hot()); |
826 | | assert!(!counter.should_cache()); |
827 | | |
828 | | counter.record_use(); |
829 | | counter.record_use(); |
830 | | assert!(!counter.is_hot()); |
831 | | |
832 | | counter.record_use(); |
833 | | assert!(counter.is_hot()); |
834 | | assert!(counter.should_cache()); |
835 | | assert_eq!(counter.count(), 3); |
836 | | |
837 | | counter.reset(); |
838 | | assert!(!counter.is_hot()); |
839 | | assert_eq!(counter.count(), 0); |
840 | | } |
841 | | |
842 | | // ------------------------------------------------------------------------ |
843 | | // DualWakerState Tests (AWP-03) |
844 | | // ------------------------------------------------------------------------ |
845 | | |
846 | | #[test] |
847 | | fn test_dual_waker_state() { |
848 | | let mut state = DualWakerState::new(20, 80); |
849 | | |
850 | | assert!(state.can_produce()); |
851 | | assert!(!state.can_consume()); |
852 | | |
853 | | // Fill above high watermark |
854 | | let decision = state.update_fill(85); |
855 | | assert_eq!(decision, WakeDecision::PauseProducer); |
856 | | assert!(!state.can_produce()); |
857 | | assert!(state.can_consume()); |
858 | | |
859 | | // Drain below low watermark |
860 | | let decision = state.update_fill(15); |
861 | | assert_eq!(decision, WakeDecision::WakeProducer); |
862 | | assert!(state.can_produce()); |
863 | | } |
864 | | |
865 | | #[test] |
866 | | fn test_dual_waker_consumer_wake() { |
867 | | let mut state = DualWakerState::new(20, 80); |
868 | | state.consumer_wait(); |
869 | | |
870 | | // Add data while consumer waiting |
871 | | let decision = state.update_fill(50); |
872 | | assert_eq!(decision, WakeDecision::WakeConsumer); |
873 | | } |
874 | | |
875 | | #[test] |
876 | | fn test_dual_waker_edge_cases() { |
877 | | let mut state = DualWakerState::new(20, 80); |
878 | | |
879 | | state.producer_wait(); |
880 | | state.consumer_wait(); |
881 | | |
882 | | state.producer_woke(); |
883 | | state.consumer_woke(); |
884 | | |
885 | | // Test clamping |
886 | | state.update_fill(200); // Should clamp to 100 |
887 | | assert!(!state.can_produce()); |
888 | | } |
889 | | |
890 | | // ------------------------------------------------------------------------ |
891 | | // StreamCapacity Tests (AWP-04) |
892 | | // ------------------------------------------------------------------------ |
893 | | |
894 | | #[test] |
895 | | fn test_stream_capacity_basic() { |
896 | | let mut cap = StreamCapacity::new(); |
897 | | |
898 | | assert_eq!(cap.available_send(), StreamCapacity::DEFAULT_WINDOW); |
899 | | assert!(!cap.is_blocked()); |
900 | | |
901 | | cap.reserve_send(1000).unwrap(); |
902 | | assert_eq!( |
903 | | cap.available_send(), |
904 | | StreamCapacity::DEFAULT_WINDOW - 1000 |
905 | | ); |
906 | | } |
907 | | |
908 | | #[test] |
909 | | fn test_stream_capacity_blocking() { |
910 | | let mut cap = StreamCapacity::with_initial_window(100); |
911 | | |
912 | | let result = cap.reserve_send(150); |
913 | | assert!(result.is_err()); |
914 | | assert!(cap.is_blocked()); |
915 | | |
916 | | cap.release_send(100); |
917 | | assert!(!cap.is_blocked()); |
918 | | } |
919 | | |
920 | | #[test] |
921 | | fn test_stream_capacity_window_ops() { |
922 | | let mut cap = StreamCapacity::new(); |
923 | | // Default window is 65535, needs_window_update when < 32767 |
924 | | |
925 | | // Consume more than half to trigger update |
926 | | cap.consume_receive(40000); |
927 | | assert!(cap.needs_window_update()); // 25535 < 32767 |
928 | | |
929 | | cap.replenish_receive(20000); |
930 | | assert!(!cap.needs_window_update()); // 45535 > 32767 |
931 | | } |
932 | | |
933 | | #[test] |
934 | | fn test_flow_control_error() { |
935 | | let err1 = FlowControlError::NegativeReservation; |
936 | | let err2 = FlowControlError::InsufficientCapacity { |
937 | | requested: 100, |
938 | | available: 50, |
939 | | }; |
940 | | |
941 | | assert_eq!(err1, FlowControlError::NegativeReservation); |
942 | | assert!(matches!( |
943 | | err2, |
944 | | FlowControlError::InsufficientCapacity { .. } |
945 | | )); |
946 | | } |
947 | | |
948 | | // ------------------------------------------------------------------------ |
949 | | // WakeSkipState Tests (AWP-09) |
950 | | // ------------------------------------------------------------------------ |
951 | | |
952 | | #[test] |
953 | | fn test_wake_skip_state() { |
954 | | let mut state = WakeSkipState::new(3); |
955 | | |
956 | | // No waker = always skip |
957 | | assert!(state.should_skip_wake()); |
958 | | |
959 | | state.register_waker(); |
960 | | assert!(!state.should_skip_wake()); |
961 | | |
962 | | // Accumulate empty polls |
963 | | state.record_poll(false); |
964 | | state.record_poll(false); |
965 | | state.record_poll(false); |
966 | | assert!(state.should_skip_wake()); |
967 | | |
968 | | // Reset tracking |
969 | | state.reset_tracking(); |
970 | | assert!(!state.should_skip_wake()); |
971 | | } |
972 | | |
973 | | #[test] |
974 | | fn test_wake_skip_needs_wake() { |
975 | | let mut state = WakeSkipState::new(3); |
976 | | state.register_waker(); |
977 | | |
978 | | // No pending items |
979 | | assert!(!state.needs_wake()); |
980 | | |
981 | | // Add pending |
982 | | state.add_pending(5); |
983 | | assert!(state.needs_wake()); |
984 | | assert_eq!(state.pending(), 5); |
985 | | |
986 | | // Remove some |
987 | | state.remove_pending(3); |
988 | | assert_eq!(state.pending(), 2); |
989 | | } |
990 | | |
991 | | #[test] |
992 | | fn test_wake_skip_tracking() { |
993 | | let mut state = WakeSkipState::new(5); |
994 | | state.register_waker(); |
995 | | |
996 | | // Work resets empty poll count |
997 | | state.record_poll(false); |
998 | | state.record_poll(false); |
999 | | state.record_poll(true); // Had work |
1000 | | state.record_poll(false); |
1001 | | |
1002 | | // Should not skip yet (only 1 empty after work) |
1003 | | assert!(!state.should_skip_wake()); |
1004 | | } |
1005 | | |
1006 | | // ------------------------------------------------------------------------ |
1007 | | // Falsification Tests |
1008 | | // ------------------------------------------------------------------------ |
1009 | | |
1010 | | /// FALSIFICATION: BoundedQueue capacity invariant. |
1011 | | /// Queue must never exceed its capacity. |
1012 | | #[test] |
1013 | | fn test_falsify_bounded_queue_capacity_invariant() { |
1014 | | for cap in [1, 5, 10, 100] { |
1015 | | let mut queue: BoundedQueue<usize> = BoundedQueue::new(cap); |
1016 | | |
1017 | | // Try to push more than capacity |
1018 | | for i in 0..cap * 2 { |
1019 | | let _ = queue.try_push(i); |
1020 | | } |
1021 | | |
1022 | | assert!( |
1023 | | queue.len() <= cap, |
1024 | | "FALSIFICATION FAILED: Queue exceeded capacity {} with len {}", |
1025 | | cap, |
1026 | | queue.len() |
1027 | | ); |
1028 | | } |
1029 | | } |
1030 | | |
1031 | | /// FALSIFICATION: ReserveStrategy must always return >= needed. |
1032 | | #[test] |
1033 | | fn test_falsify_reserve_strategy_minimum() { |
1034 | | for needed in [0, 1, 10, 100, 1000, 10000] { |
1035 | | for strategy in [ |
1036 | | ReserveStrategy::Exact, |
1037 | | ReserveStrategy::Grow50, |
1038 | | ReserveStrategy::Double, |
1039 | | ReserveStrategy::PowerOfTwo, |
1040 | | ] { |
1041 | | let reserved = reserve_capacity(needed, strategy); |
1042 | | assert!( |
1043 | | reserved >= needed, |
1044 | | "FALSIFICATION FAILED: reserve_capacity({}, {:?}) = {} < {}", |
1045 | | needed, |
1046 | | strategy, |
1047 | | reserved, |
1048 | | needed |
1049 | | ); |
1050 | | } |
1051 | | } |
1052 | | } |
1053 | | |
1054 | | /// FALSIFICATION: GraphReuseCounter hot transition. |
1055 | | /// Must become hot exactly at threshold. |
1056 | | #[test] |
1057 | | fn test_falsify_graph_reuse_threshold() { |
1058 | | for threshold in [1, 5, 10, 100] { |
1059 | | let mut counter = GraphReuseCounter::new(threshold); |
1060 | | |
1061 | | // Before threshold |
1062 | | for _ in 0..threshold - 1 { |
1063 | | counter.record_use(); |
1064 | | assert!( |
1065 | | !counter.is_hot(), |
1066 | | "FALSIFICATION FAILED: Became hot before threshold {}", |
1067 | | threshold |
1068 | | ); |
1069 | | } |
1070 | | |
1071 | | // At threshold |
1072 | | counter.record_use(); |
1073 | | assert!( |
1074 | | counter.is_hot(), |
1075 | | "FALSIFICATION FAILED: Not hot at threshold {}", |
1076 | | threshold |
1077 | | ); |
1078 | | } |
1079 | | } |
1080 | | |
1081 | | /// FALSIFICATION: StreamCapacity window consistency. |
1082 | | #[test] |
1083 | | fn test_falsify_stream_capacity_consistency() { |
1084 | | let mut cap = StreamCapacity::new(); |
1085 | | let initial = cap.available_send(); |
1086 | | |
1087 | | // Reserve and release should return to initial |
1088 | | cap.reserve_send(10000).unwrap(); |
1089 | | cap.release_send(10000); |
1090 | | assert_eq!( |
1091 | | cap.available_send(), |
1092 | | initial, |
1093 | | "FALSIFICATION FAILED: Window not restored after reserve+release" |
1094 | | ); |
1095 | | } |
1096 | | } |