@@ -16,16 +16,17 @@ type Batcher struct {
16
16
timeout time.Duration
17
17
prefilter func (interface {}) error
18
18
19
- lock sync.Mutex
20
- submit chan * work
21
- doWork func ([]interface {}) error
22
- done chan bool
19
+ lock sync.Mutex
20
+ submit chan * work
21
+ doWork func ([]interface {}) error
22
+ batchCounter sync.WaitGroup
23
+ flushTimer * time.Timer
23
24
}
24
25
25
26
// New constructs a new batcher that will batch all calls to Run that occur within
26
27
// `timeout` time before calling doWork just once for the entire batch. The doWork
27
28
// function must be safe to run concurrently with itself as this may occur, especially
28
- // when the timeout is small.
29
+ // when the doWork function is slow, or the timeout is small.
29
30
func New (timeout time.Duration , doWork func ([]interface {}) error ) * Batcher {
30
31
return & Batcher {
31
32
timeout : timeout ,
@@ -70,21 +71,23 @@ func (b *Batcher) submitWork(w *work) {
70
71
b .lock .Lock ()
71
72
defer b .lock .Unlock ()
72
73
74
+ // kick off a new batch if needed
73
75
if b .submit == nil {
74
- b .done = make ( chan bool )
76
+ b .batchCounter . Add ( 1 )
75
77
b .submit = make (chan * work , 4 )
76
- go b .batch ()
78
+ go b .batch (b .submit )
79
+ b .flushTimer = time .AfterFunc (b .timeout , b .flushCurrentBatch )
77
80
}
78
81
82
+ // then add this work to the current batch
79
83
b .submit <- w
80
84
}
81
85
82
- func (b * Batcher ) batch () {
86
+ func (b * Batcher ) batch (input <- chan * work ) {
87
+ defer b .batchCounter .Done ()
88
+
83
89
var params []interface {}
84
90
var futures []chan error
85
- input := b .submit
86
-
87
- go b .timer ()
88
91
89
92
for work := range input {
90
93
params = append (params , work .param )
@@ -97,37 +100,33 @@ func (b *Batcher) batch() {
97
100
future <- ret
98
101
close (future )
99
102
}
100
- close (b .done )
101
103
}
102
104
103
- func (b * Batcher ) timer () {
104
- time .Sleep (b .timeout )
105
-
106
- b .flush ()
107
- }
108
-
109
- // Shutdown flush the changes and wait to be saved
105
+ // Shutdown flushes and executes any pending batches. If wait is true, it also waits for the pending batches
106
+ // to finish executing before it returns. This can be used to avoid waiting for the timeout to expire when
107
+ // gracefully shutting down your application. Calling Run at any point after calling Shutdown will lead to
108
+ // undefined behaviour.
110
109
func (b * Batcher ) Shutdown (wait bool ) {
111
- b .flush ()
110
+ b .flushCurrentBatch ()
112
111
113
112
if wait {
114
- if b .done != nil {
115
- // wait done channel
116
- <- b .done
117
- }
113
+ b .batchCounter .Wait ()
118
114
}
119
115
}
120
116
121
- // Flush saves the changes before the timer expires.
122
- // It is useful to flush the changes when you shutdown your application
123
- func (b * Batcher ) flush () {
117
+ func (b * Batcher ) flushCurrentBatch () {
124
118
b .lock .Lock ()
125
119
defer b .lock .Unlock ()
126
120
127
121
if b .submit == nil {
128
122
return
129
123
}
130
124
125
+ // stop the timer to avoid spurious flushes and trigger immediate cleanup in case this flush was
126
+ // triggered manually by a call to Shutdown (it has to happen inside the lock, so it can't be done
127
+ // in the Shutdown method directly)
128
+ b .flushTimer .Stop ()
129
+
131
130
close (b .submit )
132
131
b .submit = nil
133
132
}
0 commit comments