Skip to content

Commit 0b706e1

Browse files
findleyrgopherbot
authored andcommitted
counter: avoid the rotation timer in counter.Open
As observed in golang/go#68497, scheduling a rotation timer can break runtime deadlock detection (all goroutines are asleep...). Furthermore, for short-lived processes such as command line tools, there is no reason to schedule a file rotation. Therefore, change the default behavior of counter.Open not to rotate the counter file, and introduce a new OpenAndRotate API to be used by gopls. For golang/go#68497 Change-Id: I7929c2d622d15e36ca99036d8c7eac1eed8fabf5 Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/599075 Auto-Submit: Robert Findley <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent d2ee678 commit 0b706e1

File tree

4 files changed

+84
-6
lines changed

4 files changed

+84
-6
lines changed

counter/counter.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,21 @@ func NewStack(name string, depth int) *StackCounter {
8383
// If the telemetry mode is "off", Open is a no-op. Otherwise, it opens the
8484
// counter file on disk and starts to mmap telemetry counters to the file.
8585
// Open also persists any counters already created in the current process.
86+
//
87+
// Open should only be called from short-lived processes such as command line
88+
// tools. If your process is long-running, use [OpenAndRotate].
8689
func Open() {
87-
counter.Open()
90+
counter.Open(false)
91+
}
92+
93+
// OpenAndRotate is like [Open], but also schedules a rotation of the counter
94+
// file when it expires.
95+
//
96+
// See golang/go#68497 for background on why [OpenAndRotate] is a separate API.
97+
//
98+
// TODO(rfindley): refactor Open and OpenAndRotate for Go 1.24.
99+
func OpenAndRotate() {
100+
counter.Open(true)
88101
}
89102

90103
// OpenDir prepares telemetry counters for recording to the file system, using
@@ -97,7 +110,7 @@ func OpenDir(telemetryDir string) {
97110
if telemetryDir != "" {
98111
telemetry.Default = telemetry.NewDir(telemetryDir)
99112
}
100-
counter.Open()
113+
counter.Open(false)
101114
}
102115

103116
// CountFlags creates a counter for every flag that is set

counter/counter_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package counter_test
6+
7+
import (
8+
"os"
9+
"os/exec"
10+
"testing"
11+
12+
"golang.org/x/telemetry/counter"
13+
"golang.org/x/telemetry/internal/telemetry"
14+
"golang.org/x/telemetry/internal/testenv"
15+
)
16+
17+
const telemetryDirEnvVar = "_COUNTER_TEST_TELEMETRY_DIR"
18+
19+
func TestMain(m *testing.M) {
20+
if dir := os.Getenv(telemetryDirEnvVar); dir != "" {
21+
// Run for TestOpenAPIMisuse.
22+
telemetry.Default = telemetry.NewDir(dir)
23+
counter.Open()
24+
counter.OpenAndRotate() // should panic
25+
os.Exit(0)
26+
}
27+
os.Exit(m.Run())
28+
}
29+
30+
func TestOpenAPIMisuse(t *testing.T) {
31+
testenv.SkipIfUnsupportedPlatform(t)
32+
33+
// Test that Open and OpenAndRotate cannot be used simultaneously.
34+
exe, err := os.Executable()
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
cmd := exec.Command(exe)
39+
cmd.Env = append(os.Environ(), telemetryDirEnvVar+"="+t.TempDir())
40+
41+
if err := cmd.Run(); err == nil {
42+
t.Error("Failed to detect API misuse: no error from calling both Open and OpenAndRotate")
43+
}
44+
}

counter/countertest/countertest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func Open(telemetryDir string) {
4040
}
4141
telemetry.Default = telemetry.NewDir(telemetryDir)
4242

43+
// TODO(rfindley): reinstate test coverage with counter rotation enabled.
44+
// Before the [counter.Open] and [counter.OpenAndRotate] APIs were split,
45+
// this called counter.Open (which rotated!).
4346
counter.Open()
4447
opened = true
4548
}

internal/counter/file.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,27 +357,42 @@ func (f *file) newCounter1(name string) (v *atomic.Uint64, cleanup func()) {
357357
return v, cleanup
358358
}
359359

360-
var openOnce sync.Once
360+
var (
361+
openOnce sync.Once
362+
// rotating reports whether the call to Open had rotate = true.
363+
//
364+
// In golang/go#68497, we observed that file rotation can break runtime
365+
// deadlock detection. To minimize the fix for 1.23, we are splitting the
366+
// Open API into one version that rotates the counter file, and another that
367+
// does not. The rotating variable guards against use of both APIs from the
368+
// same process.
369+
rotating bool
370+
)
361371

362372
// Open associates counting with the defaultFile.
363373
// The returned function is for testing only, and should
364374
// be called after all Inc()s are finished, but before
365375
// any reports are generated.
366376
// (Otherwise expired count files will not be deleted on Windows.)
367-
func Open() func() {
377+
func Open(rotate bool) func() {
368378
if telemetry.DisabledOnPlatform {
369379
return func() {}
370380
}
371381
close := func() {}
372382
openOnce.Do(func() {
383+
rotating = rotate
373384
if mode, _ := telemetry.Default.Mode(); mode == "off" {
374385
// Don't open the file when telemetry is off.
375386
defaultFile.err = ErrDisabled
376387
// No need to clean up.
377388
return
378389
}
379-
debugPrintf("Open")
380-
defaultFile.rotate()
390+
debugPrintf("Open(%v)", rotate)
391+
if rotate {
392+
defaultFile.rotate() // calls rotate1 and schedules a rotation
393+
} else {
394+
defaultFile.rotate1()
395+
}
381396
close = func() {
382397
// Once this has been called, the defaultFile is no longer usable.
383398
mf := defaultFile.current.Load()
@@ -388,6 +403,9 @@ func Open() func() {
388403
mf.close()
389404
}
390405
})
406+
if rotating != rotate {
407+
panic("BUG: Open called with inconsistent values for 'rotate'")
408+
}
391409
return close
392410
}
393411

0 commit comments

Comments
 (0)