cloudflare / cloudflare/quiche
Remove direct `Instant::now()` calls
- Dominant language
- Rust
- Stars
- 11.8k
- Forks
- 1.1k
- Avg merge
- 21h 9m
- Merged PRs (30d)
- 6
Description
Quiche currently uses `Instant::now()` directly in various places.
Moving time management from quiche to the applications would make the quiche state machine more deterministic, which would improve testability.
Also, the time granularity could be controlled by the application, potentially reducing the number of syscalls.
For example the signature of `send(&mut self, out: &mut [u8])` could be changed to `send(&mut self, out: &mut [u8], now: Instant)`.
Automated tests that require timed events would be simpler.
Example:
```diff
#[test]
/// Tests that old data is retransmitted on PTO.
fn early_retransmit() {
let mut buf = [0; 65535];
+ let mut now = EPOCH;
- let mut pipe = testing::Pipe::new().unwrap();
+ let mut pipe = testing::Pipe::new(now).unwrap();
- assert_eq!(pipe.handshake(), Ok(()));
+ now = pipe.handshake(now, Duration::from_millis(1)).unwrap();
// Client sends stream data.
assert_eq!(pipe.client.stream_send(0, b"a", false), Ok(1));
- assert_eq!(pipe.advance(), Ok(()));
+ now = pipe.advance(now, Duration::from_millis(1)).unwrap();
// Client sends more stream data, but packet is lost
assert_eq!(pipe.client.stream_send(4, b"b", false), Ok(1));
- assert!(pipe.client.send(&mut buf).is_ok());
+ assert!(pipe.client.send(&mut buf, now).is_ok());
// Wait until PTO expires. Since the RTT is very low, wait a bit more.
- let timer = pipe.client.timeout().unwrap();
+ let timer = pipe.client.timeout(now).unwrap();
- std::thread::sleep(timer + time::Duration::from_millis(1));
+ now += timer + Duration::from_millis(1);
- pipe.client.on_timeout();
+ pipe.client.on_timeout(now);
let epoch = packet::Epoch::Application;
assert_eq!(
pipe.client
.paths
.get_active()
.expect("no active")
.recovery
.loss_probes(epoch),
1,
);
// Client retransmits stream data in PTO probe.
- let (len, _) = pipe.client.send(&mut buf).unwrap();
+ let (len, _) = pipe.client.send(&mut buf, now).unwrap();
assert_eq!(
pipe.client
.paths
.get_active()
.expect("no active")
.recovery
.loss_probes(epoch),
0,
);
let frames =
testing::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();
let mut iter = frames.iter();
// Skip ACK frame.
iter.next();
assert_eq!(
iter.next(),
Some(&frame::Frame::Stream {
stream_id: 4,
data: stream::RangeBuf::from(b"b", 0, false),
})
);
assert_eq!(pipe.client.stats().retrans, 1);
}
```
Contributor guide
Research direction
Start by locating the direct Instant::now() calls and the send, timeout, on_timeout, and testing::Pipe entry points shown in the issue. Trace how time flows through the state machine and update timed tests such as early_retransmit to use injected Instant values; done means applications control time and timed tests no longer sleep.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, networking
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100