tokio/runtime/io/scheduled_io.rs
1use crate::io::interest::Interest;
2use crate::io::ready::Ready;
3use crate::loom::sync::atomic::AtomicUsize;
4use crate::loom::sync::Mutex;
5use crate::runtime::io::{Direction, ReadyEvent, Tick};
6use crate::util::bit;
7use crate::util::linked_list::{self, LinkedList};
8use crate::util::WakeList;
9
10use std::cell::UnsafeCell;
11use std::future::Future;
12use std::marker::PhantomPinned;
13use std::pin::Pin;
14use std::ptr::NonNull;
15use std::sync::atomic::Ordering::{AcqRel, Acquire};
16use std::task::{Context, Poll, Waker};
17
18/// Stored in the I/O driver resource slab.
19#[derive(Debug)]
20// # This struct should be cache padded to avoid false sharing. The cache padding rules are copied
21// from crossbeam-utils/src/cache_padded.rs
22//
23// Starting from Intel's Sandy Bridge, spatial prefetcher is now pulling pairs of 64-byte cache
24// lines at a time, so we have to align to 128 bytes rather than 64.
25//
26// Sources:
27// - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
28// - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107
29//
30// ARM's big.LITTLE architecture has asymmetric cores and "big" cores have 128-byte cache line size.
31//
32// Sources:
33// - https://www.mono-project.com/news/2016/09/12/arm64-icache/
34//
35// powerpc64 has 128-byte cache line size.
36//
37// Sources:
38// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_ppc64x.go#L9
39#[cfg_attr(
40 any(
41 target_arch = "x86_64",
42 target_arch = "aarch64",
43 target_arch = "powerpc64",
44 ),
45 repr(align(128))
46)]
47// arm, mips, mips64, sparc, and hexagon have 32-byte cache line size.
48//
49// Sources:
50// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_arm.go#L7
51// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips.go#L7
52// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mipsle.go#L7
53// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips64x.go#L9
54// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L17
55// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/hexagon/include/asm/cache.h#L12
56#[cfg_attr(
57 any(
58 target_arch = "arm",
59 target_arch = "mips",
60 target_arch = "mips64",
61 target_arch = "sparc",
62 target_arch = "hexagon",
63 ),
64 repr(align(32))
65)]
66// m68k has 16-byte cache line size.
67//
68// Sources:
69// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/m68k/include/asm/cache.h#L9
70#[cfg_attr(target_arch = "m68k", repr(align(16)))]
71// s390x has 256-byte cache line size.
72//
73// Sources:
74// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_s390x.go#L7
75// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/s390/include/asm/cache.h#L13
76#[cfg_attr(target_arch = "s390x", repr(align(256)))]
77// x86, riscv, wasm, and sparc64 have 64-byte cache line size.
78//
79// Sources:
80// - https://github.com/golang/go/blob/dda2991c2ea0c5914714469c4defc2562a907230/src/internal/cpu/cpu_x86.go#L9
81// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7
82// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L19
83// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/riscv/include/asm/cache.h#L10
84//
85// All others are assumed to have 64-byte cache line size.
86#[cfg_attr(
87 not(any(
88 target_arch = "x86_64",
89 target_arch = "aarch64",
90 target_arch = "powerpc64",
91 target_arch = "arm",
92 target_arch = "mips",
93 target_arch = "mips64",
94 target_arch = "sparc",
95 target_arch = "hexagon",
96 target_arch = "m68k",
97 target_arch = "s390x",
98 )),
99 repr(align(64))
100)]
101pub(crate) struct ScheduledIo {
102 pub(super) linked_list_pointers: UnsafeCell<linked_list::Pointers<Self>>,
103
104 /// Packs the resource's readiness and I/O driver latest tick.
105 readiness: AtomicUsize,
106
107 waiters: Mutex<Waiters>,
108}
109
110#[derive(Debug, Default)]
111struct Waiters {
112 /// List of all current waiters.
113 list: LinkedList<Waiter>,
114
115 /// Waker used for `AsyncRead`.
116 reader: Option<Waker>,
117
118 /// Waker used for `AsyncWrite`.
119 writer: Option<Waker>,
120}
121
122#[derive(Debug)]
123struct Waiter {
124 pointers: linked_list::Pointers<Waiter>,
125
126 /// The waker for this task.
127 waker: Option<Waker>,
128
129 /// The interest this waiter is waiting on.
130 interest: Interest,
131
132 is_ready: bool,
133
134 /// Should never be `Unpin`.
135 _p: PhantomPinned,
136}
137
138generate_addr_of_methods! {
139 impl<> Waiter {
140 unsafe fn addr_of_pointers(self: NonNull<Self>) -> NonNull<linked_list::Pointers<Waiter>> {
141 &self.pointers
142 }
143 }
144}
145
146/// Future returned by `readiness()`.
147struct Readiness<'a> {
148 scheduled_io: &'a ScheduledIo,
149
150 state: State,
151
152 /// Entry in the waiter `LinkedList`.
153 waiter: UnsafeCell<Waiter>,
154}
155
156enum State {
157 Init,
158 Waiting,
159 Done,
160}
161
162// The `ScheduledIo::readiness` (`AtomicUsize`) is packed full of goodness.
163//
164// | shutdown | driver tick | readiness |
165// |----------+-------------+-----------|
166// | 1 bit | 15 bits | 16 bits |
167
168const READINESS: bit::Pack = bit::Pack::least_significant(16);
169
170const TICK: bit::Pack = READINESS.then(15);
171
172const SHUTDOWN: bit::Pack = TICK.then(1);
173
174// ===== impl ScheduledIo =====
175
176impl Default for ScheduledIo {
177 fn default() -> ScheduledIo {
178 ScheduledIo {
179 linked_list_pointers: UnsafeCell::new(linked_list::Pointers::new()),
180 readiness: AtomicUsize::new(0),
181 waiters: Mutex::new(Waiters::default()),
182 }
183 }
184}
185
186impl ScheduledIo {
187 pub(crate) fn token(&self) -> mio::Token {
188 mio::Token(super::EXPOSE_IO.expose_provenance(self))
189 }
190
191 /// Invoked when the IO driver is shut down; forces this `ScheduledIo` into a
192 /// permanently shutdown state.
193 pub(super) fn shutdown(&self) {
194 let mask = SHUTDOWN.pack(1, 0);
195 self.readiness.fetch_or(mask, AcqRel);
196 self.wake(Ready::ALL);
197 }
198
199 /// Sets the readiness on this `ScheduledIo` by invoking the given closure on
200 /// the current value, returning the previous readiness value.
201 ///
202 /// # Arguments
203 /// - `tick`: whether setting the tick or trying to clear readiness for a
204 /// specific tick.
205 /// - `f`: a closure returning a new readiness value given the previous
206 /// readiness.
207 pub(super) fn set_readiness(&self, tick_op: Tick, f: impl Fn(Ready) -> Ready) {
208 let _ = self.readiness.fetch_update(AcqRel, Acquire, |curr| {
209 // If the io driver is shut down, then you are only allowed to clear readiness.
210 debug_assert!(SHUTDOWN.unpack(curr) == 0 || matches!(tick_op, Tick::Clear(_)));
211
212 const MAX_TICK: usize = TICK.max_value() + 1;
213 let tick = TICK.unpack(curr);
214
215 let new_tick = match tick_op {
216 // Trying to clear readiness with an old event!
217 Tick::Clear(t) if tick as u8 != t => return None,
218 Tick::Clear(t) => t as usize,
219 Tick::Set => tick.wrapping_add(1) % MAX_TICK,
220 };
221 let ready = Ready::from_usize(READINESS.unpack(curr));
222 Some(TICK.pack(new_tick, f(ready).as_usize()))
223 });
224 }
225
226 /// Notifies all pending waiters that have registered interest in `ready`.
227 ///
228 /// There may be many waiters to notify. Waking the pending task **must** be
229 /// done from outside of the lock otherwise there is a potential for a
230 /// deadlock.
231 ///
232 /// A stack array of wakers is created and filled with wakers to notify, the
233 /// lock is released, and the wakers are notified. Because there may be more
234 /// than 32 wakers to notify, if the stack array fills up, the lock is
235 /// released, the array is cleared, and the iteration continues.
236 pub(super) fn wake(&self, ready: Ready) {
237 let mut wakers = WakeList::new();
238
239 let mut waiters = self.waiters.lock();
240
241 // check for AsyncRead slot
242 if ready.is_readable() {
243 if let Some(waker) = waiters.reader.take() {
244 wakers.push(waker);
245 }
246 }
247
248 // check for AsyncWrite slot
249 if ready.is_writable() {
250 if let Some(waker) = waiters.writer.take() {
251 wakers.push(waker);
252 }
253 }
254
255 'outer: loop {
256 let mut iter = waiters.list.drain_filter(|w| ready.satisfies(w.interest));
257
258 while wakers.can_push() {
259 match iter.next() {
260 Some(waiter) => {
261 let waiter = unsafe { &mut *waiter.as_ptr() };
262
263 if let Some(waker) = waiter.waker.take() {
264 waiter.is_ready = true;
265 wakers.push(waker);
266 }
267 }
268 None => {
269 break 'outer;
270 }
271 }
272 }
273
274 drop(waiters);
275
276 wakers.wake_all();
277
278 // Acquire the lock again.
279 waiters = self.waiters.lock();
280 }
281
282 // Release the lock before notifying
283 drop(waiters);
284
285 wakers.wake_all();
286 }
287
288 pub(super) fn ready_event(&self, interest: Interest) -> ReadyEvent {
289 let curr = self.readiness.load(Acquire);
290
291 ReadyEvent {
292 tick: TICK.unpack(curr) as u8,
293 ready: interest.mask() & Ready::from_usize(READINESS.unpack(curr)),
294 is_shutdown: SHUTDOWN.unpack(curr) != 0,
295 }
296 }
297
298 /// Polls for readiness events in a given direction.
299 ///
300 /// These are to support `AsyncRead` and `AsyncWrite` polling methods,
301 /// which cannot use the `async fn` version. This uses reserved reader
302 /// and writer slots.
303 pub(super) fn poll_readiness(
304 &self,
305 cx: &mut Context<'_>,
306 direction: Direction,
307 ) -> Poll<ReadyEvent> {
308 let curr = self.readiness.load(Acquire);
309
310 let ready = direction.mask() & Ready::from_usize(READINESS.unpack(curr));
311 let is_shutdown = SHUTDOWN.unpack(curr) != 0;
312
313 if ready.is_empty() && !is_shutdown {
314 // Update the task info
315 let mut waiters = self.waiters.lock();
316 let waker = match direction {
317 Direction::Read => &mut waiters.reader,
318 Direction::Write => &mut waiters.writer,
319 };
320
321 // Avoid cloning the waker if one is already stored that matches the
322 // current task.
323 match waker {
324 Some(waker) => waker.clone_from(cx.waker()),
325 None => *waker = Some(cx.waker().clone()),
326 }
327
328 // Try again, in case the readiness was changed while we were
329 // taking the waiters lock
330 let curr = self.readiness.load(Acquire);
331 let ready = direction.mask() & Ready::from_usize(READINESS.unpack(curr));
332 let is_shutdown = SHUTDOWN.unpack(curr) != 0;
333 if is_shutdown {
334 Poll::Ready(ReadyEvent {
335 tick: TICK.unpack(curr) as u8,
336 ready: direction.mask(),
337 is_shutdown,
338 })
339 } else if ready.is_empty() {
340 Poll::Pending
341 } else {
342 Poll::Ready(ReadyEvent {
343 tick: TICK.unpack(curr) as u8,
344 ready,
345 is_shutdown,
346 })
347 }
348 } else {
349 Poll::Ready(ReadyEvent {
350 tick: TICK.unpack(curr) as u8,
351 ready,
352 is_shutdown,
353 })
354 }
355 }
356
357 pub(crate) fn clear_readiness(&self, event: ReadyEvent) {
358 // This consumes the current readiness state **except** for closed
359 // states. Closed states are excluded because they are final states.
360 let mask_no_closed = event.ready - Ready::READ_CLOSED - Ready::WRITE_CLOSED;
361 self.set_readiness(Tick::Clear(event.tick), |curr| curr - mask_no_closed);
362 }
363
364 pub(crate) fn clear_wakers(&self) {
365 let mut waiters = self.waiters.lock();
366 waiters.reader.take();
367 waiters.writer.take();
368 }
369}
370
371impl Drop for ScheduledIo {
372 fn drop(&mut self) {
373 self.wake(Ready::ALL);
374 }
375}
376
377unsafe impl Send for ScheduledIo {}
378unsafe impl Sync for ScheduledIo {}
379
380impl ScheduledIo {
381 /// An async version of `poll_readiness` which uses a linked list of wakers.
382 pub(crate) async fn readiness(&self, interest: Interest) -> ReadyEvent {
383 self.readiness_fut(interest).await
384 }
385
386 // This is in a separate function so that the borrow checker doesn't think
387 // we are borrowing the `UnsafeCell` possibly over await boundaries.
388 //
389 // Go figure.
390 fn readiness_fut(&self, interest: Interest) -> Readiness<'_> {
391 Readiness {
392 scheduled_io: self,
393 state: State::Init,
394 waiter: UnsafeCell::new(Waiter {
395 pointers: linked_list::Pointers::new(),
396 waker: None,
397 is_ready: false,
398 interest,
399 _p: PhantomPinned,
400 }),
401 }
402 }
403}
404
405unsafe impl linked_list::Link for Waiter {
406 type Handle = NonNull<Waiter>;
407 type Target = Waiter;
408
409 fn as_raw(handle: &NonNull<Waiter>) -> NonNull<Waiter> {
410 *handle
411 }
412
413 unsafe fn from_raw(ptr: NonNull<Waiter>) -> NonNull<Waiter> {
414 ptr
415 }
416
417 unsafe fn pointers(target: NonNull<Waiter>) -> NonNull<linked_list::Pointers<Waiter>> {
418 unsafe { Waiter::addr_of_pointers(target) }
419 }
420}
421
422// ===== impl Readiness =====
423
424impl Future for Readiness<'_> {
425 type Output = ReadyEvent;
426
427 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
428 use std::sync::atomic::Ordering::SeqCst;
429
430 let (scheduled_io, state, waiter) = {
431 // Safety: `Self` is `!Unpin`
432 //
433 // While we could use `pin_project!` to remove
434 // this unsafe block, there are already unsafe blocks here,
435 // so it wouldn't significantly ease the mental burden
436 // and would actually complicate the code.
437 // That's why we didn't use it.
438 let me = unsafe { self.get_unchecked_mut() };
439 (me.scheduled_io, &mut me.state, &me.waiter)
440 };
441
442 loop {
443 match *state {
444 State::Init => {
445 // Optimistically check existing readiness
446 let curr = scheduled_io.readiness.load(SeqCst);
447 let is_shutdown = SHUTDOWN.unpack(curr) != 0;
448
449 // Safety: `waiter.interest` never changes
450 let interest = unsafe { (*waiter.get()).interest };
451 let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(interest);
452
453 if !ready.is_empty() || is_shutdown {
454 // Currently ready!
455 let tick = TICK.unpack(curr) as u8;
456 *state = State::Done;
457 return Poll::Ready(ReadyEvent {
458 tick,
459 ready,
460 is_shutdown,
461 });
462 }
463
464 // Wasn't ready, take the lock (and check again while locked).
465 let mut waiters = scheduled_io.waiters.lock();
466
467 let curr = scheduled_io.readiness.load(SeqCst);
468 let mut ready = Ready::from_usize(READINESS.unpack(curr));
469 let is_shutdown = SHUTDOWN.unpack(curr) != 0;
470
471 if is_shutdown {
472 ready = Ready::ALL;
473 }
474
475 let ready = ready.intersection(interest);
476
477 if !ready.is_empty() || is_shutdown {
478 // Currently ready!
479 let tick = TICK.unpack(curr) as u8;
480 *state = State::Done;
481 return Poll::Ready(ReadyEvent {
482 tick,
483 ready,
484 is_shutdown,
485 });
486 }
487
488 // Not ready even after locked, insert into list...
489
490 // Safety: Since the `waiter` is not in the intrusive list yet,
491 // we have exclusive access to it. The Mutex ensures
492 // that this modification is visible to other threads that
493 // acquire the same Mutex.
494 let waker = unsafe { &mut (*waiter.get()).waker };
495 let old = waker.replace(cx.waker().clone());
496 debug_assert!(old.is_none(), "waker should be None at the first poll");
497
498 // Insert the waiter into the linked list
499 //
500 // safety: pointers from `UnsafeCell` are never null.
501 waiters
502 .list
503 .push_front(unsafe { NonNull::new_unchecked(waiter.get()) });
504 *state = State::Waiting;
505 }
506 State::Waiting => {
507 // Currently in the "Waiting" state, implying the caller has
508 // a waiter stored in the waiter list (guarded by
509 // `notify.waiters`). In order to access the waker fields,
510 // we must hold the lock.
511
512 let waiters = scheduled_io.waiters.lock();
513
514 // Safety: With the lock held, we have exclusive access to
515 // the waiter. In other words, `ScheduledIo::wake()`
516 // cannot access the waiter concurrently.
517 let w = unsafe { &mut *waiter.get() };
518
519 if w.is_ready {
520 // Our waker has been notified.
521 *state = State::Done;
522 } else {
523 // Update the waker, if necessary.
524 w.waker.as_mut().unwrap().clone_from(cx.waker());
525 return Poll::Pending;
526 }
527
528 // Explicit drop of the lock to indicate the scope that the
529 // lock is held. Because holding the lock is required to
530 // ensure safe access to fields not held within the lock, it
531 // is helpful to visualize the scope of the critical
532 // section.
533 drop(waiters);
534 }
535 State::Done => {
536 let curr = scheduled_io.readiness.load(Acquire);
537 let is_shutdown = SHUTDOWN.unpack(curr) != 0;
538
539 // The returned tick might be newer than the event
540 // which notified our waker. This is ok because the future
541 // still didn't return `Poll::Ready`.
542 let tick = TICK.unpack(curr) as u8;
543
544 // Safety: We don't need to acquire the lock here because
545 // 1. `State::Done`` means `waiter` is no longer shared,
546 // this means no concurrent access to `waiter` can happen
547 // at this point.
548 // 2. `waiter.interest` is never changed, this means
549 // no side effects need to be synchronized by the lock.
550 let interest = unsafe { (*waiter.get()).interest };
551 // The readiness state could have been cleared in the meantime,
552 // but we allow the returned ready set to be empty.
553 let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(interest);
554
555 return Poll::Ready(ReadyEvent {
556 tick,
557 ready,
558 is_shutdown,
559 });
560 }
561 }
562 }
563 }
564}
565
566impl Drop for Readiness<'_> {
567 fn drop(&mut self) {
568 let mut waiters = self.scheduled_io.waiters.lock();
569
570 // Safety: `waiter` is only ever stored in `waiters`
571 unsafe {
572 waiters
573 .list
574 .remove(NonNull::new_unchecked(self.waiter.get()))
575 };
576 }
577}
578
579unsafe impl Send for Readiness<'_> {}
580unsafe impl Sync for Readiness<'_> {}