chore: change mock clock to allow Advance() within timer/tick functions #13500
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I've modified the
Mock.Advance()
function to be more simple and allow more flexibility in testing.tl;dr -
Advance()
now only allows you to advance up to the next timer/ticker event and no further, and immediately advances the clock, rather than the clock advancing async as ticks and timers are processed.Why?
Prior to this change,
Advance()
moves the clock forward, possibly triggering multiple timers/ticks. You had toWait()
for the result before you could be sure the clock had moved as far as you asked. And, crucially, the internal implementation didn't allow you to make another call toAdvance()
until the first one finished.But, a test case that I want to support is being able to have time progress during a timer/ticker functional callback. The added UT to
enterprise/tailnet
is an example of this.heartbeats
makes a call toNow()
to record the time of a heartbeat, then later callsUntil()
to calculate when next to check for expiry. In a real system these calls can happen with different "current" times, and there was a bug that I noticed that if a coordinator is just about to expire, but not quite, by the time we get around to calculated when next to check, it could already be in the past, and we would have ignored it. So, we need toTrap()
the call toUntil
and advance the clock.With the prior implementation, this fails. I initially went down a rabbit hole of having a central goroutine manage advancing the clock, and allowing
Advance()
calls to come in while others have not fully resolved, but things get messy and complicated very quickly. In particular, how does the thing managing the clock know the order to process advances when one is happening during another?That way lies madness, probably, and completely destroys the goal of writing tests where the time is predictable after every mock call.
The proposed solution
So instead, we restrict
Advance()
to be able to, in a single call, move the clock up to the next event and no further. Then, it can immediately set the new time, even if you asynchronously wait for timers/ticker functions to return.For cases where you don't know or want to compute how long to the next event, I've added
AdvanceNext()
which returns the amount advanced for asserting or use in further calculations.This gets us back to a place where UTs are in the imperative style and the clock moves predictably, monotonically forward as the UT is executed.