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

Closed
gobwas opened this issue Nov 9, 2019 · 9 comments
Closed

proposal: time: allow a user callback for dropped ticks #35480

gobwas opened this issue Nov 9, 2019 · 9 comments

Comments

@gobwas
Copy link

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
Copy link
Contributor

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
Copy link
Author

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
Copy link
Contributor

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
Copy link
Contributor

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
@gobwas
Copy link
Author

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
Copy link
Contributor

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
Copy link
Contributor

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 (old) Dec 4, 2019
@rsc
Copy link
Contributor

rsc commented Feb 5, 2020

Based on the discussion above, this seems like a likely decline.

@rsc rsc moved this from Incoming to Likely Decline in Proposals (old) Feb 5, 2020
@rsc
Copy link
Contributor

rsc commented Feb 12, 2020

No change in consensus, so declining.

@rsc rsc closed this as completed Feb 12, 2020
@rsc rsc moved this from Likely Decline to Declined in Proposals (old) Feb 12, 2020
@golang golang locked and limited conversation to collaborators Feb 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

No branches or pull requests

6 participants