1
//! Support for asynchronous packet transmission.
2
//!
3
//! See [`Capture::sink`](super::Capture::sink).
4
use std::io;
5
use std::marker::Unpin;
6
use std::pin::Pin;
7
use std::task::{self, Poll};
8

            
9
use futures::{Sink, ready};
10

            
11
#[cfg(target_os = "linux")]
12
use {crate::capture::selectable::SelectableCapture, tokio::io::unix::AsyncFd};
13

            
14
#[cfg(not(target_os = "linux"))]
15
use tokio::task::coop;
16

            
17
use crate::{
18
    Error,
19
    capture::{Active, Capture},
20
};
21

            
22
impl Capture<Active> {
23
    /// Returns this capture as a [`futures::Sink`] for sending packets.
24
    ///
25
    /// ```no_run
26
    /// # use futures::SinkExt;
27
    /// # use pcap::{Active, Capture};
28
    /// # async fn doc(capture: Capture<Active>) -> Result<(), pcap::Error> {
29
    /// let mut sink = capture.sink()?;
30
    /// sink.send(vec![0u8; 64]).await?;
31
    /// # Ok(())
32
    /// # }
33
    /// ```
34
    ///
35
    /// # Errors
36
    ///
37
    /// If this capture is set to be blocking, an error will be returned. On Linux, where the sink
38
    /// waits for the interface, an error is also returned if the network device does not support
39
    /// `select()`.
40
4
    pub fn sink<C: AsRef<[u8]>>(self) -> Result<PacketSink<C>, Error> {
41
4
        if !self.is_nonblock() {
42
2
            return Err(Error::NonNonBlock);
43
2
        }
44
2
        PacketSink::new(self)
45
4
    }
46
}
47

            
48
/// Implement Sink for async use of pcap
49
///
50
/// The packet given to `start_send` is held until a later poll can send it, so it is sent without
51
/// being copied. Only one packet is held at a time. A packet that fails to send is dropped rather
52
/// than retried, as libpcap does not tell us how much of it made it onto the wire.
53
///
54
/// Closing the sink flushes it but does not close the capture, which happens when the
55
/// [`PacketSink`] is dropped. Once closed, the sink takes no more packets and reports
56
/// `Error::IoError` with a kind of `BrokenPipe` instead.
57
///
58
/// # Warning
59
///
60
/// Only on Linux does the capture report when the interface is ready for another packet.
61
/// Elsewhere the packet is sent from within the poll, and a full transmit queue comes back as an
62
/// error instead of pausing the sink until there is room.
63
pub struct PacketSink<C> {
64
    #[cfg(target_os = "linux")]
65
    inner: AsyncFd<SelectableCapture<Active>>,
66
    #[cfg(not(target_os = "linux"))]
67
    capture: Capture<Active>,
68
    #[cfg(not(target_os = "linux"))]
69
    sent_since_yield: u32,
70
    packet: Option<C>,
71
    closed: bool,
72
}
73

            
74
#[cfg(target_os = "linux")]
75
impl<C> PacketSink<C> {
76
10
    pub(crate) fn new(capture: Capture<Active>) -> Result<Self, Error> {
77
10
        let capture = SelectableCapture::new(capture)?;
78
        Ok(PacketSink {
79
10
            inner: AsyncFd::with_interest(capture, tokio::io::Interest::WRITABLE)?,
80
10
            packet: None,
81
            closed: false,
82
        })
83
10
    }
84

            
85
    /// Returns a mutable reference to the inner [`Capture`].
86
    ///
87
    /// The caller must ensure the capture will not be set to be blocking.
88
2
    pub fn capture_mut(&mut self) -> &mut Capture<Active> {
89
2
        self.inner.get_mut().get_inner_mut()
90
2
    }
91

            
92
20
    fn poll_send(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>>
93
20
    where
94
20
        C: AsRef<[u8]>,
95
    {
96
20
        let Self { inner, packet, .. } = self;
97

            
98
        loop {
99
22
            let buf = match &*packet {
100
14
                Some(buf) => buf.as_ref(),
101
8
                None => return Poll::Ready(Ok(())),
102
            };
103

            
104
14
            let mut guard = ready!(inner.poll_write_ready_mut(cx))?;
105
            // A busy device goes through the io::Result, so that try_io knows to wait for the
106
            // next readiness event. A real error goes through the inner Result untouched.
107
8
            let result = guard.try_io(|inner| {
108
8
                match inner.get_mut().get_inner_mut().sendpacket_nonblock(buf) {
109
4
                    Ok(()) => Ok(Ok(())),
110
2
                    Err(e @ Error::IoError(io::ErrorKind::WouldBlock)) => {
111
2
                        Err(io::Error::new(io::ErrorKind::WouldBlock, e))
112
                    }
113
2
                    Err(e) => Ok(Err(e)),
114
                }
115
8
            });
116

            
117
8
            match result {
118
6
                Ok(result) => {
119
6
                    *packet = None;
120
6
                    return Poll::Ready(result?);
121
                }
122
2
                Err(_would_block) => continue,
123
            }
124
        }
125
20
    }
126
}
127

            
128
/// How many packets the sink sends before it yields to the executor.
129
///
130
/// This count and tokio's task budget are redundant rather than complementary: either alone is
131
/// enough to make the sink yield, and whichever runs out first is the one that does. The value
132
/// is the budget tokio gives a task, so a sink driven on tokio yields at much the same points
133
/// it otherwise would, but correctness does not depend on the two agreeing. Tokio keeps its own
134
/// number private and is free to change it; the smaller of the two then takes effect.
135
#[cfg(not(target_os = "linux"))]
136
const SENDS_BETWEEN_YIELDS: u32 = 128;
137

            
138
#[cfg(not(target_os = "linux"))]
139
impl<C> PacketSink<C> {
140
    pub(crate) fn new(capture: Capture<Active>) -> Result<Self, Error> {
141
        Ok(PacketSink {
142
            capture,
143
            sent_since_yield: 0,
144
            packet: None,
145
            closed: false,
146
        })
147
    }
148

            
149
    /// Returns a mutable reference to the inner [`Capture`].
150
    ///
151
    /// The caller must ensure the capture will not be set to be blocking.
152
    pub fn capture_mut(&mut self) -> &mut Capture<Active> {
153
        &mut self.capture
154
    }
155

            
156
    fn poll_send(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>>
157
    where
158
        C: AsRef<[u8]>,
159
    {
160
        let buf = match &self.packet {
161
            Some(packet) => packet.as_ref(),
162
            None => return Poll::Ready(Ok(())),
163
        };
164

            
165
        // Sending here never waits for the interface, so a sink that is kept fed would never
166
        // return Pending and the task it runs in would never let the executor poll anything
167
        // else. Two things stop that. The count applies to whoever is driving the sink, which off
168
        // Linux can be any executor, as it holds nothing of tokio's. Spending the task's budget
169
        // as well holds a task that also does tokio I/O to one budget between yields rather
170
        // than one for each source.
171
        if self.sent_since_yield == SENDS_BETWEEN_YIELDS {
172
            self.sent_since_yield = 0;
173
            cx.waker().wake_by_ref();
174
            return Poll::Pending;
175
        }
176
        let coop = ready!(coop::poll_proceed(cx));
177
        self.sent_since_yield += 1;
178

            
179
        let result = self.capture.sendpacket(buf);
180
        coop.made_progress();
181
        self.packet = None;
182
        Poll::Ready(result)
183
    }
184
}
185

            
186
impl<C> Unpin for PacketSink<C> {}
187

            
188
impl<C: AsRef<[u8]>> Sink<C> for PacketSink<C> {
189
    type Error = Error;
190

            
191
6
    fn poll_ready(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>> {
192
6
        let sink = Pin::into_inner(self);
193
6
        if sink.closed {
194
2
            return Poll::Ready(Err(Error::IoError(io::ErrorKind::BrokenPipe)));
195
4
        }
196
4
        sink.poll_send(cx)
197
6
    }
198

            
199
8
    fn start_send(self: Pin<&mut Self>, item: C) -> Result<(), Error> {
200
8
        let sink = Pin::into_inner(self);
201
8
        if sink.closed {
202
            return Err(Error::IoError(io::ErrorKind::BrokenPipe));
203
8
        }
204
8
        if sink.packet.is_some() {
205
            // poll_ready did not report the sink ready, so the packet it was given last is
206
            // still waiting. Keep it: dropping it here would lose a packet the caller has
207
            // already been told the sink took.
208
2
            return Err(Error::IoError(io::ErrorKind::WouldBlock));
209
6
        }
210
6
        sink.packet = Some(item);
211
6
        Ok(())
212
8
    }
213

            
214
10
    fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>> {
215
10
        Pin::into_inner(self).poll_send(cx)
216
10
    }
217

            
218
4
    fn poll_close(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>> {
219
4
        let sink = Pin::into_inner(self);
220
4
        ready!(sink.poll_send(cx))?;
221
4
        sink.closed = true;
222
4
        Poll::Ready(Ok(()))
223
4
    }
224
}
225

            
226
#[cfg(test)]
227
mod tests {
228
    use futures::SinkExt;
229

            
230
    use crate::{
231
        capture::testmod::test_capture,
232
        raw::{
233
            mock_ffi::*,
234
            testmod::{RAWMTX, as_pcap_t, geterr_expect},
235
        },
236
    };
237

            
238
    use super::*;
239

            
240
    #[test]
241
    fn test_sink_error() {
242
        let _m = RAWMTX.lock();
243

            
244
        let mut dummy: isize = 777;
245
        let pcap = as_pcap_t(&mut dummy);
246

            
247
        let test_capture = test_capture::<Active>(pcap);
248
        let capture = test_capture.capture;
249
        assert!(!capture.is_nonblock());
250

            
251
        let result = capture.sink::<Vec<u8>>();
252
        assert!(result.is_err());
253
    }
254

            
255
    #[cfg(target_os = "linux")]
256
    mod linux {
257
        use std::os::unix::io::RawFd;
258

            
259
        use crate::raw;
260

            
261
        use super::*;
262

            
263
        // A real file descriptor to stand in for the one libpcap would hand out. AsyncFd registers
264
        // it for real, so the sink takes the same path it would with a live capture.
265
        struct FdPair([RawFd; 2]);
266

            
267
        impl FdPair {
268
            fn new() -> Self {
269
                let mut fds: [RawFd; 2] = [-1, -1];
270
                let rc = unsafe {
271
                    libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr())
272
                };
273
                assert_eq!(rc, 0, "Unable to create a socketpair");
274
                Self(fds)
275
            }
276
        }
277

            
278
        impl Drop for FdPair {
279
            fn drop(&mut self) {
280
                for fd in self.0 {
281
                    unsafe { libc::close(fd) };
282
                }
283
            }
284
        }
285

            
286
        // The caller holds on to the TestCapture, which owns the pcap_close expectation that fires
287
        // when the sink is dropped.
288
        fn test_sink(
289
            pcap: *mut raw::pcap_t,
290
            capture: Capture<Active>,
291
            fd: RawFd,
292
        ) -> PacketSink<Vec<u8>> {
293
            let ctx = raw::pcap_get_selectable_fd_context();
294
            ctx.expect()
295
                .withf_st(move |arg1| *arg1 == pcap)
296
                .return_once(move |_| fd);
297

            
298
            PacketSink::new(capture).unwrap()
299
        }
300

            
301
        #[tokio::test]
302
        async fn test_sink_ok() {
303
            let _m = RAWMTX.lock();
304

            
305
            let mut dummy: isize = 777;
306
            let pcap = as_pcap_t(&mut dummy);
307
            let fds = FdPair::new();
308
            let fd = fds.0[0];
309

            
310
            let test_capture = test_capture::<Active>(pcap);
311

            
312
            let ctx = raw::pcap_setnonblock_context();
313
            ctx.expect()
314
                .withf_st(move |arg1, arg2, _| (*arg1 == pcap) && (*arg2 == 1))
315
                .return_once(|_, _, _| 0);
316

            
317
            let capture = test_capture.capture.setnonblock().unwrap();
318

            
319
            let ctx = raw::pcap_get_selectable_fd_context();
320
            ctx.expect()
321
                .withf_st(move |arg1| *arg1 == pcap)
322
                .return_once(move |_| fd);
323

            
324
            let mut sink = capture.sink::<Vec<u8>>().unwrap();
325
            assert!(sink.capture_mut().is_nonblock());
326

            
327
            // Closing flushes the sink. The capture stays open until the sink is dropped.
328
            sink.close().await.unwrap();
329
        }
330

            
331
        #[tokio::test]
332
        async fn test_sink_sends() {
333
            let _m = RAWMTX.lock();
334

            
335
            let mut dummy: isize = 777;
336
            let pcap = as_pcap_t(&mut dummy);
337
            let fds = FdPair::new();
338

            
339
            let test_capture = test_capture::<Active>(pcap);
340
            let mut sink = test_sink(pcap, test_capture.capture, fds.0[0]);
341

            
342
            let ctx = pcap_sendpacket_context();
343
            ctx.expect()
344
                .withf_st(move |arg1, _, arg3| (*arg1 == pcap) && (*arg3 == 4))
345
                .return_once(|_, _, _| 0);
346

            
347
            sink.send(vec![1, 2, 3, 4]).await.unwrap();
348
            assert!(sink.packet.is_none());
349

            
350
            let ctx = pcap_sendpacket_context();
351
            ctx.checkpoint();
352
            ctx.expect()
353
                .withf_st(move |arg1, _, _| *arg1 == pcap)
354
                .return_once(|_, _, _| {
355
                    errno::set_errno(errno::Errno(libc::EINVAL));
356
                    -1
357
                });
358

            
359
            let _err = geterr_expect(pcap);
360

            
361
            let result = sink.send(vec![1, 2, 3, 4]).await;
362
            assert!(matches!(result, Err(Error::PcapError(_))));
363

            
364
            // The failed packet is dropped, so the sink can be used again.
365
            assert!(sink.packet.is_none());
366
        }
367

            
368
        #[tokio::test]
369
        async fn test_sink_backpressure() {
370
            let _m = RAWMTX.lock();
371

            
372
            let mut dummy: isize = 777;
373
            let pcap = as_pcap_t(&mut dummy);
374
            let fds = FdPair::new();
375

            
376
            let test_capture = test_capture::<Active>(pcap);
377
            let mut sink = test_sink(pcap, test_capture.capture, fds.0[0]);
378

            
379
            let ctx = pcap_sendpacket_context();
380
            ctx.expect()
381
                .withf_st(move |arg1, _, _| *arg1 == pcap)
382
                .returning(|_, _, _| {
383
                    errno::set_errno(errno::Errno(libc::EAGAIN));
384
                    -1
385
                });
386

            
387
            // Wait until the socketpair is reported writable, so the poll below gets past the
388
            // readiness check and asks libpcap to send. The guard is dropped without clearing,
389
            // which leaves the readiness in place.
390
            drop(sink.inner.writable().await.unwrap());
391

            
392
            sink.packet = Some(vec![1, 2, 3]);
393

            
394
            // A busy device is not an error. The sink waits and the packet stays queued.
395
            let poll = futures::future::poll_fn(|cx| Poll::Ready(sink.poll_send(cx))).await;
396
            assert!(poll.is_pending());
397
            assert_eq!(sink.packet, Some(vec![1, 2, 3]));
398
        }
399

            
400
        #[tokio::test]
401
        async fn test_sink_closed() {
402
            let _m = RAWMTX.lock();
403

            
404
            let mut dummy: isize = 777;
405
            let pcap = as_pcap_t(&mut dummy);
406
            let fds = FdPair::new();
407

            
408
            let test_capture = test_capture::<Active>(pcap);
409
            let mut sink = test_sink(pcap, test_capture.capture, fds.0[0]);
410

            
411
            sink.close().await.unwrap();
412

            
413
            // There is no pcap_sendpacket expectation, so a send here would fail the test.
414
            assert_eq!(
415
                sink.send(vec![1, 2, 3, 4]).await,
416
                Err(Error::IoError(io::ErrorKind::BrokenPipe))
417
            );
418
            assert!(sink.packet.is_none());
419
        }
420

            
421
        #[tokio::test]
422
        async fn test_sink_start_send_twice() {
423
            let _m = RAWMTX.lock();
424

            
425
            let mut dummy: isize = 777;
426
            let pcap = as_pcap_t(&mut dummy);
427
            let fds = FdPair::new();
428

            
429
            let test_capture = test_capture::<Active>(pcap);
430
            let mut sink = test_sink(pcap, test_capture.capture, fds.0[0]);
431

            
432
            Pin::new(&mut sink).start_send(vec![1, 2, 3, 4]).unwrap();
433

            
434
            // Nothing has sent the first packet yet, so the second one is refused rather than
435
            // put in its place.
436
            assert_eq!(
437
                Pin::new(&mut sink).start_send(vec![5, 6, 7]),
438
                Err(Error::IoError(io::ErrorKind::WouldBlock))
439
            );
440
            assert_eq!(sink.packet, Some(vec![1, 2, 3, 4]));
441

            
442
            let ctx = pcap_sendpacket_context();
443
            ctx.expect()
444
                .withf_st(move |arg1, _, arg3| (*arg1 == pcap) && (*arg3 == 4))
445
                .return_once(|_, _, _| 0);
446

            
447
            sink.flush().await.unwrap();
448
            assert!(sink.packet.is_none());
449
        }
450
    }
451

            
452
    #[cfg(not(target_os = "linux"))]
453
    #[tokio::test]
454
    async fn test_sink_ok() {
455
        let _m = RAWMTX.lock();
456

            
457
        let mut dummy: isize = 777;
458
        let pcap = as_pcap_t(&mut dummy);
459

            
460
        let test_capture = test_capture::<Active>(pcap);
461

            
462
        let ctx = pcap_setnonblock_context();
463
        ctx.expect()
464
            .withf_st(move |arg1, arg2, _| (*arg1 == pcap) && (*arg2 == 1))
465
            .return_once(|_, _, _| 0);
466

            
467
        let capture = test_capture.capture.setnonblock().unwrap();
468

            
469
        let mut sink = capture.sink::<Vec<u8>>().unwrap();
470
        assert!(sink.capture_mut().is_nonblock());
471

            
472
        // Closing flushes the sink. The capture stays open until the sink is dropped.
473
        sink.close().await.unwrap();
474
    }
475

            
476
    #[cfg(not(target_os = "linux"))]
477
    #[tokio::test]
478
    async fn test_sink_sends() {
479
        let _m = RAWMTX.lock();
480

            
481
        let mut dummy: isize = 777;
482
        let pcap = as_pcap_t(&mut dummy);
483

            
484
        let test_capture = test_capture::<Active>(pcap);
485
        let mut sink = PacketSink::new(test_capture.capture).unwrap();
486

            
487
        let ctx = pcap_sendpacket_context();
488
        ctx.expect()
489
            .withf_st(move |arg1, _, arg3| (*arg1 == pcap) && (*arg3 == 4))
490
            .return_once(|_, _, _| 0);
491

            
492
        sink.send(vec![1, 2, 3, 4]).await.unwrap();
493
        assert!(sink.packet.is_none());
494

            
495
        let ctx = pcap_sendpacket_context();
496
        ctx.checkpoint();
497
        ctx.expect()
498
            .withf_st(move |arg1, _, _| *arg1 == pcap)
499
            .return_once(|_, _, _| -1);
500

            
501
        let _err = geterr_expect(pcap);
502

            
503
        let result = sink.send(vec![1, 2, 3, 4]).await;
504
        assert!(matches!(result, Err(Error::PcapError(_))));
505
        assert!(sink.packet.is_none());
506
    }
507

            
508
    #[cfg(not(target_os = "linux"))]
509
    #[tokio::test]
510
    async fn test_sink_closed() {
511
        let _m = RAWMTX.lock();
512

            
513
        let mut dummy: isize = 777;
514
        let pcap = as_pcap_t(&mut dummy);
515

            
516
        let test_capture = test_capture::<Active>(pcap);
517
        let mut sink = PacketSink::new(test_capture.capture).unwrap();
518

            
519
        sink.close().await.unwrap();
520

            
521
        // There is no pcap_sendpacket expectation, so a send here would fail the test.
522
        assert_eq!(
523
            sink.send(vec![1, 2, 3, 4]).await,
524
            Err(Error::IoError(io::ErrorKind::BrokenPipe))
525
        );
526
        assert!(sink.packet.is_none());
527
    }
528

            
529
    #[cfg(not(target_os = "linux"))]
530
    #[tokio::test]
531
    async fn test_sink_start_send_twice() {
532
        let _m = RAWMTX.lock();
533

            
534
        let mut dummy: isize = 777;
535
        let pcap = as_pcap_t(&mut dummy);
536

            
537
        let test_capture = test_capture::<Active>(pcap);
538
        let mut sink = PacketSink::new(test_capture.capture).unwrap();
539

            
540
        Pin::new(&mut sink).start_send(vec![1, 2, 3, 4]).unwrap();
541

            
542
        // Nothing has sent the first packet yet, so the second one is refused rather than put
543
        // in its place.
544
        assert_eq!(
545
            Pin::new(&mut sink).start_send(vec![5, 6, 7]),
546
            Err(Error::IoError(io::ErrorKind::WouldBlock))
547
        );
548
        assert_eq!(sink.packet, Some(vec![1, 2, 3, 4]));
549

            
550
        let ctx = pcap_sendpacket_context();
551
        ctx.expect()
552
            .withf_st(move |arg1, _, arg3| (*arg1 == pcap) && (*arg3 == 4))
553
            .return_once(|_, _, _| 0);
554

            
555
        sink.flush().await.unwrap();
556
        assert!(sink.packet.is_none());
557
    }
558

            
559
    // Deliberately not a tokio test. The sink is a futures::Sink and off Linux it holds nothing
560
    // of tokio's, so it has to give up its turn whatever executor is driving it.
561
    #[cfg(not(target_os = "linux"))]
562
    #[test]
563
    fn test_sink_yields() {
564
        let _m = RAWMTX.lock();
565

            
566
        let mut dummy: isize = 777;
567
        let pcap = as_pcap_t(&mut dummy);
568

            
569
        let test_capture = test_capture::<Active>(pcap);
570
        let mut sink = PacketSink::new(test_capture.capture).unwrap();
571

            
572
        let ctx = pcap_sendpacket_context();
573
        ctx.expect()
574
            .withf_st(move |arg1, _, _| *arg1 == pcap)
575
            .returning(|_, _, _| 0);
576

            
577
        let waker = futures::task::noop_waker();
578
        let mut cx = task::Context::from_waker(&waker);
579

            
580
        // Keep the sink fed and poll it until it asks to be polled again later. One that never
581
        // does keeps the thread to itself for as long as there are packets to send.
582
        let mut sends = 0;
583
        loop {
584
            sink.packet = Some(vec![1, 2, 3, 4]);
585
            if sink.poll_send(&mut cx).is_pending() {
586
                break;
587
            }
588
            sends += 1;
589
            assert!(sends < 10_000, "the sink never gave up its turn");
590
        }
591
        assert_eq!(sends, SENDS_BETWEEN_YIELDS);
592

            
593
        // The packet the sink yielded on is still there for the poll after it.
594
        assert_eq!(sink.packet, Some(vec![1, 2, 3, 4]));
595
    }
596

            
597
    // Under tokio the sink spends the task's budget too, so a task that has already spent some
598
    // of it elsewhere gets its turn back sooner than the count alone would give it.
599
    #[cfg(not(target_os = "linux"))]
600
    #[tokio::test]
601
    async fn test_sink_yields_on_task_budget() {
602
        let _m = RAWMTX.lock();
603

            
604
        let mut dummy: isize = 777;
605
        let pcap = as_pcap_t(&mut dummy);
606

            
607
        let test_capture = test_capture::<Active>(pcap);
608
        let mut sink = PacketSink::new(test_capture.capture).unwrap();
609

            
610
        let ctx = pcap_sendpacket_context();
611
        ctx.expect()
612
            .withf_st(move |arg1, _, _| *arg1 == pcap)
613
            .returning(|_, _, _| 0);
614

            
615
        // Spend half the budget on something that is not the sink.
616
        for _ in 0..SENDS_BETWEEN_YIELDS / 2 {
617
            coop::consume_budget().await;
618
        }
619

            
620
        let mut sends = 0;
621
        loop {
622
            sink.packet = Some(vec![1, 2, 3, 4]);
623
            if futures::future::poll_fn(|cx| Poll::Ready(sink.poll_send(cx)))
624
                .await
625
                .is_pending()
626
            {
627
                break;
628
            }
629
            sends += 1;
630
            assert!(sends < 10_000, "the sink never gave up its turn");
631
        }
632
        assert!(
633
            sends < SENDS_BETWEEN_YIELDS,
634
            "the sink sent {sends} before yielding, so it kept a budget of its own"
635
        );
636
    }
637
}