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: allow a user callback for dropped ticks #35480

Open
gobwas opened this issue Nov 9, 2019 · 7 comments
Labels
Projects
Milestone

Comments

@gobwas
Copy link

@gobwas gobwas commented Nov 9, 2019

Hi there!

There is a known time.Ticker behavior when ticks are being dropped if user can't read from time.Ticker.C channel in time.

For some kind of "real-time" applications it may be useful to know that ticks are dropped to react somehow on such events.

I suppose it could be allowed just by extending time.Ticker struct with an optional exported callback field (and little tweaks of time.sendTime use):

const interval = time.Second

t := time.NewTicker(interval)
t.OnTickDrop = func() {
    log.Printf("warning: dropping %s tick", interval)
}

Without such feature users need to implement their own tick scheduler with very similar implementation to time's internals.

@mpx

This comment has been minimized.

Copy link
Contributor

@mpx mpx commented Nov 10, 2019

The Ticker is designed like tihs since it must not block to ensure the runtime behaves correctly. The time.sendTime function from sleep.go is called each tick:

func sendTime(c interface{}, seq uintptr) {
        // Non-blocking send of time on c.
        // Used in NewTimer, it cannot block anyway (buffer).
        // Used in NewTicker, dropping sends on the floor is
        // the desired behavior when the reader gets behind,
        // because the sends are periodic.
        select {
        case c.(chan Time) <- Now():
        default:
        }
}

If you must log dropped ticks, you could wrap the Ticker with a separate goroutine that proxies the tick and logs dropped ticks (mostly works provided the period isn't too short). Eg:

upstream := make(chan time.Time)
// ... separate goroutine below ...
for {
    now := <-t.C
    select {
    case upstream <- now:
    default:
        log.Printf("dropped tick: %v", now)
    }
}

Alternatively, your ticker loop could detect dropped ticks when it successfully receives one ("too much time passed since last event").

In practice, software needs to be designed to cope with lost ticks otherwise it will be unreliable under various common circumstances.

@gobwas

This comment has been minimized.

Copy link
Author

@gobwas gobwas commented Nov 10, 2019

Hi @mpx,

Yes, these approaches may work, thank you.

But the first approach theoretically may not fit if you have thousands of goroutines handling ticks – you will start twice more goroutines with twice more memory consumption and so on. And as you said it will work depending on time intervals and busyness of the machine.

The second approach will not guarantee correctness of detection because too much time may be passed just when machine is busy and OS just not gave us enough cpu time.

What I mean is that callback will guarantee detection of tick drops, but of course with the penalty to runtime behavior. If user will block inside that callback it will be his own responsibility.

@mpx

This comment has been minimized.

Copy link
Contributor

@mpx mpx commented Nov 10, 2019

The second approach will not guarantee correctness of detection because too much time may be passed just when machine is busy and OS just not gave us enough cpu time.

A program needs to cope with scheduling delays otherwise it's likely buggy and/or may require a realtime system/design. There are no guarantees that a process is scheduled in time - it doesn't matter what approach you use. If it's important to check a missed tick I'd just do it in the tick handler, or create the proxy goroutine with minimal logic so it's always blocked on the tick (eg, a single select statement in a loop).

I'd strongly recommend designing logic that copes with/recovers from delayed scheduling/insufficient cpu resources/missed ticks. Perhaps consider using a Timer instead. Check whether a delay is needed and reset the timer to expire in future. If your processing loop has taken too long, then it can handle the situation.

What I mean is that callback will guarantee detection of tick drops, but of course with the penalty to runtime behavior. If user will block inside that callback it will be his own responsibility.

Avoiding the goroutine here would create a dangerous interface that will often cause breakage for user programs/polling loop. Hence, allowing a user callback (which may block) will require the runtime to manage creating/assigning goroutines for each tick anyway since the poll loop must not block. Goroutine scheduling isn't guaranteed, so it's likely to introduce delays. If a goroutine per timer isn't acceptible, then this isn't acceptible either. It's worse than the other options.

@bcmills

This comment has been minimized.

Copy link
Member

@bcmills bcmills commented Nov 10, 2019

What happens if the OnTickDrop handler itself can't keep up with the tick rate?

Fundamentally, a ticker that can't keep up must do one of two things: it must either drop ticks, or stretch the ticker period to what is feasible. The former can be done easily with a time.Ticker, and the latter with a time.Timer (using a loop with Reset).

@bcmills bcmills added this to the Proposal milestone Nov 10, 2019
@bcmills bcmills changed the title time: allow user to setup dropped tick handler proposal: time: allow a user callback for dropped ticks Nov 10, 2019
@gopherbot gopherbot added the Proposal label Nov 10, 2019
@gobwas

This comment has been minimized.

Copy link
Author

@gobwas gobwas commented Nov 10, 2019

What happens if the OnTickDrop handler itself can't keep up with the tick rate?

I think the same thing when time.sendTime can't keep up with the tick rate?

Fundamentally, a ticker that can't keep up must do one of two things...

No doubt that's right. The proposal is about dropping ticks – how to know exactly that tick handler can't keep up with the tick rate?

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Nov 12, 2019

We are not going to add a callback to call for missed ticks. That would be a footgun.

As @bcmills says, if you must handle missing ticks, don't use a time.Ticker. That's not what time.Ticker is for.

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Nov 21, 2019

Typically you have to decide between an API using a channel, which lets the two sides proceed independently, or using a callback, which couples them tightly. This API was designed intentionally to use a channel, to decouple the the user code from package time's own timers. We can't revisit that fundamental design decision at this point, and it would be at the least inconsistent to have both.

If you see ticks more than 1.5*delta apart, you dropped one, as @mpx already said.

I don't believe there's anything to do here.

@rsc rsc added this to Incoming in Proposals Dec 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Proposals
Incoming
6 participants
You can’t perform that action at this time.