oxedyne/fe2o3/fe2o3_core/src/thread.rs
5.0 KiB, 7 runs
created by r1870400018:124, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Provides a simple boolean channel for threads. Full credit to [Denis |
| 2 | //! Kolodin](https://stackoverflow.com/questions/35883390/how-to-check-if-a-thread-has-finished-in-rust) |
| 3 | //! and his [`thread-control`](https://crates.io/crates/thread-control) crate. I've started mainly |
| 4 | //! by renaming a few things. There is no way in vanilla Rust to know when a thread has ended, |
| 5 | //! given that operating system threads are used. The basic idea of this nice workaround is to share a reference |
| 6 | //! counted pointer to a boolean, the "semaphore" created in the parent thread, with the child |
| 7 | //! thread. A non-owning reference to the semaphore, a "sentinel" is also created and can be used |
| 8 | //! in the parent thread to change the value of the boolean, or to detect when it has been dropped, |
| 9 | //! which we assume coincides with the completion of the child thread. |
| 10 | use crate::{ |
| 11 | channels::Simplex, |
| 12 | }; |
| 13 | |
| 14 | use std::{ |
| 15 | sync::{ |
| 16 | atomic::{ |
| 17 | AtomicBool, |
| 18 | Ordering, |
| 19 | }, |
| 20 | Arc, |
| 21 | Mutex, |
| 22 | Weak, |
| 23 | }, |
| 24 | thread, |
| 25 | }; |
| 26 | |
| 27 | |
| 28 | pub fn thread_channel() -> (Semaphore, Sentinel) { |
| 29 | let flag = Semaphore::new(); |
| 30 | let control = flag.to_sentinel(); |
| 31 | (flag, control) |
| 32 | } |
| 33 | |
| 34 | #[derive(Clone, Debug)] |
| 35 | pub struct ThreadController<T> { |
| 36 | pub chan: Simplex<T>, |
| 37 | pub hopt: Arc<Mutex<Option<thread::JoinHandle<()>>>>, |
| 38 | pub sema: Semaphore, |
| 39 | } |
| 40 | |
| 41 | impl<T> ThreadController<T> { |
| 42 | pub fn new( |
| 43 | chan: Simplex<T>, |
| 44 | hopt: Arc<Mutex<Option<thread::JoinHandle<()>>>>, |
| 45 | sema: Semaphore, |
| 46 | ) |
| 47 | -> Self |
| 48 | { |
| 49 | Self { |
| 50 | chan, |
| 51 | hopt, |
| 52 | sema, |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /// A semaphore contains two flags, one indicating whether the thread is, or should be, alive. The |
| 58 | /// other indicates whether the thread should be interrupted. Pass the semaphore to a child thread |
| 59 | /// so that its Drop function is called when the thread ends. |
| 60 | #[derive(Clone, Debug, Default)] |
| 61 | pub struct Semaphore { |
| 62 | alive: Arc<AtomicBool>, |
| 63 | interrupt: Arc<AtomicBool>, |
| 64 | } |
| 65 | |
| 66 | impl Drop for Semaphore { |
| 67 | fn drop(&mut self) { |
| 68 | if thread::panicking() { |
| 69 | (*self.interrupt).store(true, Ordering::Relaxed) |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | impl Semaphore { |
| 75 | |
| 76 | /// Creates new flag. |
| 77 | pub fn new() -> Self { |
| 78 | Self { |
| 79 | alive: Arc::new(AtomicBool::new(true)), |
| 80 | interrupt: Arc::new(AtomicBool::new(false)), |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// Bring the semaphore into scope so that the compiler adds a Drop function at the end of the |
| 85 | /// scope (i.e. the thread). This is used by `oxedyne_fe2o3_log::logger::Logger` because hiding the |
| 86 | /// semaphore inside the `LOG` singleton would not yield a call to its `Drop` function, since |
| 87 | /// `LOG` is static. Normally you would activate a semaphore inside a child thread with a |
| 88 | /// `while semaphore.alive() {}` loop. |
| 89 | pub fn touch(&self) {} |
| 90 | |
| 91 | /// Yields a new `Sentinel` which can be used to monitor and control this semaphore from code |
| 92 | /// executed within the child thread. |
| 93 | pub fn to_sentinel(&self) -> Sentinel { |
| 94 | Sentinel { |
| 95 | alive: Arc::downgrade(&self.alive), |
| 96 | interrupt: self.interrupt.clone(), |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | /// Return the status of the semaphore alive flag. |
| 101 | /// |
| 102 | /// # Panics |
| 103 | /// |
| 104 | /// This method panics if the interrupt flag has been set. |
| 105 | pub fn alive_or_panic(&self) -> bool { |
| 106 | if (*self.interrupt).load(Ordering::Relaxed) { |
| 107 | panic!("thread interrupted by thread-contol"); |
| 108 | } |
| 109 | (*self.alive).load(Ordering::Relaxed) |
| 110 | } |
| 111 | |
| 112 | /// Check the semaphore alive flag without any possibility of panicking. |
| 113 | pub fn is_alive(&self) -> bool { |
| 114 | (*self.alive).load(Ordering::Relaxed) && !(*self.interrupt).load(Ordering::Relaxed) |
| 115 | } |
| 116 | |
| 117 | /// Consume the `Semaphore` and set its interrupt flag to true, which causes a thread panic |
| 118 | /// next time the `alive_or_panic` method is called. |
| 119 | pub fn interrupt(self) { |
| 120 | (self.interrupt).store(true, Ordering::Relaxed) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | /// `Sentinel` is used to monitor and change the state of the `Semaphore`. |
| 125 | #[derive(Clone, Debug, Default)] |
| 126 | pub struct Sentinel { |
| 127 | alive: Weak<AtomicBool>, |
| 128 | interrupt: Arc<AtomicBool>, |
| 129 | } |
| 130 | |
| 131 | impl Sentinel { |
| 132 | |
| 133 | /// Set the interrupt state of the associated `Semaphore` to true, which causes a thread panic |
| 134 | /// next time the sempahore `alive_or_panic` method is called. |
| 135 | pub fn interrupt(&self) { |
| 136 | (*self.interrupt).store(true, Ordering::Relaxed) |
| 137 | } |
| 138 | |
| 139 | /// Set the alive state of the associated `Semaphore` to false. |
| 140 | pub fn stop(&self) { |
| 141 | self.alive.upgrade().map(|flag| { |
| 142 | (*flag).store(false, Ordering::Relaxed) |
| 143 | }); |
| 144 | } |
| 145 | |
| 146 | /// Returns `true` if the associated `Semaphore` alive state has been set to `false`. |
| 147 | pub fn is_finished(&self) -> bool { |
| 148 | self.alive.upgrade().is_none() |
| 149 | } |
| 150 | |
| 151 | /// Return `true` if the associated `Sempahore` was interrupted or panicked for some other |
| 152 | /// reason. |
| 153 | pub fn was_interrupted(&self) -> bool { |
| 154 | (*self.interrupt).load(Ordering::Relaxed) |
| 155 | } |
| 156 | } |