Oregami
Repositories/oxedyne/fe2o3

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.
10use crate::{
11 channels::Simplex,
12};
13
14use std::{
15 sync::{
16 atomic::{
17 AtomicBool,
18 Ordering,
19 },
20 Arc,
21 Mutex,
22 Weak,
23 },
24 thread,
25};
26
27
28pub 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)]
35pub struct ThreadController<T> {
36 pub chan: Simplex<T>,
37 pub hopt: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
38 pub sema: Semaphore,
39}
40
41impl<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)]
61pub struct Semaphore {
62 alive: Arc<AtomicBool>,
63 interrupt: Arc<AtomicBool>,
64}
65
66impl Drop for Semaphore {
67 fn drop(&mut self) {
68 if thread::panicking() {
69 (*self.interrupt).store(true, Ordering::Relaxed)
70 }
71 }
72}
73
74impl 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)]
126pub struct Sentinel {
127 alive: Weak<AtomicBool>,
128 interrupt: Arc<AtomicBool>,
129}
130
131impl 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}