-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Version
hyper
version 1.7.0
(not relevant AFAIK), hyper-util
version 0.1.16
Platform
MacOS
Description
Now, I realise nowhere (that I know) Hyper promises to be compatible with Tokio's paused-time feature. But neither does it document (that I know) that it isn't.
The issue I'm reporting is fundamentally due to an assumption that hyper-util
makes that std::time::Instant
always matches tokio::time::Instant
. This is not true when paused time is enabled.
This probably spans multiple places, but let's focus on the one I've investigated: the IdleTask
implementation.
- Initially
std::time::Instant
andtokio::time::Instant
start the same. - When
IdleTask
is first polled, its internaltokio::time::Sleep
is configured to trigger afterstd::time::Instant::now()
plus the timeout interval. Since the clocks are still in sync (assuming it gets polled immediately, which seems to be the case empirically), this does the correct thing and the deadline is set 90s in the future (assuming the default timeout is used) according to both clocks. - Tokio will let other tasks do work, and once an idle state is reached, it will auto-advance time until the next scheduled event. Let's suppose this is the deadline set by
IdleTask
. - Now over 90s have passed in Tokio's clock, but very little real time has actually passed. The clocks have drifted. When we configure the next deadline here, it will be based off actual time. The actual time plus 90s is going to be very close to Tokio's perceived present, maybe even in the past. This causes the task to be immediately awaken and the future polled again.
- In the next iteration, very little actual time will have passed, so the next deadline is still approximately 90s past the starting time. Again this is in the present/past from Tokio's perspective, so again it schedules the future to be polled immediately.
- This creates a busy loop. The program hangs.
Interestingly, paused time used to almost work up to release 0.1.15. This is because the next deadline was calculated relative to the previous one, rather than the realtime clock. The drift check in L801 would never trigger, because it used real time, which after the first deadline was always set in the past from Tokio's perspective. Since the two clocks started in sync, and Tokio was nice enough to wake up the task at the right times, the previous deadline + timeout interval were approximately equal to tokio::time::Instant::now()
at each iteration, so the deadlines were set more or less correctly anyway.
I think even prior to 0.1.16 enough drift between computed deadlines (always spaced exactly this.duration
apart) and Tokio's simulated time would accumulate that you'd eventually hit the drift check at L801, and then it would hit a busy loop all the same. But that was relatively unlikely, since paused time is mostly used for testing, and tests tend to not run over that long (even in simulated time).
Suggested fix
I believe the right fix for this is to not transparently convert between std::time
and tokio::time
types like here and here. The same problem would likely occur in other runtimes if they diverged at all from std::time
s monotonic clock implementation.
Instead, hyper-util
should probably use an abstraction like what it does for the Sleep
methods, where it defers to a boxed trait object for the actual implementation. So instead of directly calling std::time::Instant::now()
, it should call some TimeProvider::now()
method that can be configured to one runtime or another.
Alternatively, the logic for determining and setting the next deadline could be moved to the runtime-specific implementations, so that IdleTask
would never have to directly manipulate Instant
s.
I think this part of the codebase is currently being revised, so maybe this is not worth fixing. But I figured I'd at least report the perceived regression — I've observed tests that were working just fine break after the 0.1.16 patch release. Even if paused time compatibility is not part of hyper-utils
promises, this isn't ideal.