wiki: add Go123Timer
Change-Id: Ibc46be9c348fe017906e59da0bb4e3ceb8d47113
Reviewed-on: https://go-review.googlesource.com/c/wiki/+/582195
Commit-Queue: Russ Cox <rsc@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
diff --git a/Go123Timer.md b/Go123Timer.md
new file mode 100644
index 0000000..10a4f7c
--- /dev/null
+++ b/Go123Timer.md
@@ -0,0 +1,204 @@
+# Go 1.23 Timer Channel Changes
+
+Go 1.23 includes a new implementation of the channel-based timers
+created by [time.NewTimer](/pkg/time/#NewTimer), [time.After](/pkg/time/#After),
+[time.NewTicker](/pkg/time/#NewTicker), and [time.Tick](/pkg/time/#Tick).
+
+The new implementation makes two important changes:
+
+ 1. Unstopped timers and tickers that are no longer referenced can be garbage collected.
+ Before Go 1.23, unstopped timers could not be garbage collected until the timer went off,
+ and unstopped tickers could never be garbage collected.
+ The Go 1.23 implementation avoids resource leaks in programs that don't use `t.Stop`.
+
+ 2. Timer channels are now synchronous (unbuffered), giving the `t.Reset` and `t.Stop`
+ methods a stronger guarantee: after one of those methods returns, no future receive from the
+ timer channel will observe a stale time value corresponding to the old timer
+ configuration. Before Go 1.23, it was impossible to avoid stale values with `t.Reset`,
+ and avoiding stale values with `t.Stop` required careful use of the return value from `t.Stop`.
+ The Go 1.23 implementation removes this concern entirely.
+
+The implementation changes have two observable side effects that may affect production
+behavior or tests, described in the following sections.
+
+The new implementation is only used in programs where package main is in a module
+with a `go.mod` declaring `go 1.23` or later.
+Other programs continue to use the old semantics.
+The [GODEBUG setting](/doc/godebug) `asynctimerchan=1` forces the old semantics;
+conversely, `asynctimerchan=0` forces the new semantics.
+
+## Cap and Len
+
+Before Go 1.23, the `cap` of a timer channel was 1, and the `len` of a timer channel
+indicated whether a value was waiting to be received (1 if so, 0 if not).
+The Go 1.23 implementation creates timer channels with `cap` and `len` always 0.
+
+In general, using `len` to poll any channel is usually not helpful, since another goroutine may
+receive from the channel concurrently, invalidating the result of `len` at any time.
+Code that polls a timer channel using `len` should instead use a non-blocking select.
+
+That is, code that does:
+
+ if len(t.C) == 1 {
+ <-t.C
+ more code
+ }
+
+should instead do:
+
+ select {
+ default:
+ case <-t.C:
+ more code
+ }
+
+## Select Races
+
+Before Go 1.23, a timer created with a very short interval, like 0ns or 1ns,
+would take significantly longer than that interval to make its channel ready
+for receiving, due to scheduling delays. That delay can be observed
+in code that selects between a channel that is ready before the select
+and a newly created timer with a very short timeout:
+
+ c := make(chan bool)
+ close(c)
+
+ select {
+ case <-c:
+ println("done")
+ case <-time.After(1*time.Nanosecond):
+ println("timeout")
+ }
+
+By the time the select arguments are evaluated and select looks at
+the channels involved, the timer should have expired, meaning
+that both cases are ready to proceed. Select chooses between multiple
+ready cases by choosing one randomly, so this program should
+select each case about half the time.
+
+Due to the scheduling delays in the timer implementation before Go 1.23,
+programs like this incorrectly executed the “done” case 100% of the time.
+
+The Go 1.23 timer implementation is not affected by the same scheduling delays,
+so in Go 1.23, that program executes each case about half the time.
+
+During testing of Go 1.23 in Google's code base, we found a handful of tests
+that used select to race channels ready to proceed (often context `Done` channels)
+against timers with very low timeouts. Usually, the production code would use
+a real timeout, in which case the race is uninteresting, but for testing the timeout
+would be set to something very small. And then the test would insist on the
+non-timeout case executing, failing if the timeout was reached.
+A reduced example might look like:
+
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-time.After(timeout):
+ return errors.New("timeout")
+ }
+
+Then the test would call this code with `timeout` set to 1ns and fail if the code returned an error.
+
+To fix a test like this, either the caller can be changed to understand that timeouts are possible,
+or the code can be changed to prefer the done channel even in the timeout case, like this:
+
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-time.After(timeout):
+ // Double-check that Done is not ready,
+ // in case of short timeout during test.
+ select {
+ default:
+ case <-ctx.Done():
+ return nil
+ }
+ return errors.New("timeout")
+ }
+
+## Debugging
+
+If a program or test fails using Go 1.23 but worked using Go 1.22,
+the `asynctimerchan` [GODEBUG setting](/doc/godebug) can be
+used to check whether the new timer implementation triggers
+the failure:
+
+ GODEBUG=asynctimerchan=0 mytest # force Go 1.23 timers
+ GODEBUG=asynctimerchan=1 mytest # force Go 1.22 timers
+
+If the program or test consistently passes using Go 1.22 but consistently
+fails using Go 1.23, that is a strong sign that the problem is related to timers.
+
+In all the test failures we have observed, the problem has been in the test
+itself, not the timer implementation, so the next step is to identify exactly
+which code in `mytest` depends on the old implementation.
+To do that, you can use [the `bisect` tool](https://pkg.go.dev/golang.org/x/tools/cmd/bisect):
+
+ go install golang.org/x/tools/cmd/bisect@latest
+ bisect -godebug asynctimerchan=1 mytest
+
+Invoked this way, `bisect` runs mytest repeatedly, toggling the new timer
+implementation on and off depending on the stack trace leading to the
+timer call. Using binary search, it narrows down an induced failure to
+enabling the new timers during specific stack traces, which it reports.
+While `bisect` runs, it prints status messages about its trials,
+mainly so that when the test is slow you know it is still running.
+
+An example `bisect` run looks like:
+
+ $ bisect -godebug asynctimerchan=1 ./view.test
+ bisect: checking target with all changes disabled
+ bisect: run: GODEBUG=asynctimerchan=1#n ./view.test... FAIL (7 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#n ./view.test... FAIL (7 matches)
+ bisect: checking target with all changes enabled
+ bisect: run: GODEBUG=asynctimerchan=1#y ./view.test... ok (7 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#y ./view.test... ok (7 matches)
+ bisect: target fails with no changes, succeeds with all changes
+ bisect: searching for minimal set of disabled changes causing failure
+ bisect: run: GODEBUG=asynctimerchan=1#!+0 ./view.test... FAIL (3 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+0 ./view.test... FAIL (3 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+00 ./view.test... ok (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+00 ./view.test... ok (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+10 ./view.test... FAIL (2 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+10 ./view.test... FAIL (2 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+0010 ./view.test... ok (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+0010 ./view.test... ok (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+1010 ./view.test... FAIL (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!+1010 ./view.test... FAIL (1 matches)
+ bisect: confirming failing change set
+ bisect: run: GODEBUG=asynctimerchan=1#v!+x65a ./view.test... FAIL (1 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#v!+x65a ./view.test... FAIL (1 matches)
+ bisect: FOUND failing change set
+ --- change set #1 (disabling changes causes failure)
+ internal/godebug.(*Setting).Value()
+ go/src/internal/godebug/godebug.go:165
+ time.syncTimer()
+ go/src/time/sleep.go:25
+ time.NewTimer()
+ go/src/time/sleep.go:144
+ time.After()
+ go/src/time/sleep.go:202
+ region_dash/regionlist.(*Cache).Top()
+ region_dash/regionlist/regionlist.go:89
+ region_dash/view.(*Page).ServeHTTP()
+ region_dash/view/view.go:45
+ region_dash/view.TestServeHTTPStatus.(*Router).Handler.func2()
+ httprouter/httprouter/params_go17.go:27
+ httprouter/httprouter.(*Router).ServeHTTP()
+ httprouter/httprouter/router.go:339
+ region_dash/view.TestServeHTTPStatus.func1()
+ region_dash/view/view.test.go:105
+ testing.tRunner()
+ go/src/testing/testing.go:1689
+ runtime.goexit()
+ go/src/runtime/asm_amd64.s:1695
+
+ ---
+ bisect: checking for more failures
+ bisect: run: GODEBUG=asynctimerchan=1#!-x65a ./view.test... ok (6 matches)
+ bisect: run: GODEBUG=asynctimerchan=1#!-x65a ./view.test... ok (6 matches)
+ bisect: target succeeds with all remaining changes disabled
+
+In this case, the stack trace makes clear exactly which call to `time.After` causes a failure
+when using the new timers.
+