Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/trueno/src/brick/connection.rs
Line
Count
Source
1
//! Connection Management
2
//!
3
//! AWP-06: Managed connections with TTL, idle timeout, and health tracking.
4
//! AWP-10: Keep-Alive normalization for HTTP connections.
5
//! AWP-12: Bitflags connection state for efficient state tracking.
6
7
use std::time::{Duration, Instant};
8
9
// ----------------------------------------------------------------------------
10
// AWP-06: Connection TTL + Health Check
11
// ----------------------------------------------------------------------------
12
13
/// Connection with TTL and health tracking.
14
#[derive(Debug)]
15
pub struct ManagedConnection<T> {
16
    /// The underlying connection
17
    inner: T,
18
    /// When the connection was created
19
    created_at: Instant,
20
    /// When the connection was last used
21
    last_used: Instant,
22
    /// Maximum lifetime (TTL)
23
    max_lifetime: Duration,
24
    /// Maximum idle time
25
    max_idle: Duration,
26
    /// Health check failures
27
    health_failures: usize,
28
}
29
30
impl<T> ManagedConnection<T> {
31
    /// Create a new managed connection.
32
0
    pub fn new(inner: T, max_lifetime: Duration, max_idle: Duration) -> Self {
33
0
        let now = Instant::now();
34
0
        Self {
35
0
            inner,
36
0
            created_at: now,
37
0
            last_used: now,
38
0
            max_lifetime,
39
0
            max_idle,
40
0
            health_failures: 0,
41
0
        }
42
0
    }
43
44
    /// Check if the connection is still valid.
45
    #[must_use]
46
0
    pub fn is_valid(&self) -> bool {
47
0
        let now = Instant::now();
48
0
        let not_expired = now.duration_since(self.created_at) < self.max_lifetime;
49
0
        let not_idle = now.duration_since(self.last_used) < self.max_idle;
50
0
        let healthy = self.health_failures < 3;
51
0
        not_expired && not_idle && healthy
52
0
    }
53
54
    /// Check if the connection has expired (TTL exceeded).
55
    #[must_use]
56
0
    pub fn is_expired(&self) -> bool {
57
0
        self.created_at.elapsed() >= self.max_lifetime
58
0
    }
59
60
    /// Check if the connection is idle.
61
    #[must_use]
62
0
    pub fn is_idle(&self) -> bool {
63
0
        self.last_used.elapsed() >= self.max_idle
64
0
    }
65
66
    /// Mark the connection as used.
67
0
    pub fn touch(&mut self) {
68
0
        self.last_used = Instant::now();
69
0
    }
70
71
    /// Record a health check failure.
72
0
    pub fn record_health_failure(&mut self) {
73
0
        self.health_failures += 1;
74
0
    }
75
76
    /// Reset health failure count.
77
0
    pub fn reset_health(&mut self) {
78
0
        self.health_failures = 0;
79
0
    }
80
81
    /// Get the underlying connection.
82
0
    pub fn inner(&self) -> &T {
83
0
        &self.inner
84
0
    }
85
86
    /// Get mutable access to the underlying connection.
87
0
    pub fn inner_mut(&mut self) -> &mut T {
88
0
        &mut self.inner
89
0
    }
90
91
    /// Consume and return the underlying connection.
92
0
    pub fn into_inner(self) -> T {
93
0
        self.inner
94
0
    }
95
96
    /// Get connection age.
97
    #[must_use]
98
0
    pub fn age(&self) -> Duration {
99
0
        self.created_at.elapsed()
100
0
    }
101
102
    /// Get idle time.
103
    #[must_use]
104
0
    pub fn idle_time(&self) -> Duration {
105
0
        self.last_used.elapsed()
106
0
    }
107
108
    /// Get health failure count (for testing/diagnostics).
109
    #[must_use]
110
0
    pub fn health_failures(&self) -> usize {
111
0
        self.health_failures
112
0
    }
113
}
114
115
// ----------------------------------------------------------------------------
116
// AWP-10: Keep-Alive Normalization
117
// ----------------------------------------------------------------------------
118
119
/// Normalized keep-alive configuration.
120
///
121
/// Canonicalizes various keep-alive settings into a standard form.
122
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123
pub struct KeepAliveConfig {
124
    /// Whether keep-alive is enabled
125
    pub enabled: bool,
126
    /// Timeout duration in seconds
127
    pub timeout_secs: u32,
128
    /// Maximum number of requests per connection
129
    pub max_requests: u32,
130
}
131
132
impl KeepAliveConfig {
133
    /// Create with default values.
134
0
    pub fn new() -> Self {
135
0
        Self {
136
0
            enabled: true,
137
0
            timeout_secs: 60,
138
0
            max_requests: 100,
139
0
        }
140
0
    }
141
142
    /// Disabled keep-alive.
143
0
    pub fn disabled() -> Self {
144
0
        Self {
145
0
            enabled: false,
146
0
            timeout_secs: 0,
147
0
            max_requests: 0,
148
0
        }
149
0
    }
150
151
    /// Parse from HTTP header value (e.g., "timeout=5, max=100").
152
0
    pub fn from_header(header: &str) -> Self {
153
0
        let mut config = Self::new();
154
155
0
        for part in header.split(',') {
156
0
            let part = part.trim();
157
0
            if let Some((key, val)) = part.split_once('=') {
158
0
                let key = key.trim().to_lowercase();
159
0
                let val = val.trim();
160
161
0
                match key.as_str() {
162
0
                    "timeout" => {
163
0
                        if let Ok(t) = val.parse() {
164
0
                            config.timeout_secs = t;
165
0
                        }
166
                    }
167
0
                    "max" => {
168
0
                        if let Ok(m) = val.parse() {
169
0
                            config.max_requests = m;
170
0
                        }
171
                    }
172
0
                    _ => {}
173
                }
174
0
            }
175
        }
176
177
0
        config
178
0
    }
179
180
    /// Check if connection should be kept alive after n requests.
181
    #[must_use]
182
0
    pub fn should_keep_alive(&self, request_count: u32) -> bool {
183
0
        self.enabled && request_count < self.max_requests
184
0
    }
185
}
186
187
impl Default for KeepAliveConfig {
188
0
    fn default() -> Self {
189
0
        Self::new()
190
0
    }
191
}
192
193
// ----------------------------------------------------------------------------
194
// AWP-12: Bitflags Connection State
195
// ----------------------------------------------------------------------------
196
197
/// Compact connection state using bitflags.
198
///
199
/// Efficiently represents multiple boolean states in a single byte.
200
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
201
pub struct ConnectionState(u8);
202
203
impl ConnectionState {
204
    /// Connection is open
205
    pub const OPEN: u8 = 0b0000_0001;
206
    /// Connection is readable
207
    pub const READABLE: u8 = 0b0000_0010;
208
    /// Connection is writable
209
    pub const WRITABLE: u8 = 0b0000_0100;
210
    /// Connection has pending data
211
    pub const HAS_PENDING: u8 = 0b0000_1000;
212
    /// Connection is in keep-alive mode
213
    pub const KEEP_ALIVE: u8 = 0b0001_0000;
214
    /// Connection upgrade requested (e.g., WebSocket)
215
    pub const UPGRADE: u8 = 0b0010_0000;
216
    /// Connection is closing
217
    pub const CLOSING: u8 = 0b0100_0000;
218
    /// Connection has error
219
    pub const ERROR: u8 = 0b1000_0000;
220
221
    /// Create new state with no flags set.
222
    #[must_use]
223
0
    pub fn new() -> Self {
224
0
        Self(0)
225
0
    }
226
227
    /// Create state with initial open + writable.
228
    #[must_use]
229
0
    pub fn open_connection() -> Self {
230
0
        Self(Self::OPEN | Self::WRITABLE)
231
0
    }
232
233
    /// Set a flag.
234
0
    pub fn set(&mut self, flag: u8) {
235
0
        self.0 |= flag;
236
0
    }
237
238
    /// Clear a flag.
239
0
    pub fn clear(&mut self, flag: u8) {
240
0
        self.0 &= !flag;
241
0
    }
242
243
    /// Check if flag is set.
244
    #[must_use]
245
0
    pub fn is_set(&self, flag: u8) -> bool {
246
0
        self.0 & flag != 0
247
0
    }
248
249
    /// Check if connection is open and healthy.
250
    #[must_use]
251
0
    pub fn is_healthy(&self) -> bool {
252
0
        self.is_set(Self::OPEN) && !self.is_set(Self::ERROR) && !self.is_set(Self::CLOSING)
253
0
    }
254
255
    /// Check if connection can read.
256
    #[must_use]
257
0
    pub fn can_read(&self) -> bool {
258
0
        self.is_set(Self::OPEN) && self.is_set(Self::READABLE)
259
0
    }
260
261
    /// Check if connection can write.
262
    #[must_use]
263
0
    pub fn can_write(&self) -> bool {
264
0
        self.is_set(Self::OPEN) && self.is_set(Self::WRITABLE) && !self.is_set(Self::CLOSING)
265
0
    }
266
267
    /// Get raw bits.
268
    #[must_use]
269
0
    pub fn bits(&self) -> u8 {
270
0
        self.0
271
0
    }
272
}
273
274
#[cfg(test)]
275
mod tests {
276
    use super::*;
277
278
    #[test]
279
    fn test_managed_connection_new() {
280
        let conn = ManagedConnection::new(
281
            "test_connection",
282
            Duration::from_secs(60),
283
            Duration::from_secs(10),
284
        );
285
286
        assert_eq!(conn.inner(), &"test_connection");
287
        assert!(conn.is_valid());
288
        assert!(!conn.is_expired());
289
        assert!(!conn.is_idle());
290
    }
291
292
    #[test]
293
    fn test_managed_connection_inner_mut() {
294
        let mut conn = ManagedConnection::new(
295
            vec![1, 2, 3],
296
            Duration::from_secs(60),
297
            Duration::from_secs(10),
298
        );
299
300
        conn.inner_mut().push(4);
301
        assert_eq!(conn.inner(), &vec![1, 2, 3, 4]);
302
    }
303
304
    #[test]
305
    fn test_managed_connection_into_inner() {
306
        let conn = ManagedConnection::new(42u32, Duration::from_secs(60), Duration::from_secs(10));
307
308
        let value = conn.into_inner();
309
        assert_eq!(value, 42);
310
    }
311
312
    #[test]
313
    fn test_managed_connection_touch() {
314
        let mut conn = ManagedConnection::new(
315
            "test",
316
            Duration::from_secs(60),
317
            Duration::from_millis(10),
318
        );
319
320
        std::thread::sleep(Duration::from_millis(5));
321
        let idle_before = conn.idle_time();
322
323
        conn.touch();
324
        let idle_after = conn.idle_time();
325
326
        assert!(idle_after < idle_before);
327
    }
328
329
    #[test]
330
    fn test_managed_connection_health_failures() {
331
        let mut conn =
332
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_secs(10));
333
334
        assert!(conn.is_valid());
335
336
        conn.record_health_failure();
337
        conn.record_health_failure();
338
        assert!(conn.is_valid()); // 2 failures, still valid
339
340
        conn.record_health_failure();
341
        assert!(!conn.is_valid()); // 3 failures, now invalid
342
343
        conn.reset_health();
344
        assert!(conn.is_valid()); // Reset, valid again
345
    }
346
347
    #[test]
348
    fn test_managed_connection_age() {
349
        let conn =
350
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_secs(10));
351
352
        let age = conn.age();
353
        assert!(age < Duration::from_millis(100));
354
    }
355
356
    #[test]
357
    fn test_managed_connection_ttl_expiry() {
358
        let conn =
359
            ManagedConnection::new("test", Duration::from_millis(10), Duration::from_secs(60));
360
361
        assert!(!conn.is_expired());
362
363
        std::thread::sleep(Duration::from_millis(15));
364
365
        assert!(conn.is_expired());
366
        assert!(!conn.is_valid());
367
    }
368
369
    #[test]
370
    fn test_managed_connection_idle_expiry() {
371
        let conn =
372
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_millis(10));
373
374
        assert!(!conn.is_idle());
375
376
        std::thread::sleep(Duration::from_millis(15));
377
378
        assert!(conn.is_idle());
379
        assert!(!conn.is_valid());
380
    }
381
382
    /// FALSIFICATION TEST: Verify all three conditions must pass for validity
383
    ///
384
    /// A connection is valid only if: not_expired AND not_idle AND healthy.
385
    /// If any one fails, the connection should be invalid.
386
    #[test]
387
    fn test_falsify_validity_requires_all_conditions() {
388
        // Test 1: Healthy and not idle, but expired
389
        let expired = ManagedConnection::new(
390
            "test",
391
            Duration::from_millis(5),
392
            Duration::from_secs(60),
393
        );
394
        std::thread::sleep(Duration::from_millis(10));
395
        assert!(
396
            !expired.is_valid(),
397
            "FALSIFICATION FAILED: Expired connection should be invalid"
398
        );
399
400
        // Test 2: Healthy and not expired, but idle
401
        let idle = ManagedConnection::new(
402
            "test",
403
            Duration::from_secs(60),
404
            Duration::from_millis(5),
405
        );
406
        std::thread::sleep(Duration::from_millis(10));
407
        assert!(
408
            !idle.is_valid(),
409
            "FALSIFICATION FAILED: Idle connection should be invalid"
410
        );
411
412
        // Test 3: Not expired and not idle, but unhealthy
413
        let mut unhealthy =
414
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_secs(60));
415
        unhealthy.record_health_failure();
416
        unhealthy.record_health_failure();
417
        unhealthy.record_health_failure();
418
        assert!(
419
            !unhealthy.is_valid(),
420
            "FALSIFICATION FAILED: Unhealthy connection should be invalid"
421
        );
422
423
        // Test 4: All conditions pass
424
        let valid =
425
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_secs(60));
426
        assert!(
427
            valid.is_valid(),
428
            "FALSIFICATION FAILED: Fresh connection should be valid"
429
        );
430
    }
431
432
    /// FALSIFICATION TEST: Touch must reset idle timer
433
    ///
434
    /// Uses 200ms timeout with 100ms sleeps to provide 100ms margin against timing jitter.
435
    /// Previous 20ms/15ms (5ms margin) caused flaky failures on Mac.
436
    #[test]
437
    fn test_falsify_touch_resets_idle() {
438
        let mut conn =
439
            ManagedConnection::new("test", Duration::from_secs(60), Duration::from_millis(200));
440
441
        // Wait until almost idle (100ms of 200ms timeout = 50% margin)
442
        std::thread::sleep(Duration::from_millis(100));
443
        assert!(
444
            !conn.is_idle(),
445
            "Should not be idle yet at 100ms with 200ms timeout"
446
        );
447
448
        // Touch to reset
449
        conn.touch();
450
451
        // Wait another 100ms (would be 200ms total without touch, but only 100ms since touch)
452
        std::thread::sleep(Duration::from_millis(100));
453
        assert!(
454
            !conn.is_idle(),
455
            "FALSIFICATION FAILED: Touch should have reset idle timer"
456
        );
457
458
        // Now wait until actually idle (another 150ms = 250ms since touch > 200ms timeout)
459
        std::thread::sleep(Duration::from_millis(150));
460
        assert!(conn.is_idle(), "Should be idle now (250ms since touch > 200ms timeout)");
461
    }
462
463
    // =========================================================================
464
    // KeepAliveConfig Tests
465
    // =========================================================================
466
467
    #[test]
468
    fn test_keep_alive_config_new() {
469
        let config = KeepAliveConfig::new();
470
        assert!(config.enabled);
471
        assert_eq!(config.timeout_secs, 60);
472
        assert_eq!(config.max_requests, 100);
473
    }
474
475
    #[test]
476
    fn test_keep_alive_config_disabled() {
477
        let config = KeepAliveConfig::disabled();
478
        assert!(!config.enabled);
479
        assert_eq!(config.timeout_secs, 0);
480
        assert_eq!(config.max_requests, 0);
481
    }
482
483
    #[test]
484
    fn test_keep_alive_config_from_header() {
485
        let config = KeepAliveConfig::from_header("timeout=30, max=50");
486
        assert!(config.enabled);
487
        assert_eq!(config.timeout_secs, 30);
488
        assert_eq!(config.max_requests, 50);
489
    }
490
491
    #[test]
492
    fn test_keep_alive_config_from_header_partial() {
493
        // Only timeout specified
494
        let config = KeepAliveConfig::from_header("timeout=15");
495
        assert_eq!(config.timeout_secs, 15);
496
        assert_eq!(config.max_requests, 100); // Default
497
498
        // Only max specified
499
        let config = KeepAliveConfig::from_header("max=25");
500
        assert_eq!(config.timeout_secs, 60); // Default
501
        assert_eq!(config.max_requests, 25);
502
    }
503
504
    #[test]
505
    fn test_keep_alive_config_from_header_invalid() {
506
        // Invalid values are ignored
507
        let config = KeepAliveConfig::from_header("timeout=abc, max=xyz");
508
        assert_eq!(config.timeout_secs, 60); // Default
509
        assert_eq!(config.max_requests, 100); // Default
510
    }
511
512
    #[test]
513
    fn test_keep_alive_config_should_keep_alive() {
514
        let config = KeepAliveConfig::new(); // max_requests = 100
515
        assert!(config.should_keep_alive(0));
516
        assert!(config.should_keep_alive(50));
517
        assert!(config.should_keep_alive(99));
518
        assert!(!config.should_keep_alive(100));
519
        assert!(!config.should_keep_alive(200));
520
    }
521
522
    #[test]
523
    fn test_keep_alive_config_should_keep_alive_disabled() {
524
        let config = KeepAliveConfig::disabled();
525
        assert!(!config.should_keep_alive(0));
526
    }
527
528
    #[test]
529
    fn test_keep_alive_config_default() {
530
        let config = KeepAliveConfig::default();
531
        assert_eq!(config, KeepAliveConfig::new());
532
    }
533
534
    // =========================================================================
535
    // ConnectionState Tests
536
    // =========================================================================
537
538
    #[test]
539
    fn test_connection_state_new() {
540
        let state = ConnectionState::new();
541
        assert_eq!(state.bits(), 0);
542
        assert!(!state.is_set(ConnectionState::OPEN));
543
    }
544
545
    #[test]
546
    fn test_connection_state_open_connection() {
547
        let state = ConnectionState::open_connection();
548
        assert!(state.is_set(ConnectionState::OPEN));
549
        assert!(state.is_set(ConnectionState::WRITABLE));
550
        assert!(!state.is_set(ConnectionState::READABLE));
551
    }
552
553
    #[test]
554
    fn test_connection_state_set_clear() {
555
        let mut state = ConnectionState::new();
556
557
        state.set(ConnectionState::OPEN);
558
        assert!(state.is_set(ConnectionState::OPEN));
559
560
        state.set(ConnectionState::READABLE);
561
        assert!(state.is_set(ConnectionState::OPEN));
562
        assert!(state.is_set(ConnectionState::READABLE));
563
564
        state.clear(ConnectionState::OPEN);
565
        assert!(!state.is_set(ConnectionState::OPEN));
566
        assert!(state.is_set(ConnectionState::READABLE));
567
    }
568
569
    #[test]
570
    fn test_connection_state_is_healthy() {
571
        let mut state = ConnectionState::open_connection();
572
        assert!(state.is_healthy());
573
574
        state.set(ConnectionState::ERROR);
575
        assert!(!state.is_healthy());
576
577
        state.clear(ConnectionState::ERROR);
578
        state.set(ConnectionState::CLOSING);
579
        assert!(!state.is_healthy());
580
    }
581
582
    #[test]
583
    fn test_connection_state_can_read() {
584
        let mut state = ConnectionState::open_connection();
585
        assert!(!state.can_read()); // Not readable yet
586
587
        state.set(ConnectionState::READABLE);
588
        assert!(state.can_read());
589
590
        state.clear(ConnectionState::OPEN);
591
        assert!(!state.can_read()); // Not open
592
    }
593
594
    #[test]
595
    fn test_connection_state_can_write() {
596
        let mut state = ConnectionState::open_connection();
597
        assert!(state.can_write());
598
599
        state.set(ConnectionState::CLOSING);
600
        assert!(!state.can_write()); // Can't write when closing
601
602
        state.clear(ConnectionState::CLOSING);
603
        state.clear(ConnectionState::WRITABLE);
604
        assert!(!state.can_write()); // Not writable
605
    }
606
607
    #[test]
608
    fn test_connection_state_bits() {
609
        let mut state = ConnectionState::new();
610
        state.set(ConnectionState::OPEN);
611
        state.set(ConnectionState::WRITABLE);
612
        assert_eq!(state.bits(), 0b0000_0101);
613
    }
614
615
    #[test]
616
    fn test_connection_state_default() {
617
        let state = ConnectionState::default();
618
        assert_eq!(state.bits(), 0);
619
    }
620
621
    /// FALSIFICATION TEST: ConnectionState bitflags must be non-overlapping
622
    #[test]
623
    fn test_falsify_connection_state_flags_unique() {
624
        let flags = [
625
            ConnectionState::OPEN,
626
            ConnectionState::READABLE,
627
            ConnectionState::WRITABLE,
628
            ConnectionState::HAS_PENDING,
629
            ConnectionState::KEEP_ALIVE,
630
            ConnectionState::UPGRADE,
631
            ConnectionState::CLOSING,
632
            ConnectionState::ERROR,
633
        ];
634
635
        // Each flag must be a power of 2 (single bit set)
636
        for flag in &flags {
637
            assert!(
638
                flag.is_power_of_two(),
639
                "FALSIFICATION FAILED: Flag {:08b} is not a power of 2",
640
                flag
641
            );
642
        }
643
644
        // No two flags should overlap
645
        for i in 0..flags.len() {
646
            for j in (i + 1)..flags.len() {
647
                assert_eq!(
648
                    flags[i] & flags[j],
649
                    0,
650
                    "FALSIFICATION FAILED: Flags {:08b} and {:08b} overlap",
651
                    flags[i],
652
                    flags[j]
653
                );
654
            }
655
        }
656
    }
657
658
    /// FALSIFICATION TEST: KeepAliveConfig should_keep_alive boundary
659
    #[test]
660
    fn test_falsify_keep_alive_boundary() {
661
        let config = KeepAliveConfig {
662
            enabled: true,
663
            timeout_secs: 60,
664
            max_requests: 100,
665
        };
666
667
        // At exactly max_requests, should NOT keep alive
668
        assert!(
669
            !config.should_keep_alive(100),
670
            "FALSIFICATION FAILED: should_keep_alive should return false at max_requests"
671
        );
672
673
        // One below max should still be true
674
        assert!(
675
            config.should_keep_alive(99),
676
            "FALSIFICATION FAILED: should_keep_alive should return true below max_requests"
677
        );
678
    }
679
}