Skip to content

proposal: time: Timer.StopAndClear method #38945

@zeripath

Description

@zeripath

The correct formulation for handling for time.Timer.Stop() is error prone and awkward.

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

$ go version
go version go1.14.2 linux/amd64

Does this issue reproduce with the latest release?

yes

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

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/andrew/.cache/go-build"
GOENV="/home/andrew/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/andrew/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/andrew/src/go/gitea/go.mod"
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 -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build840805710=/tmp/go-build -gno-record-gcc-switches"

What did you do?

In several places in our codebase we create time.Timers. The problem is that handling the timer stop condition is not obvious to the beginning user and, even for those who know to clear the channel the correct incantation is errorprone (do I potentially need to clear the channel !timer.Stop() or timer.Stop()) and is needlessly repetitive.

What did you expect to see?

There should be a simple function that will stop and clear the channel for the timer.

e.g.

func (timer *time.Timer) StopAndClear() {
	if !timer.Stop() {
		select {
		case <-timer.C:
		default:
		}
	}
}

What did you see instead?

Every time I stop a timer I have to write:

timer := time.Timer(...)
...
if !timer.Stop() {
	select {
	case <-timer.C:
	default:
	}
}

to ensure that the channel does not leak. I also have to remember that the block has to activate when not stopped. Reviewers of code have to all be aware of this too.

It seems to be a needless gotcha that could be handled relatively nicely in the standard library.

I would also advise that the formulation in the standard library documentation is also changed as it encourages the naïve user to cause a deadlock:

i.e. instead of

if !t.Stop() {
	<-t.C
}

write

if !t.Stop() {
	select {
	case <-t.C:
	default:
	}
}

Thanks so much for the language in general though!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions