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 | }
|
---|