Skip to main content

ferroui_base/threading/
dispatcher_main_loop.rs

1use std::cell::Cell;
2use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
3use std::rc::Rc;
4use std::sync::atomic::Ordering;
5use std::sync::Arc;
6
7use super::{CancellationToken, Dispatcher, DispatcherFrame, DispatcherPriority, FerroSynchronizationContext};
8use crate::reactive::{Disposable, IDisposable};
9
10impl Dispatcher {
11    pub(crate) fn exit_all_frames_requested(&self) -> bool {
12        self.exit_all_frames_requested.load(Ordering::SeqCst)
13    }
14
15    /// Raised when the dispatcher is shutting down.
16    ///
17    /// # Panics
18    /// Panics when called from a thread other than the dispatcher thread.
19    pub fn shutdown_started(&self, handler: impl Fn(&Arc<Dispatcher>) + 'static) -> Rc<dyn IDisposable> {
20        self.verify_access();
21        let Some(local) = self.try_local() else {
22            return Disposable::empty();
23        };
24        let token = local.shutdown_started.add(Rc::new(handler));
25        let local = Rc::downgrade(&local);
26        Disposable::create(move || {
27            if let Some(local) = local.upgrade() {
28                local.shutdown_started.remove(token);
29            }
30        })
31    }
32
33    /// Raised when the dispatcher is shut down.
34    ///
35    /// # Panics
36    /// Panics when called from a thread other than the dispatcher thread.
37    pub fn shutdown_finished(&self, handler: impl Fn(&Arc<Dispatcher>) + 'static) -> Rc<dyn IDisposable> {
38        self.verify_access();
39        let Some(local) = self.try_local() else {
40            return Disposable::empty();
41        };
42        let token = local.shutdown_finished.add(Rc::new(handler));
43        let local = Rc::downgrade(&local);
44        Disposable::create(move || {
45            if let Some(local) = local.upgrade() {
46                local.shutdown_finished.remove(token);
47            }
48        })
49    }
50
51    /// Push an execution frame.
52    ///
53    /// # Panics
54    /// Panics when the platform implementation does not support run loops
55    /// (see [`supports_run_loops`](Self::supports_run_loops)), when the
56    /// dispatcher has shut down, when processing is disabled and when called
57    /// from another thread.
58    pub fn push_frame(&self, frame: &Arc<DispatcherFrame>) {
59        self.verify_access();
60        let impl_ = {
61            let impl_ = self.platform_impl();
62            let st = self.lock();
63            if impl_.as_controlled().is_none() {
64                panic!("Operation is not supported on this platform: the dispatcher cannot run nested loops.");
65            }
66
67            if st.has_shutdown_finished {
68                // Dispatcher thread - no lock needed for read
69                panic!("Cannot perform requested operation because the Dispatcher shut down");
70            }
71
72            if st.disabled_processing_count > 0 {
73                panic!("Cannot perform this operation while dispatcher processing is suspended.");
74            }
75            impl_
76        };
77
78        self.lock().frames.push(frame.clone());
79        let result = catch_unwind(AssertUnwindSafe(|| {
80            if let Some(controlled) = impl_.as_controlled() {
81                let _restore = FerroSynchronizationContext::ensure_with_dispatcher(self, DispatcherPriority::NORMAL);
82                frame.run(controlled);
83            }
84        }));
85
86        // finally
87        let no_frames_left = {
88            let mut st = self.lock();
89            st.frames.pop();
90            st.frames.is_empty()
91        };
92        if no_frames_left {
93            if self.has_shutdown_started() {
94                self.shutdown_impl();
95            } else {
96                self.exit_all_frames_requested.store(false, Ordering::SeqCst);
97            }
98        }
99        if let Err(payload) = result {
100            resume_unwind(payload);
101        }
102    }
103
104    /// Runs the dispatcher's main loop until `cancellation_token` is
105    /// canceled.
106    ///
107    /// # Panics
108    /// See [`push_frame`](Self::push_frame).
109    pub fn main_loop(&self, cancellation_token: &CancellationToken) {
110        if !self.supports_run_loops() {
111            panic!("Operation is not supported on this platform: the dispatcher cannot run loops.");
112        }
113        self.verify_access();
114        let frame = DispatcherFrame::with_dispatcher(&self.to_arc(), true);
115        let to_stop = frame.clone();
116        let registration = cancellation_token.register(move || to_stop.set_continue(false));
117        let result = catch_unwind(AssertUnwindSafe(|| self.push_frame(&frame)));
118        // The frame is done; a long-lived token must not keep it alive.
119        registration.dispose();
120        if let Err(payload) = result {
121            resume_unwind(payload);
122        }
123    }
124
125    /// Requests that all nested frames exit.
126    pub fn exit_all_frames(&self) {
127        let frames = self.lock().frames.clone();
128        if frames.is_empty() {
129            return;
130        }
131        self.exit_all_frames_requested.store(true, Ordering::SeqCst);
132        for frame in frames.iter().rev() {
133            frame.maybe_exit_on_dispatcher_request();
134        }
135    }
136
137    /// Begins the process of shutting down the dispatcher.
138    pub fn begin_invoke_shutdown(&self, priority: DispatcherPriority) {
139        let dispatcher = self.to_arc();
140        self.post(move || dispatcher.start_shutdown_impl(), priority);
141    }
142
143    /// Initiates the shutdown process of the dispatcher synchronously.
144    pub fn invoke_shutdown(&self) {
145        let dispatcher = self.to_arc();
146        // The dispatcher cannot be shut down twice; an aborted invoke means
147        // it already is.
148        let _ = self.invoke_with_priority(move || dispatcher.start_shutdown_impl(), DispatcherPriority::SEND);
149    }
150
151    fn start_shutdown_impl(&self) {
152        {
153            let mut st = self.lock();
154            if st.starting_shutdown {
155                return;
156            }
157            // We only need this to prevent reentrancy if the shutdown_started event
158            // tries to shut down again.
159            st.starting_shutdown = true;
160        }
161
162        // Call the shutdown_started event before we actually mark ourselves
163        // as shutting down.  This is so the handlers can actually do work
164        // when they get this event without panicking.
165        if let Some(local) = self.try_local() {
166            let dispatcher = self.to_arc();
167            for (_, handler) in local.shutdown_started.snapshot().iter() {
168                handler(&dispatcher);
169            }
170        }
171
172        self.has_shutdown_started.store(true, Ordering::SeqCst);
173
174        let has_frames = !self.lock().frames.is_empty();
175        if has_frames {
176            self.exit_all_frames();
177        } else {
178            self.shutdown_impl();
179        }
180    }
181
182    pub(crate) fn shutdown_impl(&self) {
183        let local = self.try_local();
184        if let Some(local) = &local {
185            Self::detach_implementation(local);
186        }
187        loop {
188            let operation = {
189                let mut st = self.lock();
190                if st.queue.max_priority() != DispatcherPriority::INVALID {
191                    st.queue.peek()
192                } else {
193                    if let Some(local) = &local {
194                        local.impl_.borrow().update_timer(None);
195                    }
196                    st.has_shutdown_finished = true;
197                    None
198                }
199            };
200
201            match operation {
202                Some(operation) => {
203                    operation.abort();
204                }
205                None => break,
206            }
207        }
208
209        // Nothing can resume the futures running on this dispatcher anymore:
210        // cancel them, which releases whoever waits for them.
211        self.cancel_tasks();
212
213        if let Some(local) = local {
214            let dispatcher = self.to_arc();
215            for (_, handler) in local.shutdown_finished.snapshot().iter() {
216                handler(&dispatcher);
217            }
218        }
219    }
220
221    /// Disable the event processing of the dispatcher.
222    ///
223    /// This is an advanced method intended to eliminate the chance of
224    /// unrelated reentrancy. While processing is disabled no one is allowed
225    /// to push a frame and no job processing is permitted. Processing is
226    /// enabled again by disposing the returned value.
227    pub fn disable_processing(&self) -> DispatcherProcessingDisabled {
228        self.verify_access();
229
230        // Turn off processing.
231        self.lock().disabled_processing_count += 1;
232        DispatcherProcessingDisabled { dispatcher: Cell::new(Some(self.to_arc())) }
233    }
234}
235
236/// Re-enables dispatcher processing when disposed; see
237/// [`Dispatcher::disable_processing`].
238pub struct DispatcherProcessingDisabled {
239    dispatcher: Cell<Option<Arc<Dispatcher>>>,
240}
241
242impl IDisposable for DispatcherProcessingDisabled {
243    fn dispose(&self) {
244        let Some(dispatcher) = self.dispatcher.take() else {
245            return;
246        };
247        dispatcher.lock().disabled_processing_count -= 1;
248    }
249}