Skip to main content

wavecraft_dev_server/reload/
guard.rs

1//! Concurrency control for rebuild operations
2//!
3//! Ensures only one build runs at a time, with at most one pending.
4//! Uses atomics for lock-free coordination between watcher and builder.
5
6use std::sync::atomic::{AtomicBool, Ordering};
7
8/// Concurrency control for rebuild operations.
9///
10/// Ensures only one build runs at a time, with at most one pending.
11/// Uses atomics for lock-free coordination between watcher and builder.
12pub struct BuildGuard {
13    building: AtomicBool,
14    pending: AtomicBool,
15}
16
17impl BuildGuard {
18    pub fn new() -> Self {
19        Self {
20            building: AtomicBool::new(false),
21            pending: AtomicBool::new(false),
22        }
23    }
24
25    /// Try to start a build. Returns true if acquired.
26    pub fn try_start(&self) -> bool {
27        self.building
28            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
29            .is_ok()
30    }
31
32    /// Mark a pending rebuild request (received during active build).
33    pub fn mark_pending(&self) {
34        self.pending.store(true, Ordering::SeqCst);
35    }
36
37    /// Complete current build. Returns true if a pending build should start.
38    pub fn complete(&self) -> bool {
39        self.building.store(false, Ordering::SeqCst);
40        self.pending.swap(false, Ordering::SeqCst)
41    }
42}
43
44impl Default for BuildGuard {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_build_guard_single_build() {
56        let guard = BuildGuard::new();
57
58        // First try_start should succeed
59        assert!(guard.try_start());
60
61        // Second try_start should fail (build in progress)
62        assert!(!guard.try_start());
63
64        // Complete without pending should return false
65        assert!(!guard.complete());
66
67        // After complete, try_start should succeed again
68        assert!(guard.try_start());
69        guard.complete();
70    }
71
72    #[test]
73    fn test_build_guard_pending() {
74        let guard = BuildGuard::new();
75
76        // Start a build
77        assert!(guard.try_start());
78
79        // Try to start another (should fail)
80        assert!(!guard.try_start());
81
82        // Mark as pending
83        guard.mark_pending();
84
85        // Complete should return true (pending build)
86        assert!(guard.complete());
87
88        // Now try_start should succeed (for the pending build)
89        assert!(guard.try_start());
90        assert!(!guard.complete());
91    }
92
93    #[test]
94    fn test_build_guard_multiple_pending() {
95        let guard = BuildGuard::new();
96
97        // Start a build
98        assert!(guard.try_start());
99
100        // Mark pending multiple times (only one pending should be stored)
101        guard.mark_pending();
102        guard.mark_pending();
103        guard.mark_pending();
104
105        // Complete should return true once
106        assert!(guard.complete());
107
108        // Second complete should return false (no more pending)
109        assert!(!guard.complete());
110    }
111}