Skip to main content

fuchsia_async/runtime/fuchsia/executor/
common.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::super::timer::Timers;
6use super::atomic_future::hooks::HooksMap;
7use super::atomic_future::{AtomicFutureHandle, AttemptPollResult};
8use super::packets::{
9    PacketReceiver, PacketReceiverMap, RawReceiverRegistration, ReceiverRegistration,
10};
11use super::scope::ScopeHandle;
12use super::time::{BootInstant, MonotonicInstant};
13use crate::runtime::instrument::TaskInstrument;
14use crossbeam::queue::SegQueue;
15use fuchsia_sync::Mutex;
16use zx::BootDuration;
17
18use std::any::Any;
19use std::cell::{Cell, RefCell};
20use std::fmt;
21use std::future::Future;
22use std::pin::Pin;
23use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
24use std::sync::{Arc, OnceLock};
25use std::task::Context;
26use std::thread::ThreadId;
27
28pub(crate) const TASK_READY_WAKEUP_ID: u64 = u64::MAX - 1;
29
30thread_local!(
31    static EXECUTOR: RefCell<Option<ScopeHandle>> = const { RefCell::new(None) }
32);
33
34pub enum ExecutorTime {
35    RealTime,
36    /// Fake readings used in tests.
37    FakeTime {
38        // The fake monotonic clock reading.
39        mono_reading_ns: AtomicI64,
40        // An offset to add to mono_reading_ns to get the reading of the boot
41        // clock, disregarding the difference in timelines.
42        //
43        // We disregard the fact that the reading and offset can not be
44        // read atomically, this is usually not relevant in tests.
45        mono_to_boot_offset_ns: AtomicI64,
46    },
47}
48
49enum PollReadyTasksResult {
50    NoneReady,
51    MoreReady,
52    MainTaskCompleted,
53}
54
55///  24           16           8            0
56///  +------------+------------+------------+
57///  |  foreign   |  notified  |  sleeping  |
58///  +------------+------------+------------+
59///
60///  sleeping : the number of threads sleeping
61///  notified : the number of notifications posted to wake sleeping threads
62///  foreign  : the number of foreign threads processing tasks
63#[derive(Clone, Copy, Eq, PartialEq)]
64struct ThreadsState(u32);
65
66impl ThreadsState {
67    const fn sleeping(&self) -> u8 {
68        self.0 as u8
69    }
70
71    const fn notified(&self) -> u8 {
72        (self.0 >> 8) as u8
73    }
74
75    const fn with_sleeping(self, sleeping: u8) -> Self {
76        Self((self.0 & !0xff) | sleeping as u32)
77    }
78
79    const fn with_notified(self, notified: u8) -> Self {
80        Self(self.0 & !0xff00 | (notified as u32) << 8)
81    }
82
83    const fn with_foreign(self, foreign: u8) -> Self {
84        Self(self.0 & !0xff0000 | (foreign as u32) << 16)
85    }
86}
87
88#[cfg(test)]
89static ACTIVE_EXECUTORS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
90
91pub(crate) struct Executor {
92    pub(super) port: zx::Port,
93    monotonic_timers: Arc<Timers<MonotonicInstant>>,
94    boot_timers: Arc<Timers<BootInstant>>,
95    pub(super) done: AtomicBool,
96    is_local: bool,
97    pub(crate) receivers: PacketReceiverMap,
98
99    pub(super) ready_tasks: SegQueue<TaskHandle>,
100    time: ExecutorTime,
101    // The low byte is the number of threads currently sleeping. The high byte is the number of
102    // of wake-up notifications pending.
103    pub(super) threads_state: AtomicU32,
104    pub(super) num_threads: u8,
105    pub(super) polled: AtomicU64,
106    // Data that belongs to the user that can be accessed via EHandle::local(). See
107    // `TestExecutor::poll_until_stalled`.
108    pub(super) owner_data: Mutex<Option<Box<dyn Any + Send>>>,
109    pub(super) instrument: Option<Arc<dyn TaskInstrument>>,
110    pub(super) hooks_map: HooksMap,
111    pub(super) first_thread_id: OnceLock<ThreadId>,
112
113    // The time, in nanoseconds, that the executor was last executing a normal priority task.
114    last_active: AtomicI64,
115}
116
117impl Executor {
118    pub fn new_with_port(
119        time: ExecutorTime,
120        is_local: bool,
121        num_threads: u8,
122        port: zx::Port,
123        instrument: Option<Arc<dyn TaskInstrument>>,
124    ) -> Self {
125        #[cfg(test)]
126        ACTIVE_EXECUTORS.fetch_add(1, Ordering::Relaxed);
127
128        // Is this a fake-time executor?
129        let is_fake = matches!(
130            time,
131            ExecutorTime::FakeTime { mono_reading_ns: _, mono_to_boot_offset_ns: _ }
132        );
133
134        Executor {
135            port,
136            monotonic_timers: Arc::new(Timers::<MonotonicInstant>::new(is_fake)),
137            boot_timers: Arc::new(Timers::<BootInstant>::new(is_fake)),
138            done: AtomicBool::new(false),
139            is_local,
140            receivers: PacketReceiverMap::new(),
141            ready_tasks: SegQueue::new(),
142            time,
143            threads_state: AtomicU32::new(0),
144            num_threads,
145            polled: AtomicU64::new(0),
146            owner_data: Mutex::new(None),
147            instrument,
148            hooks_map: HooksMap::default(),
149            first_thread_id: OnceLock::new(),
150            last_active: Default::default(),
151        }
152    }
153
154    pub fn set_local(root_scope: ScopeHandle) {
155        root_scope.executor().first_thread_id.get_or_init(|| std::thread::current().id());
156        EXECUTOR.with(|e| {
157            let mut e = e.borrow_mut();
158            assert!(e.is_none(), "Cannot create multiple Fuchsia Executors");
159            *e = Some(root_scope);
160        });
161    }
162
163    fn poll_ready_tasks(&self, main_task: Option<&TaskHandle>) -> PollReadyTasksResult {
164        loop {
165            for _ in 0..16 {
166                let Some(task) = self.ready_tasks.pop() else {
167                    return PollReadyTasksResult::NoneReady;
168                };
169                let is_main = Some(&task) == main_task;
170                let complete = self.try_poll(task);
171                if complete && is_main {
172                    return PollReadyTasksResult::MainTaskCompleted;
173                }
174                self.polled.fetch_add(1, Ordering::Relaxed);
175            }
176            // We didn't finish all the ready tasks. If there are sleeping threads, post a
177            // notification to wake one up.
178            let mut threads_state = ThreadsState(self.threads_state.load(Ordering::Relaxed));
179            loop {
180                if threads_state.sleeping() == 0 {
181                    // All threads are awake now. Prevent starvation.
182                    return PollReadyTasksResult::MoreReady;
183                }
184                if threads_state.notified() >= threads_state.sleeping() {
185                    // All sleeping threads have been notified. Keep going and poll more tasks.
186                    break;
187                }
188                match self.try_notify(threads_state) {
189                    Ok(()) => break,
190                    Err(s) => threads_state = s,
191                }
192            }
193        }
194    }
195
196    pub fn is_local(&self) -> bool {
197        self.is_local
198    }
199
200    pub fn notify_task_ready(&self) {
201        // Only post if there's no thread running (or soon to be running). If we happen to be
202        // running on a thread for this executor, then threads_state won't be equal to num_threads,
203        // which means notifications only get fired if this is from a non-async thread, or a thread
204        // that belongs to a different executor. We use SeqCst ordering here to make sure this load
205        // happens *after* the change to ready_tasks and to synchronize with worker_lifecycle.
206        let mut threads_state = ThreadsState(self.threads_state.load(Ordering::SeqCst));
207
208        // We only want to notify if there are no pending notifications and there are no other
209        // threads running.
210        while threads_state == ThreadsState(0).with_sleeping(self.num_threads) {
211            match self.try_notify(threads_state) {
212                Ok(()) => break,
213                Err(s) => threads_state = s,
214            }
215        }
216    }
217
218    /// Tries to notify a thread to wake up. Returns threads_state if it fails.
219    fn try_notify(&self, old_threads_state: ThreadsState) -> Result<(), ThreadsState> {
220        self.threads_state
221            .compare_exchange_weak(
222                old_threads_state.0,
223                old_threads_state.0 + ThreadsState(0).with_notified(1).0,
224                Ordering::Relaxed,
225                Ordering::Relaxed,
226            )
227            .map(|_| self.notify_id(TASK_READY_WAKEUP_ID))
228            .map_err(ThreadsState)
229    }
230
231    pub fn wake_one_thread(&self) {
232        let mut threads_state = ThreadsState(self.threads_state.load(Ordering::Relaxed));
233        let current_sleeping = threads_state.sleeping();
234        if current_sleeping == 0 {
235            return;
236        }
237        while threads_state.notified() == 0 && threads_state.sleeping() >= current_sleeping {
238            match self.try_notify(threads_state) {
239                Ok(()) => break,
240                Err(s) => threads_state = s,
241            }
242        }
243    }
244
245    pub fn notify_id(&self, id: u64) {
246        let up = zx::UserPacket::from_u8_array([0; 32]);
247        let packet = zx::Packet::from_user_packet(id, 0 /* status??? */, up);
248        if let Err(e) = self.port.queue(&packet) {
249            // TODO: logging
250            eprintln!("Failed to queue notify in port: {e:?}");
251        }
252    }
253
254    /// Returns the current reading of the monotonic clock.
255    ///
256    /// For test executors running in fake time, returns the reading of the
257    /// fake monotonic clock.
258    pub fn now(&self) -> MonotonicInstant {
259        match &self.time {
260            ExecutorTime::RealTime => MonotonicInstant::from_zx(zx::MonotonicInstant::get()),
261            ExecutorTime::FakeTime { mono_reading_ns: t, .. } => {
262                MonotonicInstant::from_nanos(t.load(Ordering::Relaxed))
263            }
264        }
265    }
266
267    /// Returns the current reading of the boot clock.
268    ///
269    /// For test executors running in fake time, returns the reading of the
270    /// fake boot clock.
271    pub fn boot_now(&self) -> BootInstant {
272        match &self.time {
273            ExecutorTime::RealTime => BootInstant::from_zx(zx::BootInstant::get()),
274
275            ExecutorTime::FakeTime { mono_reading_ns: t, mono_to_boot_offset_ns } => {
276                // The two atomic values are loaded one after the other. This should
277                // not normally be an issue in tests.
278                let fake_mono_now = MonotonicInstant::from_nanos(t.load(Ordering::Relaxed));
279                let boot_offset_ns = mono_to_boot_offset_ns.load(Ordering::Relaxed);
280                BootInstant::from_nanos(fake_mono_now.into_nanos() + boot_offset_ns)
281            }
282        }
283    }
284
285    /// Sets the reading of the fake monotonic clock.
286    ///
287    /// # Panics
288    ///
289    /// If called on an executor that runs in real time.
290    pub fn set_fake_time(&self, new: MonotonicInstant) {
291        let boot_offset_ns = match &self.time {
292            ExecutorTime::RealTime => {
293                panic!("Error: called `set_fake_time` on an executor using actual time.")
294            }
295            ExecutorTime::FakeTime { mono_reading_ns: t, mono_to_boot_offset_ns } => {
296                t.store(new.into_nanos(), Ordering::Relaxed);
297                mono_to_boot_offset_ns.load(Ordering::Relaxed)
298            }
299        };
300        self.monotonic_timers.maybe_notify(new);
301
302        // Changing fake time also affects boot time.  Notify boot clocks as well.
303        let new_boot_time = BootInstant::from_nanos(new.into_nanos() + boot_offset_ns);
304        self.boot_timers.maybe_notify(new_boot_time);
305    }
306
307    // Sets a new offset between boot and monotonic time.
308    //
309    // Only works for executors operating in fake time.
310    // The change in the fake offset will wake expired boot timers.
311    pub fn set_fake_boot_to_mono_offset(&self, offset: BootDuration) {
312        let mono_now_ns = match &self.time {
313            ExecutorTime::RealTime => {
314                panic!(
315                    "Error: called `set_fake_boot_to_mono_offset` on an executor using actual time."
316                )
317            }
318            ExecutorTime::FakeTime { mono_reading_ns: t, mono_to_boot_offset_ns: b } => {
319                // We ignore the non-atomic update between b and t, it is likely
320                // not relevant in tests.
321                b.store(offset.into_nanos(), Ordering::Relaxed);
322                t.load(Ordering::Relaxed)
323            }
324        };
325        let new_boot_now = BootInstant::from_nanos(mono_now_ns) + offset;
326        self.boot_timers.maybe_notify(new_boot_now);
327    }
328
329    /// Returns `true` if this executor is running in real time.  Returns
330    /// `false` if this executor si running in fake time.
331    pub fn is_real_time(&self) -> bool {
332        matches!(self.time, ExecutorTime::RealTime)
333    }
334
335    /// Must be called before `on_parent_drop`.
336    ///
337    /// Done flag must be set before dropping packet receivers
338    /// so that future receivers that attempt to deregister themselves
339    /// know that it's okay if their entries are already missing.
340    pub fn mark_done(&self) {
341        self.done.store(true, Ordering::SeqCst);
342
343        // Make sure there's at least one notification outstanding per thread to wake up all
344        // workers. This might be more notifications than required, but this way we don't have to
345        // worry about races where tasks are just about to sleep; when a task receives the
346        // notification, it will check done and terminate.
347        let mut threads_state = ThreadsState(self.threads_state.load(Ordering::Relaxed));
348        let num_threads = self.num_threads;
349        loop {
350            let notified = threads_state.notified();
351            if notified >= num_threads {
352                break;
353            }
354            match self.threads_state.compare_exchange_weak(
355                threads_state.0,
356                threads_state.with_notified(num_threads).0,
357                Ordering::Relaxed,
358                Ordering::Relaxed,
359            ) {
360                Ok(_) => {
361                    for _ in notified..num_threads {
362                        self.notify_id(TASK_READY_WAKEUP_ID);
363                    }
364                    return;
365                }
366                Err(old) => threads_state = ThreadsState(old),
367            }
368        }
369    }
370
371    /// Notes about the lifecycle of an Executor.
372    ///
373    /// a) The Executor stands as the only way to run a reactor based on a Fuchsia port, but the
374    /// lifecycle of the port itself is not currently tied to it. Executor vends clones of its
375    /// inner Arc structure to all receivers, so we don't have a type-safe way of ensuring that
376    /// the port is dropped alongside the Executor as it should.
377    /// TODO(https://fxbug.dev/42154828): Ensure the port goes away with the executor.
378    ///
379    /// b) The Executor's lifetime is also tied to the thread-local variable pointing to the
380    /// "current" executor being set, and that's unset when the executor is dropped.
381    ///
382    /// Point (a) is related to "what happens if I use a receiver after the executor is dropped",
383    /// and point (b) is related to "what happens when I try to create a new receiver when there
384    /// is no executor".
385    ///
386    /// Tokio, for example, encodes the lifetime of the reactor separately from the thread-local
387    /// storage [1]. And the reactor discourages usage of strong references to it by vending weak
388    /// references to it [2] instead of strong.
389    ///
390    /// There are pros and cons to both strategies. For (a), tokio encourages (but doesn't
391    /// enforce [3]) type-safety by vending weak pointers, but those add runtime overhead when
392    /// upgrading pointers. For (b) the difference mostly stand for "when is it safe to use IO
393    /// objects/receivers". Tokio says it's only safe to use them whenever a guard is in scope.
394    /// Fuchsia-async says it's safe to use them when a fuchsia_async::Executor is still in scope
395    /// in that thread.
396    ///
397    /// This acts as a prelude to the panic encoded in Executor::drop when receivers haven't
398    /// unregistered themselves when the executor drops. The choice to panic was made based on
399    /// patterns in fuchsia-async that may come to change:
400    ///
401    /// - Executor vends strong references to itself and those references are *stored* by most
402    ///   receiver implementations (as opposed to reached out on TLS every time).
403    /// - Fuchsia-async objects return zx::Status on wait calls, there isn't an appropriate and
404    ///   easy to understand error to return when polling on an extinct executor.
405    /// - All receivers are implemented in this crate and well-known.
406    ///
407    /// [1]: https://docs.rs/tokio/1.5.0/tokio/runtime/struct.Runtime.html#method.enter
408    /// [2]: https://github.com/tokio-rs/tokio/blob/b42f21ec3e212ace25331d0c13889a45769e6006/tokio/src/signal/unix/driver.rs#L35
409    /// [3]: by returning an upgraded Arc, tokio trusts callers to not "use it for too long", an
410    /// opaque non-clone-copy-or-send guard would be stronger than this. See:
411    /// https://github.com/tokio-rs/tokio/blob/b42f21ec3e212ace25331d0c13889a45769e6006/tokio/src/io/driver/mod.rs#L297
412    pub fn on_parent_drop(&self, root_scope: &ScopeHandle) {
413        // Drop all tasks.
414        // Any use of fasync::unblock can involve a waker. Wakers hold weak references to tasks, but
415        // as part of waking, there's an upgrade to a strong reference, so for a small amount of
416        // time `fasync::unblock` can hold a strong reference to a task which in turn holds the
417        // future for the task which in turn could hold references to receivers, which, if we did
418        // nothing about it, would trip the assertion below. For that reason, we forcibly drop the
419        // task futures here.
420        root_scope.drop_all_tasks();
421
422        // Drop all of the uncompleted tasks
423        while self.ready_tasks.pop().is_some() {}
424
425        // Deregister the timer receivers so that we can perform the check below.
426        self.monotonic_timers.deregister();
427        self.boot_timers.deregister();
428
429        // Do not allow any receivers to outlive the executor. That's very likely a bug waiting to
430        // happen. See discussion above.
431        //
432        // If you're here because you hit this panic check your code for:
433        //
434        // - A struct that contains a fuchsia_async::Executor NOT in the last position (last
435        // position gets dropped last: https://doc.rust-lang.org/reference/destructors.html).
436        //
437        // - A function scope that contains a fuchsia_async::Executor NOT in the first position
438        // (first position in function scope gets dropped last:
439        // https://doc.rust-lang.org/reference/destructors.html?highlight=scope#drop-scopes).
440        //
441        // - A function that holds a `fuchsia_async::Executor` in scope and whose last statement
442        // contains a temporary (temporaries are dropped after the function scope:
443        // https://doc.rust-lang.org/reference/destructors.html#temporary-scopes). This usually
444        // looks like a `match` statement at the end of the function without a semicolon.
445        //
446        // - Storing channel and FIDL objects in static variables.
447        //
448        // - fuchsia_async::unblock calls that move channels or FIDL objects to another thread.
449        assert!(self.receivers.is_empty(), "receivers must not outlive their executor");
450
451        // Remove the thread-local executor set in `new`.
452        EHandle::rm_local();
453    }
454
455    // The debugger looks for this function on the stack, so if its (fully-qualified) name changes,
456    // the debugger needs to be updated.
457    // LINT.IfChange
458    pub fn worker_lifecycle<const UNTIL_STALLED: bool>(
459        self: &Arc<Executor>,
460        // The main task should be specified for a single-threaded executor, but it isn't necessary
461        // for a multi-threaded executor since it has an independent mechanism for tracking when
462        // the main task terminates.
463        // TODO(https://fxbug.dev/481030722) remove this parameter
464        main_task: Option<&TaskHandle>,
465    ) {
466        // LINT.ThenChange(//src/developer/debug/zxdb/console/commands/verb_async_backtrace.cc)
467
468        assert!(
469            !self.is_local() || self.first_thread_id.get() == Some(&std::thread::current().id())
470        );
471
472        self.monotonic_timers.register(self);
473        self.boot_timers.register(self);
474
475        loop {
476            // Keep track of whether we are considered asleep.
477            let mut sleeping = false;
478
479            const ONE_SLEEPING: ThreadsState = ThreadsState(0).with_sleeping(1);
480
481            match self.poll_ready_tasks(main_task) {
482                PollReadyTasksResult::NoneReady => {
483                    // No more tasks, indicate we are sleeping. We use SeqCst ordering because we
484                    // want this change here to happen *before* we check ready_tasks below. This
485                    // synchronizes with notify_task_ready which is called *after* a task is added
486                    // to ready_tasks.
487                    self.threads_state.fetch_add(ONE_SLEEPING.0, Ordering::SeqCst);
488                    // Check ready tasks again. If a task got posted, wake up. This has to be done
489                    // because a notification won't get sent if there is at least one active thread
490                    // so there's a window between the preceding two lines where a task could be
491                    // made ready and a notification is not sent because it looks like there is at
492                    // least one thread running.
493                    if self.ready_tasks.is_empty() {
494                        sleeping = true;
495                    } else {
496                        // We lost a race, we're no longer sleeping.
497                        self.threads_state.fetch_sub(ONE_SLEEPING.0, Ordering::Relaxed);
498                    }
499                }
500                PollReadyTasksResult::MoreReady => {}
501                PollReadyTasksResult::MainTaskCompleted => return,
502            }
503
504            // Check done here after updating threads_state to avoid shutdown races.
505            if self.done.load(Ordering::SeqCst) {
506                if sleeping {
507                    self.threads_state.fetch_sub(ONE_SLEEPING.0, Ordering::Relaxed);
508                }
509                return;
510            }
511
512            enum Work {
513                None,
514                Packet(zx::Packet),
515                Stalled,
516            }
517
518            let mut notified = false;
519            let work = {
520                // If we're considered awake choose INFINITE_PAST which will make the wait call
521                // return immediately.  Otherwise, wait until a packet arrives.
522                let deadline = if !sleeping || UNTIL_STALLED {
523                    zx::Instant::INFINITE_PAST
524                } else {
525                    zx::Instant::INFINITE
526                };
527
528                match self.port.wait(deadline) {
529                    Ok(packet) => {
530                        if packet.key() == TASK_READY_WAKEUP_ID {
531                            notified = true;
532                            Work::None
533                        } else {
534                            Work::Packet(packet)
535                        }
536                    }
537                    Err(zx::Status::TIMED_OUT) => {
538                        if !UNTIL_STALLED || !sleeping {
539                            Work::None
540                        } else {
541                            Work::Stalled
542                        }
543                    }
544                    Err(status) => {
545                        panic!("Error calling port wait: {status:?}");
546                    }
547                }
548            };
549
550            let threads_state_sub =
551                ThreadsState(0).with_sleeping(sleeping as u8).with_notified(notified as u8);
552            if threads_state_sub.0 > 0 {
553                self.threads_state.fetch_sub(threads_state_sub.0, Ordering::Relaxed);
554            }
555
556            match work {
557                Work::Packet(packet) => self.receivers.receive_packet(packet.key(), packet),
558                Work::None => {}
559                Work::Stalled => return,
560            }
561        }
562    }
563
564    /// Drops the main task.
565    ///
566    /// # Safety
567    ///
568    /// The caller must guarantee that the executor isn't running.
569    pub(super) unsafe fn drop_main_task(&self, root_scope: &ScopeHandle, task: &TaskHandle) {
570        unsafe { root_scope.drop_task_unchecked(task) };
571    }
572
573    fn try_poll(&self, task: TaskHandle) -> bool {
574        let task_waker = task.waker();
575        let poll_result = TaskHandle::set_current_with(&task, || {
576            task.try_poll(&mut Context::from_waker(&task_waker))
577        });
578        if !task.is_low_priority() {
579            self.last_active.store(self.now().into_nanos(), Ordering::Relaxed);
580        }
581        match poll_result {
582            AttemptPollResult::Yield => {
583                self.ready_tasks.push(task);
584                false
585            }
586            AttemptPollResult::IFinished | AttemptPollResult::Aborted => {
587                task.scope().task_did_finish(&task);
588                true
589            }
590            _ => false,
591        }
592    }
593
594    /// Returns the monotonic timers.
595    pub fn monotonic_timers(&self) -> &Timers<MonotonicInstant> {
596        &self.monotonic_timers
597    }
598
599    /// Returns the boot timers.
600    pub fn boot_timers(&self) -> &Timers<BootInstant> {
601        &self.boot_timers
602    }
603
604    fn poll_tasks(&self, callback: impl FnOnce(), main_task: Option<&TaskHandle>) {
605        assert!(!self.is_local);
606
607        // Increment the count of foreign threads.
608        const ONE_FOREIGN: ThreadsState = ThreadsState(0).with_foreign(1);
609        self.threads_state.fetch_add(ONE_FOREIGN.0, Ordering::Relaxed);
610
611        callback();
612
613        // Poll up to 16 tasks.
614        for _ in 0..16 {
615            let Some(task) = self.ready_tasks.pop() else {
616                break;
617            };
618            let is_main = Some(&task) == main_task;
619            if self.try_poll(task) && is_main {
620                break;
621            }
622            self.polled.fetch_add(1, Ordering::Relaxed);
623        }
624
625        let mut threads_state = ThreadsState(
626            self.threads_state.fetch_sub(ONE_FOREIGN.0, Ordering::SeqCst) - ONE_FOREIGN.0,
627        );
628
629        if !self.ready_tasks.is_empty() {
630            // There are tasks still ready to run, so wake up a thread if all the other threads are
631            // sleeping.
632            while threads_state == ThreadsState(0).with_sleeping(self.num_threads) {
633                match self.try_notify(threads_state) {
634                    Ok(()) => break,
635                    Err(s) => threads_state = s,
636                }
637            }
638        }
639    }
640
641    pub fn task_is_ready(&self, task: TaskHandle) {
642        self.ready_tasks.push(task);
643        self.notify_task_ready();
644    }
645
646    pub(crate) fn last_active(&self) -> MonotonicInstant {
647        MonotonicInstant::from_nanos(self.last_active.load(Ordering::Relaxed))
648    }
649}
650
651#[cfg(test)]
652impl Drop for Executor {
653    fn drop(&mut self) {
654        ACTIVE_EXECUTORS.fetch_sub(1, Ordering::Relaxed);
655    }
656}
657
658/// A handle to an executor.
659#[derive(Clone)]
660pub struct EHandle {
661    // LINT.IfChange
662    pub(super) root_scope: ScopeHandle,
663    // LINT.ThenChange(//src/developer/debug/zxdb/console/commands/verb_async_backtrace.cc)
664}
665
666impl fmt::Debug for EHandle {
667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668        f.debug_struct("EHandle").field("port", &self.inner().port).finish()
669    }
670}
671
672impl EHandle {
673    /// Returns the thread-local executor.
674    ///
675    /// # Panics
676    ///
677    /// If called outside the context of an active async executor.
678    pub fn local() -> Self {
679        let root_scope = EXECUTOR
680            .with(|e| e.borrow().as_ref().map(|x| x.clone()))
681            .expect("Fuchsia Executor must be created first");
682
683        EHandle { root_scope }
684    }
685
686    /// Set the fake time to a given value.
687    ///
688    /// # Panics
689    ///
690    /// If the executor was not created with fake time.
691    pub fn set_fake_time(&self, t: MonotonicInstant) {
692        self.inner().set_fake_time(t)
693    }
694
695    /// Gets the current time as the executor sees it. May be fake time if the executor is running
696    /// on fake time.
697    pub fn now(&self) -> MonotonicInstant {
698        self.inner().now()
699    }
700
701    pub(super) fn rm_local() {
702        EXECUTOR.with(|e| *e.borrow_mut() = None);
703    }
704
705    /// The root scope of the executor.
706    ///
707    /// This can be used to spawn tasks that live as long as the executor, and
708    /// to create shorter-lived child scopes.
709    ///
710    /// Most users should create an owned scope with
711    /// [`Scope::new_with_name`][crate::Scope::new_with_name] instead of using this method.
712    pub fn global_scope(&self) -> &ScopeHandle {
713        &self.root_scope
714    }
715
716    /// Get a reference to the Fuchsia `zx::Port` being used to listen for events.
717    pub fn port(&self) -> &zx::Port {
718        &self.inner().port
719    }
720
721    /// Registers a `PacketReceiver` with the executor and returns a registration.
722    /// The `PacketReceiver` will be deregistered when the `Registration` is dropped.
723    pub fn register_receiver<T: PacketReceiver>(&self, receiver: T) -> ReceiverRegistration<T> {
724        self.inner().receivers.register(self.inner().clone(), receiver)
725    }
726
727    /// Registers a pinned `RawPacketReceiver` with the executor.
728    ///
729    /// The registration will be deregistered when dropped.
730    ///
731    /// NOTE: Unlike with `register_receiver`, `receive_packet` will be called whilst a lock is
732    /// held, so it is not safe to register or unregister receivers at that time.
733    pub fn register_pinned<T: PacketReceiver>(
734        &self,
735        raw_registration: Pin<&mut RawReceiverRegistration<T>>,
736    ) {
737        self.inner().receivers.register_pinned(self.clone(), raw_registration);
738    }
739
740    #[inline(always)]
741    pub(crate) fn inner(&self) -> &Arc<Executor> {
742        self.root_scope.executor()
743    }
744
745    /// Spawn a new task to be run on this executor.
746    ///
747    /// Tasks spawned using this method must be thread-safe (implement the `Send` trait), as they
748    /// may be run on either a singlethreaded or multithreaded executor.
749    pub fn spawn_detached(&self, future: impl Future<Output = ()> + Send + 'static) {
750        self.global_scope().spawn(future);
751    }
752
753    /// Spawn a new task to be run on this executor.
754    ///
755    /// This is similar to the `spawn_detached` method, but tasks spawned using this method do not
756    /// have to be threads-safe (implement the `Send` trait). In return, this method requires that
757    /// this executor is a LocalExecutor.
758    pub fn spawn_local_detached(&self, future: impl Future<Output = ()> + 'static) {
759        self.global_scope().spawn_local(future);
760    }
761
762    pub(crate) fn mono_timers(&self) -> &Arc<Timers<MonotonicInstant>> {
763        &self.inner().monotonic_timers
764    }
765
766    pub(crate) fn boot_timers(&self) -> &Arc<Timers<BootInstant>> {
767        &self.inner().boot_timers
768    }
769
770    /// Calls `callback` in the context of the executor and then polls (a limited number of) tasks
771    /// that are ready to run.  If tasks remain ready and no other threads are running, a thread
772    /// will be woken.  This can end up being a performance win in the case that the queue can be
773    /// cleared without needing to wake any other thread.
774    ///
775    /// # Panics
776    ///
777    /// If called on a single-threaded executor or if this thread is a thread managed by the
778    /// executor.
779    pub fn poll_tasks(&self, callback: impl FnOnce()) {
780        EXECUTOR.with(|e| {
781            assert!(
782                e.borrow_mut().replace(self.root_scope.clone()).is_none(),
783                "This thread is already associated with an executor"
784            );
785        });
786
787        // `poll_tasks` should only ever be used on a multi-threaded executor, it's safe to pass
788        // None.
789        self.inner().poll_tasks(callback, None);
790
791        EXECUTOR.with(|e| *e.borrow_mut() = None);
792    }
793}
794
795// AtomicFutureHandle can have a lifetime (for local executors we allow the main task to have a
796// non-static lifetime).  The executor doesn't handle this though; the executor just assumes all
797// tasks have the 'static lifetime.  It's up to the local executor to extend the lifetime and make
798// it safe.
799pub type TaskHandle = AtomicFutureHandle<'static>;
800
801thread_local! {
802    static CURRENT_TASK: Cell<*const TaskHandle> = const { Cell::new(std::ptr::null()) };
803}
804
805impl TaskHandle {
806    pub(crate) fn with_current<R>(f: impl FnOnce(Option<&TaskHandle>) -> R) -> R {
807        CURRENT_TASK.with(|cur| {
808            let cur = cur.get();
809            let cur = unsafe { cur.as_ref() };
810            f(cur)
811        })
812    }
813
814    fn set_current_with<R>(task: &TaskHandle, f: impl FnOnce() -> R) -> R {
815        CURRENT_TASK.with(|cur| {
816            cur.set(task);
817            let result = f();
818            cur.set(std::ptr::null());
819            result
820        })
821    }
822}
823
824#[cfg(test)]
825mod tests {
826    use super::{ACTIVE_EXECUTORS, EHandle};
827    use crate::{LocalExecutorBuilder, SendExecutorBuilder};
828    use std::sync::Arc;
829    use std::sync::atomic::{AtomicU64, Ordering};
830
831    #[test]
832    fn test_no_leaks() {
833        std::thread::spawn(|| SendExecutorBuilder::new().num_threads(1).build().run(async {}))
834            .join()
835            .unwrap();
836
837        assert_eq!(ACTIVE_EXECUTORS.load(Ordering::Relaxed), 0);
838    }
839
840    #[test]
841    fn poll_tasks() {
842        SendExecutorBuilder::new().num_threads(1).build().run(async {
843            let ehandle = EHandle::local();
844
845            // This will tie up the executor's only running thread which ensures that the task
846            // we spawn below can only run on the foreign thread.
847            std::thread::spawn(move || {
848                let ran = Arc::new(AtomicU64::new(0));
849                ehandle.poll_tasks(|| {
850                    let ran = ran.clone();
851                    ehandle.spawn_detached(async move {
852                        ran.fetch_add(1, Ordering::Relaxed);
853                    });
854                });
855
856                // The spawned task should have run in this thread.
857                assert_eq!(ran.load(Ordering::Relaxed), 1);
858            })
859            .join()
860            .unwrap();
861        });
862    }
863
864    #[test]
865    #[should_panic]
866    fn spawn_local_from_different_thread() {
867        let _executor = LocalExecutorBuilder::new().build();
868        let ehandle = EHandle::local();
869        let _ = std::thread::spawn(move || {
870            ehandle.spawn_local_detached(async {});
871        })
872        .join();
873    }
874}