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

time: NewTimer firing later if computer sleeps, how to use wall clock? #35012

Open
gabzim opened this issue Oct 19, 2019 · 16 comments
Open

time: NewTimer firing later if computer sleeps, how to use wall clock? #35012

gabzim opened this issue Oct 19, 2019 · 16 comments
Labels
help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Darwin
Milestone

Comments

@gabzim
Copy link

gabzim commented Oct 19, 2019

What version of Go are you using (go version)?

go version go1.11.4 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/gabriel/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/gabriel/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/gj/lr5vqgfn7jj_431xw83k37qc0000gn/T/go-build372524712=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

go func() {
	timer := time.NewTimer(timeUntilAlarm)
	select {
	case <-timer.C:
		log.WithField("event", event.Summary).Info("Firing alarm for event")
		alarmFired <- event
	case <-ctx.Done():
		// alarm cancelled
		timer.Stop()
	}
}()

What did you expect to see?

This works until my macbook goes to sleep (macOS) I'm guessing that it's because of the monotonic clock stopping. Is there a way to create a Timer that fires using the wall clock?

I need the alarm to fire at the right time (after x amount of minutes/hours have passed) regardless of whether the computer went into sleep mode in between the time when the timer was created and when it was supposed to fire.

What did you see instead?

If macbook goes to sleep, timer clock stops and it fires way later than it was supposed to (depending on how long the computer slept)

@gabzim gabzim changed the title time.NetTimer firing later if computer sleeps, how to use wall clock? time.NewTimer firing later if computer sleeps, how to use wall clock? Oct 19, 2019
@ianlancetaylor ianlancetaylor changed the title time.NewTimer firing later if computer sleeps, how to use wall clock? time: NewTimer firing later if computer sleeps, how to use wall clock? Oct 19, 2019
@ianlancetaylor ianlancetaylor added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Darwin labels Oct 19, 2019
@ianlancetaylor ianlancetaylor added this to the Backlog milestone Oct 19, 2019
@ianlancetaylor
Copy link
Contributor

This seems related to #24595 on GNU/Linux.

Looking at the runtime package code I'm slightly surprised that this doesn't already work as you expect. I'm not sure how to fix it.

@networkimprov
Copy link

networkimprov commented Oct 20, 2019

Apparently there are two options: #24595 (comment)

Maybe there should be two kinds of timer in Go, absolute and actual?

cc @eliasnaur @zx2c4

@odeke-em
Copy link
Member

A similar issue from last year 2018, that could use reading differences between the monotonic and wall clocks #29308

@zx2c4
Copy link
Contributor

zx2c4 commented Oct 20, 2019

I patch Go using this patch for WireGuard:

https://git.zx2c4.com/wireguard-apple/tree/wireguard-go-bridge/goruntime-boottime-over-monotonic.diff

It's a network protocol, and phones and laptops like to sleep a lot these days, so this winds up being rather important.

@gabzim
Copy link
Author

gabzim commented Oct 20, 2019

I found a lot of information about time.Sub but since time.NewTimer() only receives a Duration I'm not even sure if this is possible currently in go.

@zx2c4
Copy link
Contributor

zx2c4 commented Oct 20, 2019

Yea for that patch above to be complete, I'll need to augment the semaphore code (and futex code on Linux), either doing the same trick as on Windows, or perhaps finding some special argument. On the linux front, I talked to tglx (kernel timer maintainer) a few weeks ago about making futex boottime aware, and so maybe we'll push that forward for future improvements, if nothing currently existing suffices. On the macOS front, I haven't researched yet.

@as
Copy link
Contributor

as commented Oct 21, 2019

I need the alarm to fire at the right time (after x amount of minutes/hours have passed) regardless of whether the computer went into sleep mode in between the time when the timer was created and when it was supposed to fire.

This is impossible if the computer is asleep during the time which time.Timer is intended to fire.

  • What would you like time.Timer to do in this scenario?

I find that allowing time.Timer to fire after an arbitrary duration into the future unsettling, but it is even more unsettling when the input argument is a wall clock time.

I believe the correct front-facing API solution to this potential problem is not to create a new timer, but to add a NewTimerAt package function which takes in a Time argument instead of a duration.

func NewTimer(d Duration) *Timer
func NewTimerAt(t Time) *Timer

That being written, I do not think this is a problem worth solving in the standard library due to its apparent implementation complexity.

@mpx
Copy link
Contributor

mpx commented Oct 22, 2019

I find that allowing time.Timer to fire after an arbitrary duration into the future unsettling

This is necessarily how the contract for timers/sleep/etc.. works. A program gets control some time after the delay you specified, ideally not too long afterwards. However, there are many reasons why a program may not get control "quickly":

  • CPU starvation / scheduling, memory paging,..
  • SIGSTOP / SIGCONT
  • Suspend / resume

The best we can hope for is that the OS/runtime doesn't unnecessarily delay providing control to the program. Programs need to be written to cope with this uncertainly otherwise they are likely buggy.

In this case, I don't think we need a new timer, we just need the runtime to provide control as soon as is practical. At least, that allows the program to delay further if it is more appropriate.

Afaict, having a timer that pauses during suspend is almost never what you want, and having a timer that fires as close to the intended wall clock duration is almost always what you want. If this is problematic, then the programs were likely buggy to begin with.

@gabzim
Copy link
Author

gabzim commented Oct 22, 2019

I need the alarm to fire at the right time (after x amount of minutes/hours have passed) regardless of whether the computer went into sleep mode in between the time when the timer was created and when it was supposed to fire.

This is impossible if the computer is asleep during the time which time.Timer is intended to fire.

I understand this, of course I don't mean that I expect the timer to fire if the computer is asleep.

However something that has been happening to me is that I set a timer an hour from now, the computer sleeps for 15 mins within that hour (eg.) and the timer fires 1:15 hours later instead of 1 hour later. This is just a little counterintuitive.

It's one thing if the host is asleep during the time when the timer is supposed to fire but the monotonic clock pausing is something that makes it almost impossible to schedule any work reliably. I think the default should be a non pausing monotonic clock (eg CLOCK_BOOTIME, which I hope is supported in macos?) since it's just more common sense behavior.

@as
Copy link
Contributor

as commented Oct 22, 2019

The best we can hope for is that the OS/runtime doesn't unnecessarily delay providing control to the program.

If the process can sleep for t seconds as a result of a suspend event, why can't it sleep for t+c seconds as a result of a suspend event in addition to the remaining duration on the timer? We know that the user can suspend the machine for an arbitrary amount of time. They could even break the machine, suspending it for +inf seconds. Complex logic around minimizing the constant c is a best-effort attempt that guarantees nothing unless we know the specific duration the machine will be suspended.

@mpx
Copy link
Contributor

mpx commented Oct 22, 2019

If the process can sleep for t seconds as a result of a suspend event, why can't it sleep for t+c seconds as a result of a suspend event in addition to the remaining duration on the timer?

It could, but that's seems less useful. Timers can be delayed for many reasons (see above). Programs should be able to cope with that where possible. However, if the timer fires unnecessarily late there is no opportunity to correct it and the experience is likely worse.

Wall clock durations are well defined, and almost always what a programmer expects/assumes for timers. If a further delay is useful, it's easy enough to reschedule the timer.

Otherwise, it's unclear what time a programmer might want, and how to define it. CPU time? Time the OS is available to schedule processes? Time the OS is available to schedule a particular process (eg, handle CPU contention, SIGSTOP)? These seem to be proxies for "work done", or "work potential" which could be measured other ways. The need for timers like this isn't particularly common in my experience.

Consider a very common time event processing loop:

  • Process events expiring before now.
  • Find next event & calculate duration D to wall time T
  • Sleep D

Using wall clock durations it will process events as close to the intended clock as is practical. Using "OS runtime durations" it will be further delayed after suspend, even if it could have been triggered on time.

Complex logic around minimizing the constant c is a best-effort attempt that guarantees nothing unless we know the specific duration the machine will be suspended.

There are few guarantees with timers and program scheduling (see reasons above). If there is a choice between "wall clock durations" and "OS runtime durations", I'd pick the one that's more useful.
It doesn't need to be complicated.

@zx2c4
Copy link
Contributor

zx2c4 commented Oct 22, 2019

To be clear, I have no interest in implementing anything regarding the "wall clock". And I don't think @networkimprov's suggestions are viable.

My future participation in this general category will consist of moving go from CLOCK_MONOTONIC to CLOCK_BOOTTIME, which will solve the original poster's problem as well as some others.

@networkimprov
Copy link

networkimprov commented Oct 22, 2019

As documented in the other thread, there are use cases for both kinds of timer (absolute and suspend-aware), and a lot of code that expects the current absolute kind. So the question should be whether to leave things as they are, or to add a suspend-aware timing API.

@zx2c4 if it's viable to change the current scheme to clock_boottime, how could it be non-viable to add a timer API which uses that?

@as
Copy link
Contributor

as commented Oct 22, 2019

Using wall clock durations it will process events as close to the intended clock as is practical. Using "OS runtime durations" it will be further delayed after suspend, even if it could have been triggered on time.

@mpx What happens if the wall clock's time is changed?

@zx2c4
Copy link
Contributor

zx2c4 commented Oct 22, 2019

Using wall clock durations it will process events as close to the intended clock as is practical. Using "OS runtime durations" it will be further delayed after suspend, even if it could have been triggered on time.

@mpx What happens if the wall clock's time is changed?

Disaster ensues. Hence my suggestion that we use CLOCK_BOOTTIME instead.

@mpx
Copy link
Contributor

mpx commented Oct 22, 2019

Sorry, it seems there is a misunderstanding. It doesn't matter if the wall clock time is changed, timer durations are unaffected (that's what happens now, provided you don't suspend/resume). What I'm refering to is "real time passed" vs "time system running passed". So yes, CLOCK_BOOTTIME is better on Linux.

MichaelEischer added a commit to MichaelEischer/restic that referenced this issue Sep 10, 2022
Monotonic timers are paused during standby. Thus these timers won't fire
after waking up. Fall back to periodic polling to detect too large clock
jumps. See golang/go#35012 for a discussion of
go timers during standby.
MichaelEischer added a commit to MichaelEischer/restic that referenced this issue Oct 2, 2022
Monotonic timers are paused during standby. Thus these timers won't fire
after waking up. Fall back to periodic polling to detect too large clock
jumps. See golang/go#35012 for a discussion of
go timers during standby.
MichaelEischer added a commit to MichaelEischer/restic that referenced this issue Oct 2, 2022
Monotonic timers are paused during standby. Thus these timers won't fire
after waking up. Fall back to periodic polling to detect too large clock
jumps. See golang/go#35012 for a discussion of
go timers during standby.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Darwin
Projects
None yet
Development

No branches or pull requests

7 participants