mirror of
https://github.com/crazybber/go-pattern-examples.git
synced 2024-11-25 21:26:03 +03:00
add batcher pattern basic code
This commit is contained in:
parent
76dfaf7a59
commit
c3aebef2ff
31
gomore/batcher/README.md
Normal file
31
gomore/batcher/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
batcher
|
||||||
|
=======
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/batcher?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/batcher)
|
||||||
|
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||||
|
|
||||||
|
The batching resiliency pattern for golang.
|
||||||
|
|
||||||
|
Creating a batcher takes two parameters:
|
||||||
|
- the timeout to wait while collecting a batch
|
||||||
|
- the function to run once a batch has been collected
|
||||||
|
|
||||||
|
You can also optionally set a prefilter to fail queries before they enter the
|
||||||
|
batch.
|
||||||
|
|
||||||
|
```go
|
||||||
|
b := batcher.New(10*time.Millisecond, func(params []interface{}) error {
|
||||||
|
// do something with the batch of parameters
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Prefilter(func(param interface{}) error {
|
||||||
|
// do some sort of sanity check on the parameter, and return an error if it fails
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go b.Run(i)
|
||||||
|
}
|
||||||
|
```
|
108
gomore/batcher/batcher.go
Normal file
108
gomore/batcher/batcher.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Package batcher implements the batching resiliency pattern for Go.
|
||||||
|
package batcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type work struct {
|
||||||
|
param interface{}
|
||||||
|
future chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batcher implements the batching resiliency pattern
|
||||||
|
type Batcher struct {
|
||||||
|
timeout time.Duration
|
||||||
|
prefilter func(interface{}) error
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
submit chan *work
|
||||||
|
doWork func([]interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New constructs a new batcher that will batch all calls to Run that occur within
|
||||||
|
// `timeout` time before calling doWork just once for the entire batch. The doWork
|
||||||
|
// function must be safe to run concurrently with itself as this may occur, especially
|
||||||
|
// when the timeout is small.
|
||||||
|
func New(timeout time.Duration, doWork func([]interface{}) error) *Batcher {
|
||||||
|
return &Batcher{
|
||||||
|
timeout: timeout,
|
||||||
|
doWork: doWork,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the work function with the given parameter, possibly
|
||||||
|
// including it in a batch with other calls to Run that occur within the
|
||||||
|
// specified timeout. It is safe to call Run concurrently on the same batcher.
|
||||||
|
func (b *Batcher) Run(param interface{}) error {
|
||||||
|
if b.prefilter != nil {
|
||||||
|
if err := b.prefilter(param); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.timeout == 0 {
|
||||||
|
return b.doWork([]interface{}{param})
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &work{
|
||||||
|
param: param,
|
||||||
|
future: make(chan error, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
b.submitWork(w)
|
||||||
|
|
||||||
|
return <-w.future
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefilter specifies an optional function that can be used to run initial checks on parameters
|
||||||
|
// passed to Run before being added to the batch. If the prefilter returns a non-nil error,
|
||||||
|
// that error is returned immediately from Run and the batcher is not invoked. A prefilter
|
||||||
|
// cannot safely be specified for a batcher if Run has already been invoked. The filter function
|
||||||
|
// specified must be concurrency-safe.
|
||||||
|
func (b *Batcher) Prefilter(filter func(interface{}) error) {
|
||||||
|
b.prefilter = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batcher) submitWork(w *work) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
if b.submit == nil {
|
||||||
|
b.submit = make(chan *work, 4)
|
||||||
|
go b.batch()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.submit <- w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batcher) batch() {
|
||||||
|
var params []interface{}
|
||||||
|
var futures []chan error
|
||||||
|
input := b.submit
|
||||||
|
|
||||||
|
go b.timer()
|
||||||
|
|
||||||
|
for work := range input {
|
||||||
|
params = append(params, work.param)
|
||||||
|
futures = append(futures, work.future)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := b.doWork(params)
|
||||||
|
|
||||||
|
for _, future := range futures {
|
||||||
|
future <- ret
|
||||||
|
close(future)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Batcher) timer() {
|
||||||
|
time.Sleep(b.timeout)
|
||||||
|
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
close(b.submit)
|
||||||
|
b.submit = nil
|
||||||
|
}
|
123
gomore/batcher/batcher_test.go
Normal file
123
gomore/batcher/batcher_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package batcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errSomeError = errors.New("errSomeError")
|
||||||
|
|
||||||
|
func returnsError(params []interface{}) error {
|
||||||
|
return errSomeError
|
||||||
|
}
|
||||||
|
|
||||||
|
func returnsSuccess(params []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatcherSuccess(t *testing.T) {
|
||||||
|
b := New(10*time.Millisecond, returnsSuccess)
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
if err := b.Run(nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
b = New(0, returnsSuccess)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if err := b.Run(nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatcherError(t *testing.T) {
|
||||||
|
b := New(10*time.Millisecond, returnsError)
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
if err := b.Run(nil); err != errSomeError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatcherPrefilter(t *testing.T) {
|
||||||
|
b := New(1*time.Millisecond, returnsSuccess)
|
||||||
|
|
||||||
|
b.Prefilter(func(param interface{}) error {
|
||||||
|
if param == nil {
|
||||||
|
return errSomeError
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := b.Run(nil); err != errSomeError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.Run(1); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatcherMultipleBatches(t *testing.T) {
|
||||||
|
var iters uint32
|
||||||
|
|
||||||
|
b := New(10*time.Millisecond, func(params []interface{}) error {
|
||||||
|
atomic.AddUint32(&iters, 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
for group := 0; group < 5; group++ {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
if err := b.Run(nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
time.Sleep(15 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if iters != 5 {
|
||||||
|
t.Error("Wrong number of iters:", iters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleBatcher() {
|
||||||
|
b := New(10*time.Millisecond, func(params []interface{}) error {
|
||||||
|
// do something with the batch of parameters
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Prefilter(func(param interface{}) error {
|
||||||
|
// do some sort of sanity check on the parameter, and return an error if it fails
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go b.Run(i)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user