-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Another attempt at abstracting Instant::now
#381
Conversation
Currently, the timer uses a `Now` trait to abstract the source of time. This allows time to be mocked out. However, the current implementation has a number of limitations as represented by #288 and #296. The main issues are that `Now` requires `&mut self` which prevents a value from being easily used in a concurrent environment. Also, when wanting to write code that is abstract over the source of time, generics get out of hand. This patch provides an alternate solution. A new type, `Clock` is provided which defaults to `Instant::now` as the source of time, but allows configuring the actual source using a new iteration of the `Now` trait. This time, `Now` is `Send + Sync + 'static`. Internally, `Clock` stores the now value in an `Arc<Now>` value, which introduces dynamism and allows `Clock` values to be cloned and be `Sync`. Also, the current clock can be set for the current execution context using the `with_default` pattern. Because using the `Instant::now` will be the most common case by far, it is special cased in order to avoid the need to allocate an `Arc` and use dynamic dispatch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is outstanding! It solves a foundational problem for testing network-oriented programs.
tokio-timer/src/clock/clock.rs
Outdated
|
||
/// TODO: Dox | ||
#[derive(Default, Clone)] | ||
pub struct Clock { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this belong in tokio-timer
? What do you think about keeping this in a new tokio-clock
or somesuch? I can imagine tokio-dependent code benefiting from this primitive without using timers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly, I would rather avoid introducing a new crate until there is a real case driving it. The two primary reasons to split the crates are that dependencies are different or you want to issue breaking changes at different times. The way I see it:
tokio-clock
would have the same dependencies astokio-timer
.- If in the future, there is a reason to split them, this can be done in a backwards compatible way.
So, I would lean towards keeping it together for now until a solid case comes up.
One additional reason to abstract the source of time is that doing so allows additional control when doing fuzzing tests. When fuzzing, the return values of |
/cc @bluejekyll, we are hoping you will consider using |
The concept described in the initial comment sounds great! Here's some thoughts about implementation:
|
Is there a specific area? I haven't been following the development of this feature. I definitely have worked around mocking time in a lot of different areas for testing, I can definitely see this helping. Though, I still get nervous about global state (thread_local, I know), from abuse in other languages... |
@bluejekyll AFAIK, the main place the If you're uncomfortable with thread local storage by default, |
I mentioned this in Slack, but worth making a public linkable comment: It'd be useful to try to reduce the amount of times actually calling We could potentially help that with the API here, since otherwise, we'd need to thread around an
|
I only mention it as I see a lot of libraries using this approach, and I personally find that it can sometimes make testing harder. If you want to submit a PR, I'll happily take a look. |
Alternatively, trust-dns-resolver could provide its own |
Practically, I found that generics quickly explode: everything that builds a
I would definitely be curious to know more about the performance impact of the TLS, especially since time tends to be accessed frequently in the hot path of many applications. |
Fair point, the generic could be removed, and we just keep the
That was exactly my concern as well. For TLS implementations, there are fast ones that, if the compiler is building for a specific platform, can optimize well enough so that accesses are just an offset. If we're building more generically, this can inhibit the optimizations the compiler can do. In Rust specifically, here's the safeness checks it performs on every access of the slot. In hyper, there is a TLS cached formatted date header (since checking the clock and formatter a date is slow). I've experimented with moving it out of TLS and putting it directly in the type that writes the headers, and saw ~1-2% improvement in requests per second. |
|
I think this thread goes in a wrong direction. While I understand that The main use case is if I'm writing a protocol implementation, i.e. a HTTP client with a keep-alive timeout, I want to put clock here. Passing a clock by hand isn't going to work here. Because it needs to be at every layer. E.g. in some place I will need So either it need to be in Another point is if we have evidence that calls to struct Clock(Arc<NowWithCache>);
struct NowWithCache {
time_source: Box<Now>,
cached_value: Atomic<SystemTime>,
} The idea is that we update Then there might be explicit The precision of main loop iteration time is fine for all network-related time handling and for most other stuff too. But if we need the same clock for the non-IO thread there are approaches for that too. For example, cache |
I agree. So, given that we are still on futures 0.1, you propose to stick with the Also, your |
Yes. And Also, I'm not very familiar with current tokio codebase, but it might make sense to put the actual clock into a loop Handle (perhaps keeping under |
Right, these types would use
I'm not really following this. Using Since each component is being designed to work standalone, each has an associated TLS, but a single fn call should only ever result in at most one TLS lookup. If it starts becoming an issue in practice, I'm happy to revisit the design w/ more data. |
I pushed a commit that integrates |
Alright all, I think that this PR is good to go. I would appreciate any final thoughts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used this change in tower-balance
. I think we can definitely add some better testing utilities to improve ux, but it's usable as it is, so we shouldn't block on that!
Currently, the timer uses a
Now
trait to abstract the source of time.This allows time to be mocked out. However, the current implementation
has a number of limitations as represented by #288 and #296.
The main issues are that
Now
requires&mut self
which prevents avalue from being easily used in a concurrent environment. Also, when
wanting to write code that is abstract over the source of time, generics
get out of hand.
This patch provides an alternate solution. A new type,
Clock
isprovided which defaults to
Instant::now
as the source of time, butallows configuring the actual source using a new iteration of the
Now
trait. This time,
Now
isSend + Sync + 'static
. Internally,Clock
stores the now value in an
Arc<Now>
value, which introduces dynamismand allows
Clock
values to be cloned and beSync
.Also, the current clock can be set for the current execution context
using the
with_default
pattern.Because using the
Instant::now
will be the most common case by far, itis special cased in order to avoid the need to allocate an
Arc
and usedynamic dispatch.
Remaining
Break out issues
timer::Now
is removed, all the miscallow(deprecated)
should be removed.Timer
struct should no longer be generic overNow
. Instead, it should take aClock
.