summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrew Lytvynov <awly@tailscale.com>2026-04-24 16:16:42 -0700
committerGitHub <noreply@github.com>2026-04-24 16:16:42 -0700
commitd64ed4af89c4a709a9e08c2cc5b23d99d2753833 (patch)
tree7d7f1dec2161378b1f2f96fbbfffe0bcd3efab8d
parent4195e34f79639b644d23e49736ba2a17d53e7b48 (diff)
downloadtailscale-d64ed4af89c4a709a9e08c2cc5b23d99d2753833.tar.xz
tailscale-d64ed4af89c4a709a9e08c2cc5b23d99d2753833.zip
util/expvarx: remove unused package (#19519)
Added in 2024 and appears to be unused. Updates #cleanup Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
-rw-r--r--util/expvarx/expvarx.go89
-rw-r--r--util/expvarx/expvarx_test.go141
2 files changed, 0 insertions, 230 deletions
diff --git a/util/expvarx/expvarx.go b/util/expvarx/expvarx.go
deleted file mode 100644
index 6dc2379b9..000000000
--- a/util/expvarx/expvarx.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) Tailscale Inc & contributors
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package expvarx provides some extensions to the [expvar] package.
-package expvarx
-
-import (
- "encoding/json"
- "expvar"
- "time"
-
- "tailscale.com/syncs"
- "tailscale.com/types/lazy"
-)
-
-// SafeFunc is a wrapper around [expvar.Func] that guards against unbounded call
-// time and ensures that only a single call is in progress at any given time.
-type SafeFunc struct {
- f expvar.Func
- limit time.Duration
- onSlow func(time.Duration, any)
-
- mu syncs.Mutex
- inflight *lazy.SyncValue[any]
-}
-
-// NewSafeFunc returns a new SafeFunc that wraps f.
-// If f takes longer than limit to execute then Value calls return nil.
-// If onSlow is non-nil, it is called when f takes longer than limit to execute.
-// onSlow is called with the duration of the slow call and the final computed
-// value.
-func NewSafeFunc(f expvar.Func, limit time.Duration, onSlow func(time.Duration, any)) *SafeFunc {
- return &SafeFunc{f: f, limit: limit, onSlow: onSlow}
-}
-
-// Value acts similarly to [expvar.Func.Value], but if the underlying function
-// takes longer than the configured limit, all callers will receive nil until
-// the underlying operation completes. On completion of the underlying
-// operation, the onSlow callback is called if set.
-func (s *SafeFunc) Value() any {
- s.mu.Lock()
-
- if s.inflight == nil {
- s.inflight = new(lazy.SyncValue[any])
- }
- var inflight = s.inflight
- s.mu.Unlock()
-
- // inflight ensures that only a single work routine is spawned at any given
- // time, but if the routine takes too long inflight is populated with a nil
- // result. The long running computed value is lost forever.
- return inflight.Get(func() any {
- start := time.Now()
- result := make(chan any, 1)
-
- // work is spawned in routine so that the caller can timeout.
- go func() {
- // Allow new work to be started after this work completes
- defer func() {
- s.mu.Lock()
- s.inflight = nil
- s.mu.Unlock()
-
- }()
-
- v := s.f.Value()
- result <- v
- }()
-
- select {
- case v := <-result:
- return v
- case <-time.After(s.limit):
- if s.onSlow != nil {
- go func() {
- s.onSlow(time.Since(start), <-result)
- }()
- }
- return nil
- }
- })
-}
-
-// String implements stringer in the same pattern as [expvar.Func], calling
-// Value and serializing the result as JSON, ignoring errors.
-func (s *SafeFunc) String() string {
- v, _ := json.Marshal(s.Value())
- return string(v)
-}
diff --git a/util/expvarx/expvarx_test.go b/util/expvarx/expvarx_test.go
deleted file mode 100644
index f8d2139d3..000000000
--- a/util/expvarx/expvarx_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) Tailscale Inc & contributors
-// SPDX-License-Identifier: BSD-3-Clause
-
-package expvarx
-
-import (
- "expvar"
- "fmt"
- "sync"
- "sync/atomic"
- "testing"
- "testing/synctest"
- "time"
-)
-
-func ExampleNewSafeFunc() {
- // An artificial blocker to emulate a slow operation.
- blocker := make(chan struct{})
-
- // limit is the amount of time a call can take before Value returns nil. No
- // new calls to the unsafe func will be started until the slow call
- // completes, at which point onSlow will be called.
- limit := time.Millisecond
-
- // onSlow is called with the final call duration and the final value in the
- // event a slow call.
- onSlow := func(d time.Duration, v any) {
- _ = d // d contains the time the call took
- _ = v // v contains the final value computed by the slow call
- fmt.Println("slow call!")
- }
-
- // An unsafe expvar.Func that blocks on the blocker channel.
- unsafeFunc := expvar.Func(func() any {
- for range blocker {
- }
- return "hello world"
- })
-
- // f implements the same interface as expvar.Func, but returns nil values
- // when the unsafe func is too slow.
- f := NewSafeFunc(unsafeFunc, limit, onSlow)
-
- fmt.Println(f.Value())
- fmt.Println(f.Value())
- close(blocker)
- time.Sleep(time.Millisecond)
- fmt.Println(f.Value())
- // Output: <nil>
- // <nil>
- // slow call!
- // hello world
-}
-
-func TestSafeFuncHappyPath(t *testing.T) {
- synctest.Test(t, func(t *testing.T) {
- var count int
- f := NewSafeFunc(expvar.Func(func() any {
- count++
- return count
- }), time.Second, nil)
-
- if got, want := f.Value(), 1; got != want {
- t.Errorf("got %v, want %v", got, want)
- }
- time.Sleep(5 * time.Second) // (fake time in synctest)
- if got, want := f.Value(), 2; got != want {
- t.Errorf("got %v, want %v", got, want)
- }
- })
-}
-
-func TestSafeFuncSlow(t *testing.T) {
- var count int
- blocker := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(1)
- f := NewSafeFunc(expvar.Func(func() any {
- defer wg.Done()
- count++
- <-blocker
- return count
- }), time.Millisecond, nil)
-
- if got := f.Value(); got != nil {
- t.Errorf("got %v; want nil", got)
- }
- if got := f.Value(); got != nil {
- t.Errorf("got %v; want nil", got)
- }
-
- close(blocker)
- wg.Wait()
-
- if count != 1 {
- t.Errorf("got count=%d; want 1", count)
- }
-}
-
-func TestSafeFuncSlowOnSlow(t *testing.T) {
- var count int
- blocker := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(2)
- var slowDuration atomic.Pointer[time.Duration]
- var slowCallCount atomic.Int32
- var slowValue atomic.Value
- f := NewSafeFunc(expvar.Func(func() any {
- defer wg.Done()
- count++
- <-blocker
- return count
- }), time.Millisecond, func(d time.Duration, v any) {
- defer wg.Done()
- slowDuration.Store(&d)
- slowCallCount.Add(1)
- slowValue.Store(v)
- })
-
- for range 10 {
- if got := f.Value(); got != nil {
- t.Fatalf("got value=%v; want nil", got)
- }
- }
-
- close(blocker)
- wg.Wait()
-
- if count != 1 {
- t.Errorf("got count=%d; want 1", count)
- }
- if got, want := *slowDuration.Load(), 1*time.Millisecond; got < want {
- t.Errorf("got slowDuration=%v; want at least %d", got, want)
- }
- if got, want := slowCallCount.Load(), int32(1); got != want {
- t.Errorf("got slowCallCount=%d; want %d", got, want)
- }
- if got, want := slowValue.Load().(int), 1; got != want {
- t.Errorf("got slowValue=%d, want %d", got, want)
- }
-}