Skip to content

Commit 80b9fac

Browse files
gaalSajmani
authored andcommitted
x/time/rate: add rate.Sometimes, which runs a function occasionally.
Modeled after sync.Once; intended to provide simple throttling akin to the C++ log functions LOG_FIRST_N, LOG_EVERY_N, and LOG_EVERY_N_SEC. Originally authored by sameer@golang.org. Fixes golang/go#54237 Change-Id: I7c6266cc780eb6dad30d310485de492f790dbcdb Reviewed-on: https://go-review.googlesource.com/c/time/+/421915 Reviewed-by: Sameer Ajmani <sameer@golang.org>
1 parent f3bd1da commit 80b9fac

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

rate/sometimes.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2022 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 rate
6+
7+
import (
8+
"sync"
9+
"time"
10+
)
11+
12+
// Sometimes will perform an action occasionally. The First, Every, and
13+
// Interval fields govern the behavior of Do, which performs the action.
14+
// A zero Sometimes value will perform an action exactly once.
15+
//
16+
// # Example: logging with rate limiting
17+
//
18+
// var sometimes = rate.Sometimes{First: 3, Interval: 10*time.Second}
19+
// func Spammy() {
20+
// sometimes.Do(func() { log.Info("here I am!") })
21+
// }
22+
type Sometimes struct {
23+
First int // if non-zero, the first N calls to Do will run f.
24+
Every int // if non-zero, every Nth call to Do will run f.
25+
Interval time.Duration // if non-zero and Interval has elapsed since f's last run, Do will run f.
26+
27+
mu sync.Mutex
28+
count int // number of Do calls
29+
last time.Time // last time f was run
30+
}
31+
32+
// Do runs the function f as allowed by First, Every, and Interval.
33+
//
34+
// The model is a union (not intersection) of filters. The first call to Do
35+
// always runs f. Subsequent calls to Do run f if allowed by First or Every or
36+
// Interval.
37+
//
38+
// A non-zero First:N causes the first N Do(f) calls to run f.
39+
//
40+
// A non-zero Every:M causes every Mth Do(f) call, starting with the first, to
41+
// run f.
42+
//
43+
// A non-zero Interval causes Do(f) to run f if Interval has elapsed since
44+
// Do last ran f.
45+
//
46+
// Specifying multiple filters produces the union of these execution streams.
47+
// For example, specifying both First:N and Every:M causes the first N Do(f)
48+
// calls and every Mth Do(f) call, starting with the first, to run f. See
49+
// Examples for more.
50+
//
51+
// If Do is called multiple times simultaneously, the calls will block and run
52+
// serially. Therefore, Do is intended for lightweight operations.
53+
//
54+
// Because a call to Do may block until f returns, if f causes Do to be called,
55+
// it will deadlock.
56+
func (s *Sometimes) Do(f func()) {
57+
s.mu.Lock()
58+
defer s.mu.Unlock()
59+
if s.count == 0 ||
60+
(s.First > 0 && s.count < s.First) ||
61+
(s.Every > 0 && s.count%s.Every == 0) ||
62+
(s.Interval > 0 && time.Since(s.last) >= s.Interval) {
63+
f()
64+
s.last = time.Now()
65+
}
66+
s.count++
67+
}

rate/sometimes_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2022 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 rate_test
6+
7+
import (
8+
"fmt"
9+
"math"
10+
"testing"
11+
"time"
12+
13+
"golang.org/x/time/rate"
14+
)
15+
16+
func ExampleSometimes_once() {
17+
// The zero value of Sometimes behaves like sync.Once, though less efficiently.
18+
var s rate.Sometimes
19+
s.Do(func() { fmt.Println("1") })
20+
s.Do(func() { fmt.Println("2") })
21+
s.Do(func() { fmt.Println("3") })
22+
// Output:
23+
// 1
24+
}
25+
26+
func ExampleSometimes_first() {
27+
s := rate.Sometimes{First: 2}
28+
s.Do(func() { fmt.Println("1") })
29+
s.Do(func() { fmt.Println("2") })
30+
s.Do(func() { fmt.Println("3") })
31+
// Output:
32+
// 1
33+
// 2
34+
}
35+
36+
func ExampleSometimes_every() {
37+
s := rate.Sometimes{Every: 2}
38+
s.Do(func() { fmt.Println("1") })
39+
s.Do(func() { fmt.Println("2") })
40+
s.Do(func() { fmt.Println("3") })
41+
// Output:
42+
// 1
43+
// 3
44+
}
45+
46+
func ExampleSometimes_interval() {
47+
s := rate.Sometimes{Interval: 1 * time.Second}
48+
s.Do(func() { fmt.Println("1") })
49+
s.Do(func() { fmt.Println("2") })
50+
time.Sleep(1 * time.Second)
51+
s.Do(func() { fmt.Println("3") })
52+
// Output:
53+
// 1
54+
// 3
55+
}
56+
57+
func ExampleSometimes_mix() {
58+
s := rate.Sometimes{
59+
First: 2,
60+
Every: 2,
61+
Interval: 2 * time.Second,
62+
}
63+
s.Do(func() { fmt.Println("1 (First:2)") })
64+
s.Do(func() { fmt.Println("2 (First:2)") })
65+
s.Do(func() { fmt.Println("3 (Every:2)") })
66+
time.Sleep(2 * time.Second)
67+
s.Do(func() { fmt.Println("4 (Interval)") })
68+
s.Do(func() { fmt.Println("5 (Every:2)") })
69+
s.Do(func() { fmt.Println("6") })
70+
// Output:
71+
// 1 (First:2)
72+
// 2 (First:2)
73+
// 3 (Every:2)
74+
// 4 (Interval)
75+
// 5 (Every:2)
76+
}
77+
78+
func TestSometimesZero(t *testing.T) {
79+
s := rate.Sometimes{Interval: 0}
80+
s.Do(func() {})
81+
s.Do(func() {})
82+
}
83+
84+
func TestSometimesMax(t *testing.T) {
85+
s := rate.Sometimes{Interval: math.MaxInt64}
86+
s.Do(func() {})
87+
s.Do(func() {})
88+
}
89+
90+
func TestSometimesNegative(t *testing.T) {
91+
s := rate.Sometimes{Interval: -1}
92+
s.Do(func() {})
93+
s.Do(func() {})
94+
}

0 commit comments

Comments
 (0)