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