//! An [`AsyncWrite`] rate limiter which receives rate limit changes from a [`FusedStream`]. use std::pin::Pin; use std::task::{Context, Poll}; use futures::AsyncWrite; use futures::io::Error; use futures::stream::FusedStream; use tor_rtcompat::SleepProvider; use super::writer::{RateLimitedWriter, RateLimitedWriterConfig}; /// A rate-limited async [writer](AsyncWrite). /// /// This wraps a [`RateLimitedWriter`] and watches a stream for configuration changes (such as rate /// limit changes). #[derive(educe::Educe)] #[educe(Debug)] #[pin_project::pin_project] pub struct DynamicRateLimitedWriter { /// The rate-limited writer. #[pin] writer: RateLimitedWriter, /// A stream that provides configuration updates, including rate limit updates. #[educe(Debug(ignore))] #[pin] updates: S, } impl DynamicRateLimitedWriter where W: AsyncWrite, P: SleepProvider, { /// Create a new [`DynamicRateLimitedWriter`]. /// /// This wraps the `writer` and watches for configuration changes from the `updates` stream. pub fn new(writer: RateLimitedWriter, updates: S) -> Self { Self { writer, updates } } /// Access the inner [`AsyncWrite`] writer of the [`RateLimitedWriter`]. pub fn inner(&self) -> &W { self.writer.inner() } } impl AsyncWrite for DynamicRateLimitedWriter where W: AsyncWrite, S: FusedStream, P: SleepProvider, { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { let mut self_ = self.as_mut().project(); // Try getting any update to the rate limit and burst. // // We loop until we receive `Ready(None)` or `Pending`. The former indicates that we // shouldn't receive any more updates. The latter indicates that there aren't currently more // to read, and that we've registered the waker with the stream so that we'll wake when the // rate limit is later updated. // // Since `S` is a `FusedStream`, it's fine to call `poll_next()` even if `Ready(None)` was // returned in the past. let mut iters = 0; while let Poll::Ready(Some(config)) = self_.updates.as_mut().poll_next(cx) { // update the writer's configuration let now = self_.writer.sleep_provider().now(); self_.writer.adjust(now, &config); // It's possible that `DynamicRateLimitedWriter` was constructed with a stream where an // infinite number of items will be immediately ready, for example with // `futures::stream::repeat()`. We escape the possible infinite loop by returning an // error. iters += 1; if iters > 100_000 { const MSG: &str = "possible infinite loop in `DynamicRateLimitedWriter::poll_write`"; tracing::debug!(MSG); return Poll::Ready(Err(Error::other(MSG))); } } // Try writing the bytes. This also registers the waker with the `RateLimitedWriter`. self_.writer.poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().writer.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().writer.poll_close(cx) } } /// A module to make it easier to implement tokio traits without putting `cfg()` conditionals /// everywhere. #[cfg(feature = "tokio")] mod tokio_impl { use super::*; use tokio::io::AsyncWrite as TokioAsyncWrite; use tokio_util::compat::FuturesAsyncWriteCompatExt; use std::io::Result as IoResult; impl TokioAsyncWrite for DynamicRateLimitedWriter where W: AsyncWrite, S: FusedStream, P: SleepProvider, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { TokioAsyncWrite::poll_write(Pin::new(&mut self.compat_write()), cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { TokioAsyncWrite::poll_flush(Pin::new(&mut self.compat_write()), cx) } fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { TokioAsyncWrite::poll_shutdown(Pin::new(&mut self.compat_write()), cx) } } } // The single test here requires the `tokio` feature and so gate the test module to avoid // unused imports when the feature is disabled. #[cfg(all(test, feature = "tokio"))] mod test { #![allow(clippy::unwrap_used)] use super::*; use std::num::NonZero; use std::time::Duration; use futures::{AsyncReadExt, AsyncWriteExt, FutureExt, SinkExt}; use tor_rtcompat::SpawnExt; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; /// This test ensures that a [`DynamicRateLimitedWriter`] writes the expected number of bytes, /// as a background task alternates the rate limit between on/off once every second. #[test] fn alternating_on_off() { tor_rtmock::MockRuntime::test_with_various(|rt| async move { // drive time forward from 0 to 8_000 ms in 1 ms intervals let rt_clone = rt.clone(); rt.spawn(async move { for _ in 0..8_000 { rt_clone.progress_until_stalled().await; rt_clone.advance_by(Duration::from_millis(1)).await; } }) .unwrap(); // start with a rate limiter that doesn't allow any bytes let config = RateLimitedWriterConfig { rate: 0, burst: 0, // wake up the writer each time the rate limiter allows 10 bytes to be sent wake_when_bytes_available: NonZero::new(10).unwrap(), }; // there are some other crates which allow you to make a data "pipe" without tokio, but // I don't think it's worth bringing in a new dev-dependency for this let (writer, reader) = tokio::io::duplex(/* max_buf_size= */ 1000); let writer = writer.compat_write(); let mut reader = reader.compat(); let writer = RateLimitedWriter::new(writer, &config, rt.clone()); // how we send rate updates to the rate-limited writer let (mut rate_tx, rate_rx) = futures::channel::mpsc::unbounded(); // our rate-limited writer which can receive rate limit changes let mut writer = DynamicRateLimitedWriter::new(writer, rate_rx); /// Duration between updates. A prime number is used so that smaller intervals don't /// fall on this interval, which can causes issues with `MockRuntime::test_with_various` /// since the test becomes dependent on the order that tasks are woken. const UPDATE_INTERVAL: Duration = Duration::from_millis(841); // a background task which sends alternating on/off rate limits every 841 ms let rt_clone = rt.clone(); rt.spawn(async move { for rate in [100, 0, 200, 0, 400, 0] { rt_clone.sleep(UPDATE_INTERVAL).await; // update the rate/burst let mut config = config.clone(); config.rate = rate; config.burst = rate; // we expect the send() to succeed immediately rate_tx.send(config).now_or_never().unwrap().unwrap(); } }) .unwrap(); // a background task which writes as much as possible rt.spawn(async move { // write until the receiving end goes away while writer.write(&[0; 100]).await.is_ok() {} }) .unwrap(); // helper to make the `assert_eq` a single line let res_unwrap = Result::unwrap; let mut buf = vec![0; 1000]; let buf = &mut buf; // sleep for 1 ms so that our upcoming sleeps end 1 ms after the rate limit changes rt.sleep(Duration::from_millis(1)).await; // Rate is 0, so no bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(None, reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 100 bytes/s, so 841/(1000/100) = 84 bytes expected. // Woken every `wake_when_bytes_available` = 10 bytes, so 80 bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(Some(80), reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 0, so no bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(None, reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 200 bytes/s, so 841/(1000/200) = 168 bytes expected. // Woken every `wake_when_bytes_available` = 10 bytes, so 160 bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(Some(160), reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 0, so no bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(None, reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 400 bytes/s, so 841/(1000/400) = 336 bytes expected. // Woken every `wake_when_bytes_available` = 10 bytes, so 330 bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(Some(330), reader.read(buf).now_or_never().map(res_unwrap)); // Rate is 0, so no bytes expected. rt.sleep(UPDATE_INTERVAL).await; assert_eq!(None, reader.read(buf).now_or_never().map(res_unwrap)); }); } }