@@ -8,47 +8,64 @@ import (
8
8
"github.com/reugn/go-streams"
9
9
)
10
10
11
- // ThrottleMode represents Throttler's processing behavior when its element
12
- // buffer overflows.
11
+ // ThrottleMode defines the behavior of the Throttler when its internal buffer is full.
13
12
type ThrottleMode int8
14
13
15
14
const (
16
- // Backpressure slows down upstream ingestion when the element buffer overflows.
15
+ // Backpressure instructs the Throttler to block upstream ingestion when its internal
16
+ // buffer is full. This effectively slows down the producer, preventing data loss
17
+ // and ensuring all elements are eventually processed, albeit at a reduced rate. This
18
+ // mode can cause upstream operations to block indefinitely if the downstream consumer
19
+ // cannot keep up.
17
20
Backpressure ThrottleMode = iota
18
-
19
- // Discard drops incoming elements when the element buffer overflows.
21
+ // Discard instructs the Throttler to drop incoming elements when its internal buffer
22
+ // is full. This mode prioritizes maintaining the target throughput rate, even at the
23
+ // cost of data loss. Elements are silently dropped without any indication to the
24
+ // upstream producer. Use this mode when data loss is acceptable.
20
25
Discard
21
26
)
22
27
23
28
// Throttler limits the throughput to a specific number of elements per time unit.
24
29
type Throttler struct {
25
- maxElements int64
26
30
period time.Duration
27
31
mode ThrottleMode
32
+ maxElements int64
33
+ counter atomic.Int64
34
+
28
35
in chan any
29
36
out chan any
30
37
quotaSignal chan struct {}
31
38
done chan struct {}
32
- counter int64
33
39
}
34
40
35
41
// Verify Throttler satisfies the Flow interface.
36
42
var _ streams.Flow = (* Throttler )(nil )
37
43
38
44
// NewThrottler returns a new Throttler operator.
39
45
//
46
+ // The Throttler operator limits the rate at which elements are produced. It allows a
47
+ // maximum of 'elements' number of elements to be processed within a specified 'period'
48
+ // of time.
49
+ //
40
50
// elements is the maximum number of elements to be produced per the given period of time.
41
- // bufferSize specifies the buffer size for incoming elements.
42
- // mode specifies the processing behavior when the elements buffer overflows.
51
+ // bufferSize is the size of the internal buffer for incoming elements. This buffer
52
+ // temporarily holds elements waiting to be processed.
53
+ // mode specifies the processing behavior when the internal elements buffer is full.
54
+ // See [ThrottleMode] for available options.
43
55
//
44
- // If elements or bufferSize are not positive, NewThrottler will panic.
56
+ // If elements or bufferSize are not positive, or if mode is not a supported
57
+ // ThrottleMode, NewThrottler will panic.
45
58
func NewThrottler (elements int , period time.Duration , bufferSize int , mode ThrottleMode ) * Throttler {
46
59
if elements < 1 {
47
60
panic (fmt .Sprintf ("nonpositive elements number: %d" , elements ))
48
61
}
49
62
if bufferSize < 1 {
50
63
panic (fmt .Sprintf ("nonpositive buffer size: %d" , bufferSize ))
51
64
}
65
+ if mode != Discard && mode != Backpressure {
66
+ panic (fmt .Sprintf ("unsupported ThrottleMode: %d" , mode ))
67
+ }
68
+
52
69
throttler := & Throttler {
53
70
maxElements : int64 (elements ),
54
71
period : period ,
@@ -66,19 +83,19 @@ func NewThrottler(elements int, period time.Duration, bufferSize int, mode Throt
66
83
67
84
// quotaExceeded checks whether the quota per time unit has been exceeded.
68
85
func (th * Throttler ) quotaExceeded () bool {
69
- return atomic . LoadInt64 ( & th .counter ) >= th .maxElements
86
+ return th .counter . Load ( ) >= th .maxElements
70
87
}
71
88
72
89
// resetQuotaCounterLoop resets the throttler quota counter every th.period
73
- // and sends a release notification to the downstream processor .
90
+ // and notifies the downstream processing goroutine of the quota reset .
74
91
func (th * Throttler ) resetQuotaCounterLoop () {
75
92
ticker := time .NewTicker (th .period )
76
93
defer ticker .Stop ()
77
94
78
95
for {
79
96
select {
80
97
case <- ticker .C :
81
- atomic . StoreInt64 ( & th .counter , 0 )
98
+ th .counter . Store ( 0 )
82
99
th .notifyQuotaReset () // send quota reset
83
100
84
101
case <- th .done :
@@ -95,8 +112,8 @@ func (th *Throttler) notifyQuotaReset() {
95
112
}
96
113
}
97
114
98
- // buffer starts buffering incoming elements.
99
- // If an unsupported ThrottleMode was specified, buffer will panic .
115
+ // buffer buffers incoming elements from the in channel by sending them
116
+ // to the out channel, adhering to the configured ThrottleMode .
100
117
func (th * Throttler ) buffer () {
101
118
switch th .mode {
102
119
case Discard :
@@ -110,8 +127,6 @@ func (th *Throttler) buffer() {
110
127
for element := range th .in {
111
128
th .out <- element
112
129
}
113
- default :
114
- panic (fmt .Sprintf ("Unsupported ThrottleMode: %d" , th .mode ))
115
130
}
116
131
close (th .out )
117
132
}
@@ -139,15 +154,15 @@ func (th *Throttler) In() chan<- any {
139
154
return th .in
140
155
}
141
156
142
- // streamPortioned streams elements to the next Inlet.
143
- // Subsequent processing of elements will be suspended when the quota limit is reached
144
- // until the next quota reset event .
157
+ // streamPortioned streams elements to the given Inlet, enforcing a quota .
158
+ // Elements are sent to inlet.In() until th.out is closed. If the quota is exceeded,
159
+ // the function blocks until a quota reset signal is received on th.quotaSignal .
145
160
func (th * Throttler ) streamPortioned (inlet streams.Inlet ) {
146
161
for element := range th .out {
147
162
if th .quotaExceeded () {
148
163
<- th .quotaSignal // wait for quota reset
149
164
}
150
- atomic . AddInt64 ( & th .counter , 1 )
165
+ th .counter . Add ( 1 )
151
166
inlet .In () <- element
152
167
}
153
168
close (th .done )
0 commit comments