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 match self.poll_ready_tasks(main_task) {
480 PollReadyTasksResult::NoneReady => {
481 // No more tasks, indicate we are sleeping. We use SeqCst ordering because we
482 // want this change here to happen *before* we check ready_tasks below. This
483 // synchronizes with notify_task_ready which is called *after* a task is added
484 // to ready_tasks.
485 const ONE_SLEEPING: ThreadsState = ThreadsState(0).with_sleeping(1);
486 self.threads_state.fetch_add(ONE_SLEEPING.0, Ordering::SeqCst);
487 // Check ready tasks again. If a task got posted, wake up. This has to be done
488 // because a notification won't get sent if there is at least one active thread
489 // so there's a window between the preceding two lines where a task could be
490 // made ready and a notification is not sent because it looks like there is at
491 // least one thread running.
492 if self.ready_tasks.is_empty() {
493 sleeping = true;
494 } else {
495 // We lost a race, we're no longer sleeping.
496 self.threads_state.fetch_sub(ONE_SLEEPING.0, Ordering::Relaxed);
497 }
498 }
499 PollReadyTasksResult::MoreReady => {}
500 PollReadyTasksResult::MainTaskCompleted => return,
501 }
502
503 // Check done here after updating threads_state to avoid shutdown races.
504 if self.done.load(Ordering::SeqCst) {
505 return;
506 }
507
508 enum Work {
509 None,
510 Packet(zx::Packet),
511 Stalled,
512 }
513
514 let mut notified = false;
515 let work = {
516 // If we're considered awake choose INFINITE_PAST which will make the wait call
517 // return immediately. Otherwise, wait until a packet arrives.
518 let deadline = if !sleeping || UNTIL_STALLED {
519 zx::Instant::INFINITE_PAST
520 } else {
521 zx::Instant::INFINITE
522 };
523
524 match self.port.wait(deadline) {
525 Ok(packet) => {
526 if packet.key() == TASK_READY_WAKEUP_ID {
527 notified = true;
528 Work::None
529 } else {
530 Work::Packet(packet)
531 }
532 }
533 Err(zx::Status::TIMED_OUT) => {
534 if !UNTIL_STALLED || !sleeping {
535 Work::None
536 } else {
537 Work::Stalled
538 }
539 }
540 Err(status) => {
541 panic!("Error calling port wait: {status:?}");
542 }
543 }
544 };
545
546 let threads_state_sub =
547 ThreadsState(0).with_sleeping(sleeping as u8).with_notified(notified as u8);
548 if threads_state_sub.0 > 0 {
549 self.threads_state.fetch_sub(threads_state_sub.0, Ordering::Relaxed);
550 }
551
552 match work {
553 Work::Packet(packet) => self.receivers.receive_packet(packet.key(), packet),
554 Work::None => {}
555 Work::Stalled => return,
556 }
557 }
558 }
559
560 /// Drops the main task.
561 ///
562 /// # Safety
563 ///
564 /// The caller must guarantee that the executor isn't running.
565 pub(super) unsafe fn drop_main_task(&self, root_scope: &ScopeHandle, task: &TaskHandle) {
566 unsafe { root_scope.drop_task_unchecked(task) };
567 }
568
569 fn try_poll(&self, task: TaskHandle) -> bool {
570 let task_waker = task.waker();
571 let poll_result = TaskHandle::set_current_with(&task, || {
572 task.try_poll(&mut Context::from_waker(&task_waker))
573 });
574 if !task.is_low_priority() {
575 self.last_active.store(self.now().into_nanos(), Ordering::Relaxed);
576 }
577 match poll_result {
578 AttemptPollResult::Yield => {
579 self.ready_tasks.push(task);
580 false
581 }
582 AttemptPollResult::IFinished | AttemptPollResult::Aborted => {
583 task.scope().task_did_finish(&task);
584 true
585 }
586 _ => false,
587 }
588 }
589
590 /// Returns the monotonic timers.
591 pub fn monotonic_timers(&self) -> &Timers<MonotonicInstant> {
592 &self.monotonic_timers
593 }
594
595 /// Returns the boot timers.
596 pub fn boot_timers(&self) -> &Timers<BootInstant> {
597 &self.boot_timers
598 }
599
600 fn poll_tasks(&self, callback: impl FnOnce(), main_task: Option<&TaskHandle>) {
601 assert!(!self.is_local);
602
603 // Increment the count of foreign threads.
604 const ONE_FOREIGN: ThreadsState = ThreadsState(0).with_foreign(1);
605 self.threads_state.fetch_add(ONE_FOREIGN.0, Ordering::Relaxed);
606
607 callback();
608
609 // Poll up to 16 tasks.
610 for _ in 0..16 {
611 let Some(task) = self.ready_tasks.pop() else {
612 break;
613 };
614 let is_main = Some(&task) == main_task;
615 if self.try_poll(task) && is_main {
616 break;
617 }
618 self.polled.fetch_add(1, Ordering::Relaxed);
619 }
620
621 let mut threads_state = ThreadsState(
622 self.threads_state.fetch_sub(ONE_FOREIGN.0, Ordering::SeqCst) - ONE_FOREIGN.0,
623 );
624
625 if !self.ready_tasks.is_empty() {
626 // There are tasks still ready to run, so wake up a thread if all the other threads are
627 // sleeping.
628 while threads_state == ThreadsState(0).with_sleeping(self.num_threads) {
629 match self.try_notify(threads_state) {
630 Ok(()) => break,
631 Err(s) => threads_state = s,
632 }
633 }
634 }
635 }
636
637 pub fn task_is_ready(&self, task: TaskHandle) {
638 self.ready_tasks.push(task);
639 self.notify_task_ready();
640 }
641
642 pub(crate) fn last_active(&self) -> MonotonicInstant {
643 MonotonicInstant::from_nanos(self.last_active.load(Ordering::Relaxed))
644 }
645}
646
647#[cfg(test)]
648impl Drop for Executor {
649 fn drop(&mut self) {
650 ACTIVE_EXECUTORS.fetch_sub(1, Ordering::Relaxed);
651 }
652}
653
654/// A handle to an executor.
655#[derive(Clone)]
656pub struct EHandle {
657 // LINT.IfChange
658 pub(super) root_scope: ScopeHandle,
659 // LINT.ThenChange(//src/developer/debug/zxdb/console/commands/verb_async_backtrace.cc)
660}
661
662impl fmt::Debug for EHandle {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 f.debug_struct("EHandle").field("port", &self.inner().port).finish()
665 }
666}
667
668impl EHandle {
669 /// Returns the thread-local executor.
670 ///
671 /// # Panics
672 ///
673 /// If called outside the context of an active async executor.
674 pub fn local() -> Self {
675 let root_scope = EXECUTOR
676 .with(|e| e.borrow().as_ref().map(|x| x.clone()))
677 .expect("Fuchsia Executor must be created first");
678
679 EHandle { root_scope }
680 }
681
682 /// Set the fake time to a given value.
683 ///
684 /// # Panics
685 ///
686 /// If the executor was not created with fake time.
687 pub fn set_fake_time(&self, t: MonotonicInstant) {
688 self.inner().set_fake_time(t)
689 }
690
691 /// Gets the current time as the executor sees it. May be fake time if the executor is running
692 /// on fake time.
693 pub fn now(&self) -> MonotonicInstant {
694 self.inner().now()
695 }
696
697 pub(super) fn rm_local() {
698 EXECUTOR.with(|e| *e.borrow_mut() = None);
699 }
700
701 /// The root scope of the executor.
702 ///
703 /// This can be used to spawn tasks that live as long as the executor, and
704 /// to create shorter-lived child scopes.
705 ///
706 /// Most users should create an owned scope with
707 /// [`Scope::new_with_name`][crate::Scope::new_with_name] instead of using this method.
708 pub fn global_scope(&self) -> &ScopeHandle {
709 &self.root_scope
710 }
711
712 /// Get a reference to the Fuchsia `zx::Port` being used to listen for events.
713 pub fn port(&self) -> &zx::Port {
714 &self.inner().port
715 }
716
717 /// Registers a `PacketReceiver` with the executor and returns a registration.
718 /// The `PacketReceiver` will be deregistered when the `Registration` is dropped.
719 pub fn register_receiver<T: PacketReceiver>(&self, receiver: T) -> ReceiverRegistration<T> {
720 self.inner().receivers.register(self.inner().clone(), receiver)
721 }
722
723 /// Registers a pinned `RawPacketReceiver` with the executor.
724 ///
725 /// The registration will be deregistered when dropped.
726 ///
727 /// NOTE: Unlike with `register_receiver`, `receive_packet` will be called whilst a lock is
728 /// held, so it is not safe to register or unregister receivers at that time.
729 pub fn register_pinned<T: PacketReceiver>(
730 &self,
731 raw_registration: Pin<&mut RawReceiverRegistration<T>>,
732 ) {
733 self.inner().receivers.register_pinned(self.clone(), raw_registration);
734 }
735
736 #[inline(always)]
737 pub(crate) fn inner(&self) -> &Arc<Executor> {
738 self.root_scope.executor()
739 }
740
741 /// Spawn a new task to be run on this executor.
742 ///
743 /// Tasks spawned using this method must be thread-safe (implement the `Send` trait), as they
744 /// may be run on either a singlethreaded or multithreaded executor.
745 pub fn spawn_detached(&self, future: impl Future<Output = ()> + Send + 'static) {
746 self.global_scope().spawn(future);
747 }
748
749 /// Spawn a new task to be run on this executor.
750 ///
751 /// This is similar to the `spawn_detached` method, but tasks spawned using this method do not
752 /// have to be threads-safe (implement the `Send` trait). In return, this method requires that
753 /// this executor is a LocalExecutor.
754 pub fn spawn_local_detached(&self, future: impl Future<Output = ()> + 'static) {
755 self.global_scope().spawn_local(future);
756 }
757
758 pub(crate) fn mono_timers(&self) -> &Arc<Timers<MonotonicInstant>> {
759 &self.inner().monotonic_timers
760 }
761
762 pub(crate) fn boot_timers(&self) -> &Arc<Timers<BootInstant>> {
763 &self.inner().boot_timers
764 }
765
766 /// Calls `callback` in the context of the executor and then polls (a limited number of) tasks
767 /// that are ready to run. If tasks remain ready and no other threads are running, a thread
768 /// will be woken. This can end up being a performance win in the case that the queue can be
769 /// cleared without needing to wake any other thread.
770 ///
771 /// # Panics
772 ///
773 /// If called on a single-threaded executor or if this thread is a thread managed by the
774 /// executor.
775 pub fn poll_tasks(&self, callback: impl FnOnce()) {
776 EXECUTOR.with(|e| {
777 assert!(
778 e.borrow_mut().replace(self.root_scope.clone()).is_none(),
779 "This thread is already associated with an executor"
780 );
781 });
782
783 // `poll_tasks` should only ever be used on a multi-threaded executor, it's safe to pass
784 // None.
785 self.inner().poll_tasks(callback, None);
786
787 EXECUTOR.with(|e| *e.borrow_mut() = None);
788 }
789}
790
791// AtomicFutureHandle can have a lifetime (for local executors we allow the main task to have a
792// non-static lifetime). The executor doesn't handle this though; the executor just assumes all
793// tasks have the 'static lifetime. It's up to the local executor to extend the lifetime and make
794// it safe.
795pub type TaskHandle = AtomicFutureHandle<'static>;
796
797thread_local! {
798 static CURRENT_TASK: Cell<*const TaskHandle> = const { Cell::new(std::ptr::null()) };
799}
800
801impl TaskHandle {
802 pub(crate) fn with_current<R>(f: impl FnOnce(Option<&TaskHandle>) -> R) -> R {
803 CURRENT_TASK.with(|cur| {
804 let cur = cur.get();
805 let cur = unsafe { cur.as_ref() };
806 f(cur)
807 })
808 }
809
810 fn set_current_with<R>(task: &TaskHandle, f: impl FnOnce() -> R) -> R {
811 CURRENT_TASK.with(|cur| {
812 cur.set(task);
813 let result = f();
814 cur.set(std::ptr::null());
815 result
816 })
817 }
818}
819
820#[cfg(test)]
821mod tests {
822 use super::{ACTIVE_EXECUTORS, EHandle};
823 use crate::{LocalExecutorBuilder, SendExecutorBuilder};
824 use std::sync::Arc;
825 use std::sync::atomic::{AtomicU64, Ordering};
826
827 #[test]
828 fn test_no_leaks() {
829 std::thread::spawn(|| SendExecutorBuilder::new().num_threads(1).build().run(async {}))
830 .join()
831 .unwrap();
832
833 assert_eq!(ACTIVE_EXECUTORS.load(Ordering::Relaxed), 0);
834 }
835
836 #[test]
837 fn poll_tasks() {
838 SendExecutorBuilder::new().num_threads(1).build().run(async {
839 let ehandle = EHandle::local();
840
841 // This will tie up the executor's only running thread which ensures that the task
842 // we spawn below can only run on the foreign thread.
843 std::thread::spawn(move || {
844 let ran = Arc::new(AtomicU64::new(0));
845 ehandle.poll_tasks(|| {
846 let ran = ran.clone();
847 ehandle.spawn_detached(async move {
848 ran.fetch_add(1, Ordering::Relaxed);
849 });
850 });
851
852 // The spawned task should have run in this thread.
853 assert_eq!(ran.load(Ordering::Relaxed), 1);
854 })
855 .join()
856 .unwrap();
857 });
858 }
859
860 #[test]
861 #[should_panic]
862 fn spawn_local_from_different_thread() {
863 let _executor = LocalExecutorBuilder::new().build();
864 let ehandle = EHandle::local();
865 let _ = std::thread::spawn(move || {
866 ehandle.spawn_local_detached(async {});
867 })
868 .join();
869 }
870}