Skip to main content

ferroui_base/threading/
dispatcher_operation.rs

1use std::any::{Any, TypeId};
2use std::fmt;
3use std::future::Future;
4use std::marker::PhantomData;
5use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
6use std::pin::Pin;
7use std::rc::Rc;
8use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, AtomicU8, AtomicUsize, Ordering};
9use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
10use std::task::{Context, Poll, Waker};
11use std::time::Duration;
12
13use super::dispatcher::DispatcherLocal;
14use super::dispatcher_priority_queue::NOT_QUEUED;
15use super::{
16    CancellationTokenSource, Dispatcher, DispatcherFrame, DispatcherPriority, DispatcherTimer,
17    FerroSynchronizationContext,
18};
19use crate::reactive::{Disposable, IDisposable};
20
21/// The payload of a panic raised by a dispatcher callback. This is what the
22/// reference implementation models as the exception object.
23pub type DispatcherException = Box<dyn Any + Send + 'static>;
24
25/// A callback that can be queued from any thread.
26pub(crate) type SendCallback = Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>;
27
28/// A callback that is queued on, and never leaves, the dispatcher thread.
29/// Returns `None` when the result is `()`.
30pub(crate) type LocalCallback = Box<dyn FnOnce() -> Option<Box<dyn Any>>>;
31
32type EventHandler = Arc<dyn Fn() + Send + Sync>;
33
34/// The state of a [`DispatcherOperation`].
35#[repr(i32)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub enum DispatcherOperationStatus {
38    Pending = 0,
39    Aborted = 1,
40    Completed = 2,
41    Executing = 3,
42}
43
44impl DispatcherOperationStatus {
45    fn from_u8(value: u8) -> Self {
46        match value {
47            0 => Self::Pending,
48            1 => Self::Aborted,
49            2 => Self::Completed,
50            _ => Self::Executing,
51        }
52    }
53}
54
55/// The error produced when the result of an aborted operation is requested.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct OperationCanceledError;
58
59impl fmt::Display for OperationCanceledError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str("The dispatcher operation was canceled")
62    }
63}
64
65impl std::error::Error for OperationCanceledError {}
66
67#[derive(Default)]
68struct OperationInner {
69    callback: Option<SendCallback>,
70    result: Option<Box<dyn Any + Send>>,
71    panic: Option<DispatcherException>,
72    wakers: Vec<Waker>,
73    aborted: Vec<(u64, EventHandler)>,
74    completed: Vec<(u64, EventHandler)>,
75    next_handler_id: u64,
76}
77
78static NEXT_OPERATION_ID: AtomicU64 = AtomicU64::new(1);
79
80/// Ids are shared by operations and tasks: both key the same thread-local
81/// storage.
82pub(crate) fn next_operation_id() -> u64 {
83    NEXT_OPERATION_ID.fetch_add(1, Ordering::Relaxed)
84}
85
86/// How a wait with a limit ended.
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub(crate) enum WaitOutcome {
89    /// The operation is completed or aborted.
90    Finished,
91    /// The time ran out first.
92    TimedOut,
93}
94
95/// The untyped, shareable part of a dispatcher operation. This is what the
96/// dispatcher queue stores.
97pub(crate) struct OperationCore {
98    id: u64,
99    dispatcher: Arc<Dispatcher>,
100    throw_on_ui_thread: bool,
101    /// The callback (and the result, unless it is `()`) is kept in the
102    /// dispatcher thread's local storage instead of in `inner`.
103    is_local: bool,
104    /// Whether local storage may still hold something for this operation.
105    local_storage_used: AtomicBool,
106    status: AtomicU8,
107    priority: AtomicI32,
108    queue_slot: AtomicUsize,
109    inner: Mutex<OperationInner>,
110    finished: Condvar,
111}
112
113impl OperationCore {
114    fn new(
115        dispatcher: &Arc<Dispatcher>,
116        priority: DispatcherPriority,
117        throw_on_ui_thread: bool,
118        callback: Option<SendCallback>,
119    ) -> Arc<Self> {
120        let is_local = callback.is_none();
121        Arc::new(Self {
122            id: next_operation_id(),
123            dispatcher: dispatcher.clone(),
124            throw_on_ui_thread,
125            is_local,
126            local_storage_used: AtomicBool::new(is_local),
127            status: AtomicU8::new(DispatcherOperationStatus::Pending as u8),
128            priority: AtomicI32::new(priority.value()),
129            queue_slot: AtomicUsize::new(NOT_QUEUED),
130            inner: Mutex::new(OperationInner { callback, ..OperationInner::default() }),
131            finished: Condvar::new(),
132        })
133    }
134
135    /// Creates an operation whose callback can be queued from any thread.
136    pub(crate) fn new_send(
137        dispatcher: &Arc<Dispatcher>,
138        priority: DispatcherPriority,
139        throw_on_ui_thread: bool,
140        callback: SendCallback,
141    ) -> Arc<Self> {
142        Self::new(dispatcher, priority, throw_on_ui_thread, Some(callback))
143    }
144
145    /// Creates an operation whose callback stays on the dispatcher thread.
146    ///
147    /// # Panics
148    /// Panics when called from another thread.
149    pub(crate) fn new_local(
150        dispatcher: &Arc<Dispatcher>,
151        priority: DispatcherPriority,
152        throw_on_ui_thread: bool,
153        callback: LocalCallback,
154    ) -> Arc<Self> {
155        dispatcher.verify_access();
156        let core = Self::new(dispatcher, priority, throw_on_ui_thread, None);
157        match dispatcher.try_local() {
158            Some(local) => {
159                local.jobs.borrow_mut().insert(core.id, callback);
160            }
161            None => core.local_storage_used.store(false, Ordering::Relaxed),
162        }
163        core
164    }
165
166    fn lock(&self) -> MutexGuard<'_, OperationInner> {
167        self.inner.lock().unwrap_or_else(PoisonError::into_inner)
168    }
169
170    #[inline]
171    pub(crate) fn status(&self) -> DispatcherOperationStatus {
172        DispatcherOperationStatus::from_u8(self.status.load(Ordering::SeqCst))
173    }
174
175    #[inline]
176    pub(crate) fn set_status(&self, status: DispatcherOperationStatus) {
177        self.status.store(status as u8, Ordering::SeqCst);
178    }
179
180    #[inline]
181    pub(crate) fn dispatcher(&self) -> &Arc<Dispatcher> {
182        &self.dispatcher
183    }
184
185    #[inline]
186    pub(crate) fn priority(&self) -> DispatcherPriority {
187        let value = self.priority.load(Ordering::SeqCst);
188        if value == DispatcherPriority::INVALID.value() {
189            DispatcherPriority::INVALID
190        } else {
191            DispatcherPriority::from_value(value)
192        }
193    }
194
195    pub(crate) fn set_priority(self: &Arc<Self>, value: DispatcherPriority) {
196        self.priority.store(value.value(), Ordering::SeqCst);
197        self.dispatcher.set_priority(self, value);
198    }
199
200    #[inline]
201    pub(crate) fn queue_slot(&self) -> usize {
202        self.queue_slot.load(Ordering::Relaxed)
203    }
204
205    #[inline]
206    pub(crate) fn set_queue_slot(&self, slot: usize) {
207        self.queue_slot.store(slot, Ordering::Relaxed);
208    }
209
210    #[inline]
211    pub(crate) fn is_queued(&self) -> bool {
212        self.queue_slot() != NOT_QUEUED
213    }
214
215    fn add_handler(self: &Arc<Self>, aborted: bool, handler: EventHandler) -> u64 {
216        let mut inner = self.lock();
217        inner.next_handler_id += 1;
218        let id = inner.next_handler_id;
219        if aborted {
220            inner.aborted.push((id, handler));
221        } else {
222            inner.completed.push((id, handler));
223        }
224        id
225    }
226
227    fn remove_handler(&self, aborted: bool, id: u64) {
228        let removed = {
229            let mut inner = self.lock();
230            let list = if aborted { &mut inner.aborted } else { &mut inner.completed };
231            list.iter().position(|(handler_id, _)| *handler_id == id).map(|index| list.remove(index))
232        };
233        drop(removed);
234    }
235
236    pub(crate) fn add_aborted(self: &Arc<Self>, handler: EventHandler) -> u64 {
237        self.add_handler(true, handler)
238    }
239
240    pub(crate) fn add_completed(self: &Arc<Self>, handler: EventHandler) -> u64 {
241        self.add_handler(false, handler)
242    }
243
244    pub(crate) fn remove_aborted(&self, id: u64) {
245        self.remove_handler(true, id);
246    }
247
248    pub(crate) fn remove_completed(&self, id: u64) {
249        self.remove_handler(false, id);
250    }
251
252    pub(crate) fn abort(self: &Arc<Self>) -> bool {
253        if self.dispatcher.abort(self) {
254            self.call_abort_callbacks();
255            return true;
256        }
257
258        false
259    }
260
261    /// Wakes everything waiting for the operation and raises `Aborted`.
262    pub(crate) fn call_abort_callbacks(&self) {
263        debug_assert_eq!(self.status(), DispatcherOperationStatus::Aborted);
264        let (callback, wakers, handlers) = {
265            let mut inner = self.lock();
266            let result = (inner.callback.take(), std::mem::take(&mut inner.wakers), inner.aborted.clone());
267            self.finished.notify_all();
268            result
269        };
270        // The callback will never run; release whatever it captured.
271        drop(callback);
272        self.discard_local_storage();
273        for waker in wakers {
274            waker.wake();
275        }
276        for (_, handler) in handlers {
277            handler();
278        }
279    }
280
281    fn call_completed_callbacks(&self) {
282        let (wakers, handlers) = {
283            let mut inner = self.lock();
284            let result = (std::mem::take(&mut inner.wakers), inner.completed.clone());
285            self.finished.notify_all();
286            result
287        };
288        for waker in wakers {
289            waker.wake();
290        }
291        for (_, handler) in handlers {
292            handler();
293        }
294    }
295
296    fn discard_local_storage(&self) {
297        if self.is_local && self.local_storage_used.swap(false, Ordering::SeqCst) {
298            self.dispatcher.discard_local_operation(self.id);
299        }
300    }
301
302    /// Runs the callback. Called by the dispatcher, on its thread, after the
303    /// status has been changed to `Executing`.
304    pub(crate) fn execute(&self) {
305        debug_assert_eq!(self.status(), DispatcherOperationStatus::Executing);
306        let local = self.dispatcher.try_local();
307        if let Some(local) = &local {
308            local.executing_depth.set(local.executing_depth.get() + 1);
309        }
310
311        let outcome = catch_unwind(AssertUnwindSafe(|| {
312            // The current context tells asynchronous code started by the
313            // callback to continue at the priority of this operation.
314            let _restore = FerroSynchronizationContext::ensure_with_dispatcher(&self.dispatcher, self.priority());
315            self.invoke_core(local.as_deref())
316        }));
317
318        // finally
319        let completed = catch_unwind(AssertUnwindSafe(|| self.call_completed_callbacks()));
320
321        if let Some(local) = &local {
322            let depth = local.executing_depth.get() - 1;
323            local.executing_depth.set(depth);
324            if depth == 0 {
325                // No dispatcher code is left on the stack that could see one
326                // of the already-filtered panics again.
327                local.seen_exceptions.borrow_mut().clear();
328            }
329        }
330
331        match outcome {
332            Ok(None) => {}
333            Ok(Some(payload)) | Err(payload) => resume_unwind(payload),
334        }
335        if let Err(payload) = completed {
336            resume_unwind(payload);
337        }
338    }
339
340    /// Returns the panic payload that has to be re-raised on the dispatcher
341    /// thread, if any.
342    fn invoke_core(&self, local: Option<&DispatcherLocal>) -> Option<DispatcherException> {
343        enum Value {
344            Send(Box<dyn Any + Send>),
345            Local(Option<Box<dyn Any>>),
346            Missing,
347        }
348
349        let result = if self.is_local {
350            let job = local.and_then(|local| local.jobs.borrow_mut().remove(&self.id));
351            match job {
352                Some(job) => catch_unwind(AssertUnwindSafe(|| Value::Local(job()))),
353                None => Ok(Value::Missing),
354            }
355        } else {
356            let job = self.lock().callback.take();
357            match job {
358                Some(job) => catch_unwind(AssertUnwindSafe(|| Value::Send(job()))),
359                None => Ok(Value::Missing),
360            }
361        };
362
363        match result {
364            Ok(value) => {
365                let mut local_result = None;
366                {
367                    let mut inner = self.lock();
368                    self.set_status(DispatcherOperationStatus::Completed);
369                    match value {
370                        Value::Send(value) => inner.result = Some(value),
371                        Value::Local(value) => local_result = value,
372                        Value::Missing => {}
373                    }
374                }
375                match (local_result, local) {
376                    (Some(value), Some(local)) => {
377                        local.results.borrow_mut().insert(self.id, value);
378                    }
379                    _ => self.local_storage_used.store(false, Ordering::SeqCst),
380                }
381                None
382            }
383            Err(payload) => {
384                self.local_storage_used.store(false, Ordering::SeqCst);
385                if self.throw_on_ui_thread {
386                    self.set_status(DispatcherOperationStatus::Completed);
387                    if self.dispatcher.try_catch_when(&payload) {
388                        None
389                    } else {
390                        Some(payload)
391                    }
392                } else {
393                    let mut inner = self.lock();
394                    self.set_status(DispatcherOperationStatus::Completed);
395                    inner.panic = Some(payload);
396                    None
397                }
398            }
399        }
400    }
401
402    /// Blocks the calling (non-dispatcher) thread until the operation is
403    /// completed or aborted.
404    fn block_until_finished(&self) {
405        let mut inner = self.lock();
406        while matches!(self.status(), DispatcherOperationStatus::Pending | DispatcherOperationStatus::Executing) {
407            inner = self.finished.wait(inner).unwrap_or_else(PoisonError::into_inner);
408        }
409    }
410
411    fn is_finished(&self) -> bool {
412        matches!(self.status(), DispatcherOperationStatus::Completed | DispatcherOperationStatus::Aborted)
413    }
414
415    pub(crate) fn wait(self: &Arc<Self>) {
416        self.wait_with_limit(None, false);
417    }
418
419    /// Waits for the operation to finish, for at most `timeout`.
420    ///
421    /// On the dispatcher thread this pumps the queue, measuring time with
422    /// the dispatcher clock; on any other thread it blocks. With
423    /// `abort_on_timeout` the operation is aborted when the time runs out
424    /// while it is still pending; when it has already started by then, the
425    /// wait continues until it is done.
426    pub(crate) fn wait_with_limit(self: &Arc<Self>, timeout: Option<Duration>, abort_on_timeout: bool) -> WaitOutcome {
427        let dispatcher = &self.dispatcher;
428        let on_dispatcher_thread = dispatcher.check_access();
429
430        if matches!(self.status(), DispatcherOperationStatus::Pending | DispatcherOperationStatus::Executing)
431            && timeout != Some(Duration::ZERO)
432            && on_dispatcher_thread
433        {
434            if self.status() == DispatcherOperationStatus::Executing {
435                // We are the dispatching thread, and the current operation state is
436                // executing, which means that the operation is in the middle of
437                // executing (on this thread) and is trying to wait for the execution
438                // to complete.  Unfortunately, the thread will now deadlock, so
439                // we panic instead.
440                panic!("A thread cannot wait on operations already running on the same thread.");
441            }
442
443            let deadline = timeout.map(|timeout| dispatcher.now().saturating_add(duration_to_ms(timeout)));
444            let cts = CancellationTokenSource::new();
445            let cancel = cts.clone();
446            let finished_handler: EventHandler = Arc::new(move || cancel.cancel());
447            let completed = self.add_completed(finished_handler.clone());
448            let aborted = self.add_aborted(finished_handler);
449
450            let result = catch_unwind(AssertUnwindSafe(|| {
451                while self.status() == DispatcherOperationStatus::Pending {
452                    let remaining = match deadline {
453                        Some(deadline) => {
454                            let remaining = deadline - dispatcher.now();
455                            if remaining <= 0 {
456                                break;
457                            }
458                            Some(remaining)
459                        }
460                        None => None,
461                    };
462                    if dispatcher.supports_run_loops() {
463                        if self.priority() >= DispatcherPriority::MINIMUM_FOREGROUND_PRIORITY {
464                            dispatcher.run_jobs_with_cancellation(Some(self.priority()), &cts.token());
465                        } else {
466                            self.push_operation_frame(remaining);
467                        }
468                    } else {
469                        dispatcher.run_jobs_with_cancellation(
470                            Some(DispatcherPriority::MINIMUM_ACTIVE_VALUE),
471                            &cts.token(),
472                        );
473                    }
474                }
475            }));
476
477            // finally
478            self.remove_completed(completed);
479            self.remove_aborted(aborted);
480            if let Err(payload) = result {
481                resume_unwind(payload);
482            }
483        }
484
485        if self.is_finished() {
486            return WaitOutcome::Finished;
487        }
488
489        if on_dispatcher_thread {
490            // Only reachable with a timeout: blocking here could never end.
491            if abort_on_timeout {
492                self.abort();
493            }
494            return WaitOutcome::TimedOut;
495        }
496
497        match timeout {
498            None => {
499                self.block_until_finished();
500                WaitOutcome::Finished
501            }
502            Some(timeout) => {
503                if self.block_until_finished_or_timeout(timeout) {
504                    return WaitOutcome::Finished;
505                }
506                if !abort_on_timeout {
507                    return WaitOutcome::TimedOut;
508                }
509                if self.abort() {
510                    return WaitOutcome::TimedOut;
511                }
512                // The operation has already started: we can't leave it in a
513                // state that it might execute if we report that it did not.
514                self.block_until_finished();
515                WaitOutcome::Finished
516            }
517        }
518    }
519
520    /// Blocks the calling (non-dispatcher) thread; returns whether the
521    /// operation finished in time.
522    fn block_until_finished_or_timeout(&self, timeout: Duration) -> bool {
523        let inner = self.lock();
524        let (_inner, _result) = self
525            .finished
526            .wait_timeout_while(inner, timeout, |_| {
527                matches!(self.status(), DispatcherOperationStatus::Pending | DispatcherOperationStatus::Executing)
528            })
529            .unwrap_or_else(PoisonError::into_inner);
530        self.is_finished()
531    }
532
533    /// Pumps a nested frame until the operation is completed or aborted, or
534    /// until `timeout_ms` of dispatcher time have passed.
535    ///
536    /// The frame does not exit when the dispatcher asks its frames to exit,
537    /// because operations may have to be invoked during the shutdown process.
538    fn push_operation_frame(self: &Arc<Self>, timeout_ms: Option<i64>) {
539        let frame = DispatcherFrame::with_dispatcher(&self.dispatcher, false);
540
541        // We will exit this frame once the operation is completed or aborted.
542        let exit_frame = frame.clone();
543        let handler: EventHandler = Arc::new(move || exit_frame.set_continue(false));
544        let aborted = self.add_aborted(handler.clone());
545        let completed = self.add_completed(handler);
546
547        // We will exit the frame if the operation is not completed within
548        // the requested timeout.
549        let wait_timer = timeout_ms.filter(|timeout| *timeout > 0).map(|timeout| {
550            let exit_frame = frame.clone();
551            DispatcherTimer::with_callback_and_dispatcher(
552                Duration::from_millis(timeout.min(i32::MAX as i64) as u64),
553                DispatcherPriority::SEND,
554                &self.dispatcher,
555                move |timer| {
556                    timer.stop();
557                    exit_frame.set_continue(false);
558                },
559            )
560        });
561
562        // Some other thread could have aborted the operation while we were
563        // setting up the handlers.  We check the state again and mark the
564        // frame as "should not continue" if this happened.
565        if self.status() != DispatcherOperationStatus::Pending {
566            frame.set_continue(false);
567        }
568
569        let result = catch_unwind(AssertUnwindSafe(|| self.dispatcher.push_frame(&frame)));
570        if let Some(wait_timer) = wait_timer {
571            wait_timer.stop();
572        }
573        self.remove_aborted(aborted);
574        self.remove_completed(completed);
575        if let Err(payload) = result {
576            resume_unwind(payload);
577        }
578    }
579
580    /// Takes the outcome of a finished operation: re-raises the callback's
581    /// panic, or returns the boxed result.
582    fn take_outcome<R: 'static>(&self) -> Result<R, OperationCanceledError> {
583        debug_assert!(self.is_finished());
584        if self.status() == DispatcherOperationStatus::Aborted {
585            return Err(OperationCanceledError);
586        }
587
588        let (panic, result) = {
589            let mut inner = self.lock();
590            (inner.panic.take(), inner.result.take())
591        };
592        if let Some(payload) = panic {
593            resume_unwind(payload);
594        }
595
596        let value: Option<Box<dyn Any>> = if !self.is_local {
597            result.map(|value| value as Box<dyn Any>)
598        } else if TypeId::of::<R>() == TypeId::of::<()>() {
599            Some(Box::new(()))
600        } else {
601            if !self.dispatcher.check_access() {
602                panic!("The result of a local dispatcher operation can only be read on the dispatcher thread.");
603            }
604            let value = self.dispatcher.try_local().and_then(|local| local.results.borrow_mut().remove(&self.id));
605            self.local_storage_used.store(false, Ordering::SeqCst);
606            value
607        };
608
609        match value.and_then(|value| value.downcast::<R>().ok()) {
610            Some(value) => Ok(*value),
611            None => panic!("The result of the dispatcher operation has already been taken."),
612        }
613    }
614}
615
616impl Drop for OperationCore {
617    fn drop(&mut self) {
618        self.discard_local_storage();
619    }
620}
621
622/// Represents an object used to interact with a job that has been posted to
623/// the [`Dispatcher`] queue.
624///
625/// The handle can be cloned and sent to other threads. It is also a
626/// [`Future`] that resolves when the operation has completed
627/// (`Ok(result)`) or has been aborted (`Err(OperationCanceledError)`).
628///
629/// A panic raised by the callback is the equivalent of an exception in the
630/// reference implementation: it is captured when the callback runs and
631/// re-raised by [`wait`](Self::wait), [`result`](Self::result) and when the
632/// future is polled.
633pub struct DispatcherOperation<R = ()> {
634    core: Arc<OperationCore>,
635    _result: PhantomData<fn() -> R>,
636}
637
638impl<R> Clone for DispatcherOperation<R> {
639    fn clone(&self) -> Self {
640        Self { core: self.core.clone(), _result: PhantomData }
641    }
642}
643
644impl<R> fmt::Debug for DispatcherOperation<R> {
645    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646        write!(f, "DispatcherOperation #{} [{}] {:?}", self.core.id, self.core.priority(), self.core.status())
647    }
648}
649
650impl<R: 'static> DispatcherOperation<R> {
651    pub(crate) fn from_core(core: Arc<OperationCore>) -> Self {
652        Self { core, _result: PhantomData }
653    }
654
655    pub(crate) fn core(&self) -> &Arc<OperationCore> {
656        &self.core
657    }
658
659    /// Creates an operation that can be queued from any thread.
660    pub(crate) fn new_send(
661        dispatcher: &Arc<Dispatcher>,
662        priority: DispatcherPriority,
663        callback: impl FnOnce() -> R + Send + 'static,
664    ) -> Self
665    where
666        R: Send,
667    {
668        let callback: SendCallback = Box::new(move || Box::new(callback()) as Box<dyn Any + Send>);
669        Self::from_core(OperationCore::new_send(dispatcher, priority, false, callback))
670    }
671
672    /// Creates an operation whose callback and result stay on the dispatcher
673    /// thread.
674    pub(crate) fn new_local(
675        dispatcher: &Arc<Dispatcher>,
676        priority: DispatcherPriority,
677        callback: impl FnOnce() -> R + 'static,
678    ) -> Self {
679        Self::from_core(OperationCore::new_local(dispatcher, priority, false, box_local_callback(callback)))
680    }
681
682    /// The current status of the operation.
683    pub fn status(&self) -> DispatcherOperationStatus {
684        self.core.status()
685    }
686
687    /// The dispatcher the operation was queued on.
688    pub fn dispatcher(&self) -> &Arc<Dispatcher> {
689        self.core.dispatcher()
690    }
691
692    pub fn priority(&self) -> DispatcherPriority {
693        self.core.priority()
694    }
695
696    /// Changes the priority of the operation. A queued operation keeps its
697    /// position relative to the other operations of the new priority.
698    pub fn set_priority(&self, value: DispatcherPriority) {
699        self.core.set_priority(value);
700    }
701
702    /// An event that is raised when the operation is aborted or canceled.
703    ///
704    /// The handler runs on the thread that aborts the operation.
705    pub fn aborted(&self, handler: impl Fn() + Send + Sync + 'static) -> Rc<dyn IDisposable> {
706        let id = self.core.add_aborted(Arc::new(handler));
707        let core = self.core.clone();
708        Disposable::create(move || core.remove_aborted(id))
709    }
710
711    /// An event that is raised when the operation completes.
712    ///
713    /// The handler runs on the dispatcher thread.
714    pub fn completed(&self, handler: impl Fn() + Send + Sync + 'static) -> Rc<dyn IDisposable> {
715        let id = self.core.add_completed(Arc::new(handler));
716        let core = self.core.clone();
717        Disposable::create(move || core.remove_completed(id))
718    }
719
720    /// Removes a pending operation from the queue. Returns `false` when the
721    /// operation is no longer pending.
722    pub fn abort(&self) -> bool {
723        self.core.abort()
724    }
725
726    /// Waits for this operation to complete.
727    ///
728    /// On the dispatcher thread this pumps the queue (nesting a frame when
729    /// the operation has a background priority and the platform supports run
730    /// loops). On any other thread it blocks.
731    ///
732    /// Returns `Err` when the operation was aborted.
733    ///
734    /// # Panics
735    /// Re-raises the panic of the callback. Panics when called on the
736    /// dispatcher thread for the operation that is currently executing.
737    pub fn wait(&self) -> Result<(), OperationCanceledError> {
738        self.core.wait();
739        if self.core.status() == DispatcherOperationStatus::Aborted {
740            return Err(OperationCanceledError);
741        }
742        let panic = self.core.lock().panic.take();
743        if let Some(payload) = panic {
744            resume_unwind(payload);
745        }
746        Ok(())
747    }
748
749    /// Waits for this operation to complete, for at most `timeout`.
750    ///
751    /// Returns `Ok(true)` when the operation completed in time, `Ok(false)`
752    /// when it is still pending or executing after `timeout`, and `Err` when
753    /// it was aborted.
754    ///
755    /// On the dispatcher thread this pumps the queue like
756    /// [`wait`](Self::wait) and measures the timeout with the dispatcher
757    /// clock; a nested frame (background priorities, where the platform
758    /// supports run loops) is left by a dispatcher timer. On any other
759    /// thread it blocks. A zero timeout only reports the current state.
760    ///
761    /// # Panics
762    /// Re-raises the panic of the callback. Panics when called on the
763    /// dispatcher thread for the operation that is currently executing.
764    pub fn wait_with_timeout(&self, timeout: Duration) -> Result<bool, OperationCanceledError> {
765        if self.core.wait_with_limit(Some(timeout), false) == WaitOutcome::TimedOut {
766            return Ok(false);
767        }
768        self.wait().map(|()| true)
769    }
770
771    /// Takes the result of the operation.
772    ///
773    /// When the operation has not finished yet this blocks, which is only
774    /// supported on non-dispatcher threads.
775    ///
776    /// # Panics
777    /// Re-raises the panic of the callback. Panics when the operation is not
778    /// finished and the calling thread is the dispatcher thread, when the
779    /// result has already been taken, and when the result of a local
780    /// operation is requested from another thread.
781    pub fn result(&self) -> Result<R, OperationCanceledError> {
782        if !self.core.is_finished() {
783            if self.core.dispatcher.check_access() {
784                panic!("Synchronous wait is only supported on non-UI threads");
785            }
786            self.core.block_until_finished();
787        }
788        self.core.take_outcome()
789    }
790
791    /// Takes the result of a finished operation.
792    pub(crate) fn take_result(&self) -> Result<R, OperationCanceledError> {
793        self.core.take_outcome()
794    }
795}
796
797impl<R: 'static> Future for DispatcherOperation<R> {
798    type Output = Result<R, OperationCanceledError>;
799
800    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
801        {
802            let mut inner = self.core.lock();
803            // Checked under the lock: the status is published before the
804            // wakers are drained under the same lock.
805            if !self.core.is_finished() {
806                if !inner.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
807                    inner.wakers.push(cx.waker().clone());
808                }
809                return Poll::Pending;
810            }
811        }
812        Poll::Ready(self.core.take_outcome())
813    }
814}
815
816pub(crate) fn box_local_callback<R: 'static>(callback: impl FnOnce() -> R + 'static) -> LocalCallback {
817    Box::new(move || {
818        let value = callback();
819        if TypeId::of::<R>() == TypeId::of::<()>() {
820            None
821        } else {
822            Some(Box::new(value) as Box<dyn Any>)
823        }
824    })
825}
826
827/// Milliseconds of a duration on the dispatcher clock.
828pub(crate) fn duration_to_ms(duration: Duration) -> i64 {
829    i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
830}