Skip to content
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

proposal: time: change Ticker to provide info about skipped ticks #3516

Closed
gopherbot opened this issue Apr 13, 2012 · 18 comments

Comments

Projects
None yet
4 participants
@gopherbot
Copy link

commented Apr 13, 2012

by kballard:

The interface for time.Ticker makes it impossible to determine if any ticks have been
skipped if the period is small. The difficulty lies in the fact that the underlying
timer has a "when" field that isn't exposed, and the client of the ticker
can't possibly know exactly what the value of "when" is. On each tick it's
given the time that the tick was calculated at, but without the "when" field,
determining if any ticks have been skipped is not possible with 100% accuracy. This is,
of course, only a problem for very small periods.

Rather than exposing the "when" field, it's probably much simpler and better
to simply create a second constructor that takes a Time and a Duration instead of just a
Duration. This Time is used as the initial "when". Not only does this make the
code for detecting skipped ticks rather trivial, it also allows for more flexibility in
timer creation for clients that need that.
@adg

This comment has been minimized.

Copy link
Contributor

commented Apr 19, 2012

Comment 1:

func NewTickerFrom(time.Time, time.Duration)
?
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 20, 2012

Comment 2:

I don't believe this is worth the complexity of added API.  If you need such precise
information, it should be easy to record the time when you processed the first tick and
then use that as a reference for deciding how many "ticks" have elapsed for each future
processing.

Labels changed: added priority-later, removed priority-triage.

Status changed to Thinking.

@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 20, 2012

Comment 3 by kballard:

This also won't work with 100% accuracy, because the time.Time passed into the timer is
not the value of the 'when' field that triggered the timer, but rather the current time
at the point where the runtime began processing the timer list. Using this Time value is
no better than just grabbing time.Now() before scheduling the timer.
This could actually be fixed without any API changes by simply modifying the Time passed
into the timer. Instead of using the current time, just send in the 'when' field. For
periodic timers, this should actually be the updated 'when' field minus the period, so
that it reflects the fact that ticks (may) have been skipped.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 20, 2012

Comment 4 by kballard:

Incidentally, while reading through this code, it occurs to me that it doesn't actually
skip ticks properly if multiple timers are processed in a single pass and any timer
takes more time to process than the period of subsequent timers (which is actually a
very real risk in my code). This is because the 'now' value is only grabbed once per
pass. If this occurs then it won't skip the ticks on the later timers that the earlier
timers "used", and the later timer will end up being invoked twice in a row (with the
same time value, in fact).
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 20, 2012

Comment 5:

All the code running during the pass is non-blocking.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 20, 2012

Comment 6 by kballard:

Upon further thought, the fact that the API code uses a buffered channel mitigates this
risk significantly. I'm not sure what the Go runtime's behavior is when sending a value
to an empty buffered channel with a pending reader (e.g. whether it wakes up the reader
immediately, or whether it continues the current goroutine and yields later). Internally
it'll certainly call the runtimeTimer.f function twice in the scenario I described, I'm
just unsure what the risk of actually managing to get two identical time.Time values
passed to a client-visible Timer.C channel is.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 20, 2012

Comment 7 by kballard:

Ok, I think I've overanalyzed this too much and missed something. It can't send the
exact same Time twice because the Time value itself never changes during a pass. What it
can do is not skip enough ticks, if processing earlier timers takes enough time that a
tick on a later timer should have passed. But it won't re-invoke the same timer until a
subsequent pass through the timer list (with a new Time value). Assuming that this code
is not only non-blocking but also non-yielding then I guess this isn't really a problem,
because a timer with a period so small that processing a single timer takes longer than
the tick is already broken.
So lets set that diversion to rest.
Regardless, my original point about the timer not working with 100% accuracy is still a
concern. With the proposed modification, skipped ticks can be detected with 100%
accuracy and with no delays in detection, and it involves no API changes. I also cannot
think of any reason why existing code that uses Timers would break with this change.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 23, 2012

Comment 8:

I don't understand why this is worth so much effort.
Why not check the time when your code is actually
executing?  The time sent on the channel is as correct
as the current code can make it, but by the time you run
it is probably out of date.
Russ
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 23, 2012

Comment 9 by kballard:

Because I'm trying to peg a cycle-accurate CPU emulator at exactly 100KHz. I need to
know precisely when ticks have been skipped, so I can spin several cycles in the next
tick to catch up.
Because of the lack of proper support for this in time.Ticker I was forced to roll my
own solution, using a separate call to time.After every single time I needed to delay
(which means almost 100,000 calls to time.After per second). Only this way was I able to
keep track of precisely when the next cycle was supposed to fire, so I could tell how
many ticks I needed to catch up.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 23, 2012

Comment 10:

It seems like
const period = 1*time.Second/100e3
t0 := time.Now()
var ticks int64
for _ = range time.Ticker(period) {
    t1 := time.Now()
    allowed := int64(t1.Sub(t0)/period)
    for ; ticks < allowed; ticks++ {
        step()
    }
}
would work.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 23, 2012

Comment 11 by kballard:

This works, except the ticker's fire date is not connected to the t0 value, which
basically means I'm not getting accurate ticks (defined as a tick that comes as close as
possible to firing on an exact multiple of the period since t0). I suppose it's a fairly
trivial complaint, since the difference is pretty small, and I'm operating at 100KHz
anyway. But this is also a fairly academic project, what with me trying to run a
cycle-accurate emulation of a fake CPU. I admit the actual start time of the CPU doesn't
really matter in the slightest, but I just don't like the fact that I'm not getting
truly accurate ticks.
Anyway, at this point I'm basically just using the ticker as a way to drive my own test
for missed ticks. While that will certainly produce results, it also annoys me. It also
feels like the time value passed into the channel is completely pointless, since it
doesn't have much meaning. Passing the runtimeTicker.when field (modified appropriately
by the period) into the channel instead would at least provide some real useful meaning
to that value.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 23, 2012

Comment 12:

You are using the ticker both for driving a test for missed ticks
_and_ for sleeping between ticks.  The latter is the more important part.
I do not want to change the time.Ticker API right now, nor do I want
to introduce a second API entry point.  Part of Go 1 is putting up with
what we have instead of continuing to refine.
Russ
@gopherbot

This comment has been minimized.

Copy link
Author

commented Apr 23, 2012

Comment 13 by kballard:

I recognize that you don't want to change the API, which is why I'm suggesting right now
just changing the time.Time value that's passed into the channel. The current value has
very little meaning, it just being the time at which the runtime called nanoseconds()
before starting to process the timers. All we really know about the time is that it's >=
the fire date for any timers that get fired. By changing it to the actual fire date for
the timer in question (after accounting for missed ticks), the time value becomes more
useful.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Apr 23, 2012

Comment 14:

In addition to not changing the type signatures, we're not planning to
change implementation details unless they are clearly bugs.  If we
were writing Timer today I would be happy to think about changing
things, but we're not.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Jul 30, 2013

Comment 15:

Labels changed: added go2.

@rsc

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2013

Comment 16:

Labels changed: added repo-main.

@rsc

This comment has been minimized.

Copy link
Contributor

commented Mar 4, 2014

Comment 17:

Labels changed: added release-none.

@rsc rsc added this to the Unplanned milestone Apr 10, 2015

@rsc rsc changed the title time: change Ticker to provide info about skipped ticks proposal: time: change Ticker to provide info about skipped ticks Jun 17, 2017

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Nov 29, 2017

The current implementation of time.NewTicker sends the current time (as returned by time.Now) on each tick. I don't think we can do any better; Go is not a hard real time system. If you need more precision I think you need your own mechanism outside of the standard library.

Closing.

@golang golang locked and limited conversation to collaborators Nov 29, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.