1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
//! 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<W: AsyncWrite, S, P: SleepProvider> {
/// The rate-limited writer.
#[pin]
writer: RateLimitedWriter<W, P>,
/// A stream that provides configuration updates, including rate limit updates.
#[educe(Debug(ignore))]
#[pin]
updates: S,
}
impl<W, S, P> DynamicRateLimitedWriter<W, S, P>
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<W, P>, updates: S) -> Self {
Self { writer, updates }
}
/// Access the inner [`AsyncWrite`] writer of the [`RateLimitedWriter`].
pub fn inner(&self) -> &W {
self.writer.inner()
}
}
impl<W, S, P> AsyncWrite for DynamicRateLimitedWriter<W, S, P>
where
W: AsyncWrite,
S: FusedStream<Item = RateLimitedWriterConfig>,
P: SleepProvider,
{
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, Error>> {
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<Result<(), Error>> {
self.project().writer.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
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<W, S, P> TokioAsyncWrite for DynamicRateLimitedWriter<W, S, P>
where
W: AsyncWrite,
S: FusedStream<Item = RateLimitedWriterConfig>,
P: SleepProvider,
{
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<IoResult<usize>> {
TokioAsyncWrite::poll_write(Pin::new(&mut self.compat_write()), cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
TokioAsyncWrite::poll_flush(Pin::new(&mut self.compat_write()), cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
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));
});
}
}
|