1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! at startup, verify accounts hash in the background
use {
    crate::waitable_condvar::WaitableCondvar,
    std::{
        sync::{
            atomic::{AtomicBool, Ordering},
            Arc, Mutex,
        },
        thread::JoinHandle,
        time::Duration,
    },
};

#[derive(Debug)]
pub(crate) struct VerifyAccountsHashInBackground {
    /// true when verification has completed or never had to run in background
    pub(crate) verified: Arc<AtomicBool>,
    /// enable waiting for verification to become complete
    complete: Arc<WaitableCondvar>,
    /// thread doing verification
    thread: Mutex<Option<JoinHandle<bool>>>,
    /// set when background thread has completed
    background_completed: Arc<AtomicBool>,
}

impl Default for VerifyAccountsHashInBackground {
    fn default() -> Self {
        // initialize, expecting possible background verification to be started
        Self {
            complete: Arc::default(),
            // with default initialization, 'verified' is false
            verified: Arc::new(AtomicBool::new(false)),
            // no thread to start with
            thread: Mutex::new(None::<JoinHandle<bool>>),
            background_completed: Arc::new(AtomicBool::new(false)),
        }
    }
}

impl VerifyAccountsHashInBackground {
    /// start the bg thread to do the verification
    pub(crate) fn start(&self, start: impl FnOnce() -> JoinHandle<bool>) {
        // note that we're not verified before
        self.verified.store(false, Ordering::Release);
        *self.thread.lock().unwrap() = Some(start());
    }

    /// notify that the bg process has completed
    pub(crate) fn background_finished(&self) {
        self.complete.notify_all();
        self.background_completed.store(true, Ordering::Release);
    }

    /// notify that verification was completed successfully
    /// This can occur because it completed in the background
    /// or if the verification was run in the foreground.
    pub(crate) fn verification_complete(&self) {
        self.verified.store(true, Ordering::Release);
    }

    /// block until bg process is complete
    pub fn wait_for_complete(&self) {
        // just now completing
        let mut lock = self.thread.lock().unwrap();
        if lock.is_none() {
            return; // nothing to do
        }
        let result = lock.take().unwrap().join().unwrap();
        if !result {
            panic!("initial hash verification failed: {result:?}");
        }
        // we never have to check again
        self.verification_complete();
    }

    /// return true if bg hash verification is complete
    /// return false if bg hash verification has not completed yet
    /// if hash verification failed, a panic will occur
    pub(crate) fn check_complete(&self) -> bool {
        if self.verified.load(Ordering::Acquire) {
            // already completed
            return true;
        }
        if self.complete.wait_timeout(Duration::default())
            && !self.background_completed.load(Ordering::Acquire)
        {
            // timed out, so not complete
            false
        } else {
            // Did not time out, so thread finished. Join it.
            self.wait_for_complete();
            true
        }
    }
}

#[cfg(test)]
pub(crate) mod tests {
    use {super::*, std::thread::Builder};

    #[test]
    fn test_default() {
        let def = VerifyAccountsHashInBackground::default();
        assert!(!def.check_complete());
        assert!(!def.verified.load(Ordering::Acquire));
        assert!(def.thread.lock().unwrap().is_none());
        def.verification_complete();
        assert!(def.check_complete());
    }

    fn start_thread_and_return(
        verify: &Arc<VerifyAccountsHashInBackground>,
        result: bool,
        action: impl FnOnce() + Send + 'static,
    ) {
        assert!(!verify.check_complete());
        let verify_ = Arc::clone(verify);
        verify.start(|| {
            Builder::new()
                .name("solBgHashVerfy".to_string())
                .spawn(move || {
                    // should have been marked not complete before thread started
                    assert!(!verify_.check_complete());
                    action();
                    verify_.background_finished();
                    result
                })
                .unwrap()
        });
    }

    #[test]
    fn test_real() {
        solana_logger::setup();
        let verify = Arc::new(VerifyAccountsHashInBackground::default());
        start_thread_and_return(&verify, true, || {});
        verify.wait_for_complete();
        assert!(verify.check_complete());
    }

    #[test]
    #[should_panic(expected = "initial hash verification failed")]
    fn test_panic() {
        let verify = Arc::new(VerifyAccountsHashInBackground::default());
        start_thread_and_return(&verify, false, || {});
        verify.wait_for_complete();
        assert!(!verify.check_complete());
    }

    #[test]
    fn test_long_running() {
        solana_logger::setup();
        let verify = Arc::new(VerifyAccountsHashInBackground::default());
        let finish = Arc::new(AtomicBool::default());
        let finish_ = finish.clone();
        start_thread_and_return(&verify, true, move || {
            // busy wait until atomic is set
            while !finish_.load(Ordering::Relaxed) {}
        });
        assert!(!verify.check_complete());
        finish.store(true, Ordering::Relaxed);
        verify.wait_for_complete();
        assert!(verify.check_complete());
    }
}