2015-07-09 15:20:12 +00:00
|
|
|
package lfs
|
|
|
|
|
2016-09-19 23:07:32 +00:00
|
|
|
import "sync/atomic"
|
2015-08-18 19:53:04 +00:00
|
|
|
|
2015-07-09 15:20:12 +00:00
|
|
|
// Batcher provides a way to process a set of items in groups of n. Items can
|
|
|
|
// be added to the batcher from multiple goroutines and pulled off in groups
|
|
|
|
// when one of the following conditions occurs:
|
|
|
|
// * The batch size is reached
|
2016-09-19 23:04:24 +00:00
|
|
|
// * Flush() is called, forcing the batch to be returned immediately, as-is
|
2015-07-09 15:20:12 +00:00
|
|
|
// * Exit() is called
|
2016-09-19 23:04:24 +00:00
|
|
|
// When an Exit() or Flush() occurs, the group may be smaller than the batch
|
2016-09-19 20:53:08 +00:00
|
|
|
// size.
|
2015-07-09 15:20:12 +00:00
|
|
|
type Batcher struct {
|
2015-09-04 20:01:48 +00:00
|
|
|
exited uint32
|
2015-07-09 15:20:12 +00:00
|
|
|
batchSize int
|
2016-05-18 15:54:54 +00:00
|
|
|
input chan interface{}
|
|
|
|
batchReady chan []interface{}
|
2016-09-19 23:04:24 +00:00
|
|
|
flush chan interface{}
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewBatcher creates a Batcher with the batchSize.
|
|
|
|
func NewBatcher(batchSize int) *Batcher {
|
|
|
|
b := &Batcher{
|
|
|
|
batchSize: batchSize,
|
2016-09-19 23:07:32 +00:00
|
|
|
input: make(chan interface{}),
|
2016-05-18 15:54:54 +00:00
|
|
|
batchReady: make(chan []interface{}),
|
2016-09-19 23:04:24 +00:00
|
|
|
flush: make(chan interface{}),
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 04:25:18 +00:00
|
|
|
go b.acceptInput()
|
2015-07-09 15:20:12 +00:00
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2016-09-19 23:12:32 +00:00
|
|
|
// Add adds one or more items to the batcher. Add is safe to call from multiple
|
|
|
|
// goroutines.
|
2016-09-19 21:07:11 +00:00
|
|
|
func (b *Batcher) Add(ts ...interface{}) {
|
2015-09-09 14:18:49 +00:00
|
|
|
if atomic.CompareAndSwapUint32(&b.exited, 1, 0) {
|
2016-09-19 23:07:32 +00:00
|
|
|
b.input = make(chan interface{})
|
2016-09-19 23:04:24 +00:00
|
|
|
b.flush = make(chan interface{})
|
2015-09-09 14:18:49 +00:00
|
|
|
go b.acceptInput()
|
|
|
|
}
|
|
|
|
|
2016-09-19 21:07:11 +00:00
|
|
|
for _, t := range ts {
|
|
|
|
b.input <- t
|
|
|
|
}
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Next will wait for the one of the above batch triggers to occur and return
|
|
|
|
// the accumulated batch.
|
2016-05-18 15:54:54 +00:00
|
|
|
func (b *Batcher) Next() []interface{} {
|
2015-07-09 15:20:12 +00:00
|
|
|
return <-b.batchReady
|
|
|
|
}
|
|
|
|
|
2016-09-19 23:04:24 +00:00
|
|
|
// Flush causes the current batch to halt accumulation and return
|
2016-09-19 20:50:16 +00:00
|
|
|
// immediately, even if it is smaller than the given batch size.
|
2016-09-19 23:04:24 +00:00
|
|
|
func (b *Batcher) Flush() {
|
|
|
|
b.flush <- struct{}{}
|
2016-09-19 20:50:16 +00:00
|
|
|
}
|
|
|
|
|
2015-09-09 15:16:24 +00:00
|
|
|
// Exit stops all batching and allows Next() to return. Calling Add() after
|
|
|
|
// calling Exit() will reset the batcher.
|
2015-07-09 15:20:12 +00:00
|
|
|
func (b *Batcher) Exit() {
|
2015-09-04 20:01:48 +00:00
|
|
|
atomic.StoreUint32(&b.exited, 1)
|
2015-07-09 15:20:12 +00:00
|
|
|
close(b.input)
|
2016-09-19 23:04:24 +00:00
|
|
|
close(b.flush)
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 04:25:18 +00:00
|
|
|
// acceptInput runs in its own goroutine and accepts input from external
|
2016-09-20 19:28:35 +00:00
|
|
|
// clients. Without flushing, the batch is filled completely in a sequential
|
|
|
|
// order, and then dispensed. If, while filling a batch, it is flushed part-way
|
|
|
|
// through, the batch will be dispensed with its current contents, and all
|
|
|
|
// subsequent Add()s will be placed in the next batch.
|
2015-08-28 04:25:18 +00:00
|
|
|
func (b *Batcher) acceptInput() {
|
2016-09-19 20:51:42 +00:00
|
|
|
var exit bool
|
2015-08-28 04:25:18 +00:00
|
|
|
|
|
|
|
for {
|
2016-05-18 15:54:54 +00:00
|
|
|
batch := make([]interface{}, 0, b.batchSize)
|
2016-09-19 20:51:23 +00:00
|
|
|
Acc:
|
2015-08-28 15:31:35 +00:00
|
|
|
for len(batch) < b.batchSize {
|
2016-09-19 20:50:16 +00:00
|
|
|
select {
|
|
|
|
case t, ok := <-b.input:
|
|
|
|
if !ok {
|
|
|
|
exit = true // input channel was closed by Exit()
|
2016-09-19 20:51:23 +00:00
|
|
|
break Acc
|
2016-09-19 20:50:16 +00:00
|
|
|
}
|
2016-09-19 23:02:44 +00:00
|
|
|
|
2016-09-19 20:50:16 +00:00
|
|
|
batch = append(batch, t)
|
2016-09-19 23:04:24 +00:00
|
|
|
case <-b.flush:
|
2016-09-19 20:51:23 +00:00
|
|
|
break Acc
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
2015-08-28 04:25:18 +00:00
|
|
|
}
|
2015-07-09 15:20:12 +00:00
|
|
|
|
2015-08-28 15:31:35 +00:00
|
|
|
b.batchReady <- batch
|
2015-07-09 15:20:12 +00:00
|
|
|
|
2015-08-28 04:25:18 +00:00
|
|
|
if exit {
|
|
|
|
return
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|
2015-08-28 04:25:18 +00:00
|
|
|
}
|
2015-07-09 15:20:12 +00:00
|
|
|
}
|