Skip to main content

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    /// Marks the resource ready in the given directions without an event from
188    /// the driver. Readiness may have false positives; an operation that finds
189    /// the resource not ready clears it as usual. Leaves the tick alone and
190    /// does nothing after shutdown.
191    pub(super) fn assume_ready(&self, ready: Ready) {
192        let _ = self.readiness.fetch_update(AcqRel, Acquire, |curr| {
193            (SHUTDOWN.unpack(curr) == 0)
194                .then(|| READINESS.pack(READINESS.unpack(curr) | ready.as_usize(), curr))
195        });
196    }
197
198    pub(crate) fn token(&self) -> mio::Token {
199        mio::Token(super::EXPOSE_IO.expose_provenance(self))
200    }
201
202    /// Invoked when the IO driver is shut down; forces this `ScheduledIo` into a
203    /// permanently shutdown state.
204    pub(super) fn shutdown(&self) {
205        let mask = SHUTDOWN.pack(1, 0);
206        self.readiness.fetch_or(mask, AcqRel);
207        self.wake(Ready::ALL);
208    }
209
210    /// Sets the readiness on this `ScheduledIo` by invoking the given closure on
211    /// the current value, returning the previous readiness value.
212    ///
213    /// # Arguments
214    /// - `tick_op`: whether setting the tick or trying to clear readiness for a
215    ///   specific tick.
216    /// - `f`: a closure returning a new readiness value given the previous
217    ///   readiness.
218    pub(super) fn set_readiness(&self, tick_op: Tick, f: impl Fn(Ready) -> Ready) {
219        let _ = self.readiness.fetch_update(AcqRel, Acquire, |curr| {
220            // If the io driver is shut down, then you are only allowed to clear readiness.
221            debug_assert!(SHUTDOWN.unpack(curr) == 0 || matches!(tick_op, Tick::Clear(_)));
222
223            const MAX_TICK: usize = TICK.max_value() + 1;
224            let tick = TICK.unpack(curr);
225
226            let new_tick = match tick_op {
227                // Trying to clear readiness with an old event!
228                Tick::Clear(t) if tick != usize::from(t) => return None,
229                Tick::Clear(t) => usize::from(t),
230                Tick::Set => tick.wrapping_add(1) % MAX_TICK,
231            };
232            let ready = Ready::from_usize(READINESS.unpack(curr));
233            // Keep the shutdown bit, so that a clear after shutdown (a
234            // `WouldBlock` observed once the driver is gone) does not turn the
235            // next wait into a wait for an event that will never come.
236            let next = TICK.pack(new_tick, f(ready).as_usize());
237            Some(SHUTDOWN.pack(SHUTDOWN.unpack(curr), next))
238        });
239    }
240
241    /// Notifies all pending waiters that have registered interest in `ready`.
242    ///
243    /// There may be many waiters to notify. Waking the pending task **must** be
244    /// done from outside of the lock otherwise there is a potential for a
245    /// deadlock.
246    ///
247    /// A stack array of wakers is created and filled with wakers to notify, the
248    /// lock is released, and the wakers are notified. Because there may be more
249    /// than 32 wakers to notify, if the stack array fills up, the lock is
250    /// released, the array is cleared, and the iteration continues.
251    pub(super) fn wake(&self, ready: Ready) {
252        let mut wakers = WakeList::new();
253
254        let mut waiters = self.waiters.lock();
255
256        // check for AsyncRead slot
257        if ready.is_readable() {
258            if let Some(waker) = waiters.reader.take() {
259                wakers.push(waker);
260            }
261        }
262
263        // check for AsyncWrite slot
264        if ready.is_writable() {
265            if let Some(waker) = waiters.writer.take() {
266                wakers.push(waker);
267            }
268        }
269
270        'outer: loop {
271            let mut iter = waiters.list.drain_filter(|w| ready.satisfies(w.interest));
272
273            while wakers.can_push() {
274                match iter.next() {
275                    Some(waiter) => {
276                        let waiter = unsafe { &mut *waiter.as_ptr() };
277
278                        if let Some(waker) = waiter.waker.take() {
279                            waiter.is_ready = true;
280                            wakers.push(waker);
281                        }
282                    }
283                    None => {
284                        break 'outer;
285                    }
286                }
287            }
288
289            drop(waiters);
290
291            wakers.wake_all();
292
293            // Acquire the lock again.
294            waiters = self.waiters.lock();
295        }
296
297        // Release the lock before notifying
298        drop(waiters);
299
300        wakers.wake_all();
301    }
302
303    pub(super) fn ready_event(&self, interest: Interest) -> ReadyEvent {
304        let curr = self.readiness.load(Acquire);
305
306        ReadyEvent {
307            tick: TICK.unpack(curr) as u16,
308            ready: interest.mask() & Ready::from_usize(READINESS.unpack(curr)),
309            is_shutdown: SHUTDOWN.unpack(curr) != 0,
310        }
311    }
312
313    /// Polls for readiness events in a given direction.
314    ///
315    /// These are to support `AsyncRead` and `AsyncWrite` polling methods,
316    /// which cannot use the `async fn` version. This uses reserved reader
317    /// and writer slots.
318    pub(super) fn poll_readiness(
319        &self,
320        cx: &mut Context<'_>,
321        direction: Direction,
322    ) -> Poll<ReadyEvent> {
323        let curr = self.readiness.load(Acquire);
324
325        let ready = direction.mask() & Ready::from_usize(READINESS.unpack(curr));
326        let is_shutdown = SHUTDOWN.unpack(curr) != 0;
327
328        if ready.is_empty() && !is_shutdown {
329            // Update the task info
330            let mut waiters = self.waiters.lock();
331            let waker = match direction {
332                Direction::Read => &mut waiters.reader,
333                Direction::Write => &mut waiters.writer,
334            };
335
336            // Avoid cloning the waker if one is already stored that matches the
337            // current task.
338            match waker {
339                Some(waker) => waker.clone_from(cx.waker()),
340                None => *waker = Some(cx.waker().clone()),
341            }
342
343            // Try again, in case the readiness was changed while we were
344            // taking the waiters lock
345            let curr = self.readiness.load(Acquire);
346            let ready = direction.mask() & Ready::from_usize(READINESS.unpack(curr));
347            let is_shutdown = SHUTDOWN.unpack(curr) != 0;
348            if is_shutdown {
349                Poll::Ready(ReadyEvent {
350                    tick: TICK.unpack(curr) as u16,
351                    ready: direction.mask(),
352                    is_shutdown,
353                })
354            } else if ready.is_empty() {
355                Poll::Pending
356            } else {
357                Poll::Ready(ReadyEvent {
358                    tick: TICK.unpack(curr) as u16,
359                    ready,
360                    is_shutdown,
361                })
362            }
363        } else {
364            Poll::Ready(ReadyEvent {
365                tick: TICK.unpack(curr) as u16,
366                ready,
367                is_shutdown,
368            })
369        }
370    }
371
372    pub(crate) fn clear_readiness(&self, event: ReadyEvent) {
373        // This consumes the current readiness state **except** for closed
374        // states. Closed states are excluded because they are final states.
375        let mask_no_closed = event.ready - Ready::READ_CLOSED - Ready::WRITE_CLOSED;
376        self.set_readiness(Tick::Clear(event.tick), |curr| curr - mask_no_closed);
377    }
378
379    pub(crate) fn clear_wakers(&self) {
380        let mut waiters = self.waiters.lock();
381        waiters.reader.take();
382        waiters.writer.take();
383    }
384}
385
386impl Drop for ScheduledIo {
387    fn drop(&mut self) {
388        self.wake(Ready::ALL);
389    }
390}
391
392unsafe impl Send for ScheduledIo {}
393unsafe impl Sync for ScheduledIo {}
394
395impl ScheduledIo {
396    /// An async version of `poll_readiness` which uses a linked list of wakers.
397    pub(crate) async fn readiness(&self, interest: Interest) -> ReadyEvent {
398        self.readiness_fut(interest).await
399    }
400
401    // This is in a separate function so that the borrow checker doesn't think
402    // we are borrowing the `UnsafeCell` possibly over await boundaries.
403    //
404    // Go figure.
405    fn readiness_fut(&self, interest: Interest) -> Readiness<'_> {
406        Readiness {
407            scheduled_io: self,
408            state: State::Init,
409            waiter: UnsafeCell::new(Waiter {
410                pointers: linked_list::Pointers::new(),
411                waker: None,
412                is_ready: false,
413                interest,
414                _p: PhantomPinned,
415            }),
416        }
417    }
418}
419
420unsafe impl linked_list::Link for Waiter {
421    type Handle = NonNull<Waiter>;
422    type Target = Waiter;
423
424    fn as_raw(handle: &NonNull<Waiter>) -> NonNull<Waiter> {
425        *handle
426    }
427
428    unsafe fn from_raw(ptr: NonNull<Waiter>) -> NonNull<Waiter> {
429        ptr
430    }
431
432    unsafe fn pointers(target: NonNull<Waiter>) -> NonNull<linked_list::Pointers<Waiter>> {
433        unsafe { Waiter::addr_of_pointers(target) }
434    }
435}
436
437// ===== impl Readiness =====
438
439impl Future for Readiness<'_> {
440    type Output = ReadyEvent;
441
442    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
443        use std::sync::atomic::Ordering::SeqCst;
444
445        let (scheduled_io, state, waiter) = {
446            // Safety: `Self` is `!Unpin`
447            //
448            // While we could use `pin_project!` to remove
449            // this unsafe block, there are already unsafe blocks here,
450            // so it wouldn't significantly ease the mental burden
451            // and would actually complicate the code.
452            // That's why we didn't use it.
453            let me = unsafe { self.get_unchecked_mut() };
454            (me.scheduled_io, &mut me.state, &me.waiter)
455        };
456
457        loop {
458            match *state {
459                State::Init => {
460                    // Optimistically check existing readiness
461                    let curr = scheduled_io.readiness.load(SeqCst);
462                    let is_shutdown = SHUTDOWN.unpack(curr) != 0;
463
464                    // Safety: `waiter.interest` never changes
465                    let interest = unsafe { (*waiter.get()).interest };
466                    let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(interest);
467
468                    if !ready.is_empty() || is_shutdown {
469                        // Currently ready!
470                        let tick = TICK.unpack(curr) as u16;
471                        *state = State::Done;
472                        return Poll::Ready(ReadyEvent {
473                            tick,
474                            ready,
475                            is_shutdown,
476                        });
477                    }
478
479                    // Wasn't ready, take the lock (and check again while locked).
480                    let mut waiters = scheduled_io.waiters.lock();
481
482                    let curr = scheduled_io.readiness.load(SeqCst);
483                    let mut ready = Ready::from_usize(READINESS.unpack(curr));
484                    let is_shutdown = SHUTDOWN.unpack(curr) != 0;
485
486                    if is_shutdown {
487                        ready = Ready::ALL;
488                    }
489
490                    let ready = ready.intersection(interest);
491
492                    if !ready.is_empty() || is_shutdown {
493                        // Currently ready!
494                        let tick = TICK.unpack(curr) as u16;
495                        *state = State::Done;
496                        return Poll::Ready(ReadyEvent {
497                            tick,
498                            ready,
499                            is_shutdown,
500                        });
501                    }
502
503                    // Not ready even after locked, insert into list...
504
505                    // Safety: Since the `waiter` is not in the intrusive list yet,
506                    // we have exclusive access to it. The Mutex ensures
507                    // that this modification is visible to other threads that
508                    // acquire the same Mutex.
509                    let waker = unsafe { &mut (*waiter.get()).waker };
510                    let old = waker.replace(cx.waker().clone());
511                    debug_assert!(old.is_none(), "waker should be None at the first poll");
512
513                    // Insert the waiter into the linked list
514                    //
515                    // safety: pointers from `UnsafeCell` are never null.
516                    waiters
517                        .list
518                        .push_front(unsafe { NonNull::new_unchecked(waiter.get()) });
519                    *state = State::Waiting;
520                }
521                State::Waiting => {
522                    // Currently in the "Waiting" state, implying the caller has
523                    // a waiter stored in the waiter list (guarded by
524                    // `notify.waiters`). In order to access the waker fields,
525                    // we must hold the lock.
526
527                    let waiters = scheduled_io.waiters.lock();
528
529                    // Safety: With the lock held, we have exclusive access to
530                    // the waiter. In other words, `ScheduledIo::wake()`
531                    // cannot access the waiter concurrently.
532                    let w = unsafe { &mut *waiter.get() };
533
534                    if w.is_ready {
535                        // Our waker has been notified.
536                        *state = State::Done;
537                    } else {
538                        // Update the waker, if necessary.
539                        w.waker.as_mut().unwrap().clone_from(cx.waker());
540                        return Poll::Pending;
541                    }
542
543                    // Explicit drop of the lock to indicate the scope that the
544                    // lock is held. Because holding the lock is required to
545                    // ensure safe access to fields not held within the lock, it
546                    // is helpful to visualize the scope of the critical
547                    // section.
548                    drop(waiters);
549                }
550                State::Done => {
551                    let curr = scheduled_io.readiness.load(Acquire);
552                    let is_shutdown = SHUTDOWN.unpack(curr) != 0;
553
554                    // The returned tick might be newer than the event
555                    // which notified our waker. This is ok because the future
556                    // still didn't return `Poll::Ready`.
557                    let tick = TICK.unpack(curr) as u16;
558
559                    // Safety: We don't need to acquire the lock here because
560                    //   1. `State::Done`` means `waiter` is no longer shared,
561                    //      this means no concurrent access to `waiter` can happen
562                    //      at this point.
563                    //   2. `waiter.interest` is never changed, this means
564                    //      no side effects need to be synchronized by the lock.
565                    let interest = unsafe { (*waiter.get()).interest };
566                    // The readiness state could have been cleared in the meantime,
567                    // but we allow the returned ready set to be empty.
568                    let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(interest);
569
570                    return Poll::Ready(ReadyEvent {
571                        tick,
572                        ready,
573                        is_shutdown,
574                    });
575                }
576            }
577        }
578    }
579}
580
581impl Drop for Readiness<'_> {
582    fn drop(&mut self) {
583        let mut waiters = self.scheduled_io.waiters.lock();
584
585        // Safety: `waiter` is only ever stored in `waiters`
586        unsafe {
587            waiters
588                .list
589                .remove(NonNull::new_unchecked(self.waiter.get()))
590        };
591    }
592}
593
594unsafe impl Send for Readiness<'_> {}
595unsafe impl Sync for Readiness<'_> {}
596
597#[cfg(all(test, not(loom)))]
598mod tests {
599    use super::*;
600
601    #[test]
602    fn stale_event_does_not_clear_readiness_after_u8_wraparound() {
603        let io = ScheduledIo::default();
604        io.set_readiness(Tick::Set, |curr| curr | Ready::READABLE);
605        let event = io.ready_event(Interest::READABLE);
606
607        for _ in 0..=u8::MAX {
608            io.set_readiness(Tick::Set, |curr| curr | Ready::READABLE);
609        }
610
611        io.clear_readiness(event);
612
613        assert!(io.ready_event(Interest::READABLE).ready.is_readable());
614    }
615}
616
617#[cfg(all(test, loom))]
618mod loom_tests {
619    use super::*;
620    use loom::future::block_on;
621    use loom::sync::Arc;
622    use loom::thread;
623
624    #[test]
625    fn shutdown_wakes_readiness() {
626        loom::model(|| {
627            let io = Arc::new(ScheduledIo::default());
628            let shutdown_io = io.clone();
629            let shutdown = thread::spawn(move || shutdown_io.shutdown());
630
631            // Joining before polling would hide a lost wakeup during registration.
632            let event = block_on(io.readiness(Interest::READABLE));
633            assert!(event.is_shutdown);
634
635            shutdown.join().unwrap();
636        });
637    }
638
639    #[test]
640    fn shutdown_wakes_poll_readiness() {
641        for direction in [Direction::Read, Direction::Write] {
642            loom::model(move || {
643                let io = Arc::new(ScheduledIo::default());
644                let shutdown_io = io.clone();
645                let shutdown = thread::spawn(move || shutdown_io.shutdown());
646
647                let event = block_on(std::future::poll_fn(|cx| io.poll_readiness(cx, direction)));
648                assert!(event.is_shutdown);
649
650                shutdown.join().unwrap();
651            });
652        }
653    }
654
655    /// Tests that delivering an event via raw pointer correctly wakes a pending task waiting
656    /// on readiness via `poll_readiness`.
657    #[test]
658    fn event_delivery_wakes_waiter() {
659        loom::model(|| {
660            let io = Arc::new(ScheduledIo::default());
661            let token = io.token();
662
663            let waiter_io = io.clone();
664            let waiter = thread::spawn(move || {
665                let event = block_on(std::future::poll_fn(|cx| {
666                    waiter_io.poll_readiness(cx, Direction::Read)
667                }));
668                assert!(event.ready.is_readable());
669            });
670
671            // Driver delivers event and wakes the waiter.
672            //
673            // SAFETY: `io` remains alive for the duration of this model.
674            unsafe {
675                super::super::driver::dispatch_event(token, Ready::READABLE);
676            }
677
678            waiter.join().unwrap();
679        });
680    }
681}