Use a ring channel to avoid blocking write of events (#2082)
* Use a ring channel to avoid blocking write of events * Add eapache/channels dependency
This commit is contained in:
parent
33475b7184
commit
9bcb5b08ea
35 changed files with 2833 additions and 78 deletions
22
vendor/github.com/eapache/channels/.gitignore
generated
vendored
Normal file
22
vendor/github.com/eapache/channels/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
11
vendor/github.com/eapache/channels/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/eapache/channels/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
script: go test -v -race -timeout 10s ./...
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
17
vendor/github.com/eapache/channels/CHANGELOG.md
generated
vendored
Normal file
17
vendor/github.com/eapache/channels/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
#### Version 1.1.0 (2015-11-22)
|
||||
|
||||
Bug Fixes:
|
||||
- The `Len()` and `Cap()` methods on several implementations were racy
|
||||
([#18](https://github.com/eapache/channels/issues/18)).
|
||||
|
||||
Note: Fixing the above issue led to a fairly substantial performance hit
|
||||
(anywhere from 10-25% in benchmarks depending on use case) and involved fairly
|
||||
major refactoring, which is why this is being released as v1.1.0 instead
|
||||
of v1.0.1.
|
||||
|
||||
#### Version 1.0.0 (2015-01-24)
|
||||
|
||||
Version 1.0.0 is the first tagged release. All core functionality was available
|
||||
at this point.
|
||||
20
vendor/github.com/eapache/channels/LICENSE
generated
vendored
Normal file
20
vendor/github.com/eapache/channels/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Evan Huus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
27
vendor/github.com/eapache/channels/README.md
generated
vendored
Normal file
27
vendor/github.com/eapache/channels/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
channels
|
||||
========
|
||||
|
||||
[](https://travis-ci.org/eapache/channels)
|
||||
[](https://godoc.org/github.com/eapache/channels)
|
||||
[](https://eapache.github.io/conduct.html)
|
||||
|
||||
A collection of helper functions and special types for working with and
|
||||
extending [Go](https://golang.org/)'s existing channels. Due to limitations
|
||||
of Go's type system, importing this library directly is often not practical for
|
||||
production code. It serves equally well, however, as a reference guide and
|
||||
template for implementing many common idioms; if you use it in this way I would
|
||||
appreciate the inclusion of some sort of credit in the resulting code.
|
||||
|
||||
See https://godoc.org/github.com/eapache/channels for full documentation or
|
||||
https://gopkg.in/eapache/channels.v1 for a versioned import path.
|
||||
|
||||
Requires Go version 1.1 or later, as certain necessary elements of the `reflect`
|
||||
package were not present in 1.0.
|
||||
|
||||
Most of the buffered channel types in this package are backed by a very fast
|
||||
queue implementation that used to be built into this package but has now been
|
||||
extracted into its own package at https://github.com/eapache/queue.
|
||||
|
||||
*Note:* Several types in this package provide so-called "infinite" buffers. Be
|
||||
very careful using these, as no buffer is truly infinite. If such a buffer
|
||||
grows too large your program will run out of memory and crash. Caveat emptor.
|
||||
87
vendor/github.com/eapache/channels/batching_channel.go
generated
vendored
Normal file
87
vendor/github.com/eapache/channels/batching_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package channels
|
||||
|
||||
// BatchingChannel implements the Channel interface, with the change that instead of producing individual elements
|
||||
// on Out(), it batches together the entire internal buffer each time. Trying to construct an unbuffered batching channel
|
||||
// will panic, that configuration is not supported (and provides no benefit over an unbuffered NativeChannel).
|
||||
type BatchingChannel struct {
|
||||
input, output chan interface{}
|
||||
length chan int
|
||||
buffer []interface{}
|
||||
size BufferCap
|
||||
}
|
||||
|
||||
func NewBatchingChannel(size BufferCap) *BatchingChannel {
|
||||
if size == None {
|
||||
panic("channels: BatchingChannel does not support unbuffered behaviour")
|
||||
}
|
||||
if size < 0 && size != Infinity {
|
||||
panic("channels: invalid negative size in NewBatchingChannel")
|
||||
}
|
||||
ch := &BatchingChannel{
|
||||
input: make(chan interface{}),
|
||||
output: make(chan interface{}),
|
||||
length: make(chan int),
|
||||
size: size,
|
||||
}
|
||||
go ch.batchingBuffer()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *BatchingChannel) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
// Out returns a <-chan interface{} in order that BatchingChannel conforms to the standard Channel interface provided
|
||||
// by this package, however each output value is guaranteed to be of type []interface{} - a slice collecting the most
|
||||
// recent batch of values sent on the In channel. The slice is guaranteed to not be empty or nil. In practice the net
|
||||
// result is that you need an additional type assertion to access the underlying values.
|
||||
func (ch *BatchingChannel) Out() <-chan interface{} {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
func (ch *BatchingChannel) Len() int {
|
||||
return <-ch.length
|
||||
}
|
||||
|
||||
func (ch *BatchingChannel) Cap() BufferCap {
|
||||
return ch.size
|
||||
}
|
||||
|
||||
func (ch *BatchingChannel) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
func (ch *BatchingChannel) batchingBuffer() {
|
||||
var input, output, nextInput chan interface{}
|
||||
nextInput = ch.input
|
||||
input = nextInput
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if open {
|
||||
ch.buffer = append(ch.buffer, elem)
|
||||
} else {
|
||||
input = nil
|
||||
nextInput = nil
|
||||
}
|
||||
case output <- ch.buffer:
|
||||
ch.buffer = nil
|
||||
case ch.length <- len(ch.buffer):
|
||||
}
|
||||
|
||||
if len(ch.buffer) == 0 {
|
||||
input = nextInput
|
||||
output = nil
|
||||
} else if ch.size != Infinity && len(ch.buffer) >= int(ch.size) {
|
||||
input = nil
|
||||
output = ch.output
|
||||
} else {
|
||||
input = nextInput
|
||||
output = ch.output
|
||||
}
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
close(ch.length)
|
||||
}
|
||||
45
vendor/github.com/eapache/channels/batching_channel_test.go
generated
vendored
Normal file
45
vendor/github.com/eapache/channels/batching_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func testBatches(t *testing.T, ch Channel) {
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
}()
|
||||
|
||||
i := 0
|
||||
for val := range ch.Out() {
|
||||
for _, elem := range val.([]interface{}) {
|
||||
if i != elem.(int) {
|
||||
t.Fatal("batching channel expected", i, "but got", elem.(int))
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchingChannel(t *testing.T) {
|
||||
ch := NewBatchingChannel(Infinity)
|
||||
testBatches(t, ch)
|
||||
|
||||
ch = NewBatchingChannel(2)
|
||||
testBatches(t, ch)
|
||||
|
||||
ch = NewBatchingChannel(1)
|
||||
testChannelConcurrentAccessors(t, "batching channel", ch)
|
||||
}
|
||||
|
||||
func TestBatchingChannelCap(t *testing.T) {
|
||||
ch := NewBatchingChannel(Infinity)
|
||||
if ch.Cap() != Infinity {
|
||||
t.Error("incorrect capacity on infinite channel")
|
||||
}
|
||||
|
||||
ch = NewBatchingChannel(5)
|
||||
if ch.Cap() != 5 {
|
||||
t.Error("incorrect capacity on infinite channel")
|
||||
}
|
||||
}
|
||||
54
vendor/github.com/eapache/channels/black_hole.go
generated
vendored
Normal file
54
vendor/github.com/eapache/channels/black_hole.go
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package channels
|
||||
|
||||
// BlackHole implements the InChannel interface and provides an analogue for the "Discard" variable in
|
||||
// the ioutil package - it never blocks, and simply discards every value it reads. The number of items
|
||||
// discarded in this way is counted and returned from Len.
|
||||
type BlackHole struct {
|
||||
input chan interface{}
|
||||
length chan int
|
||||
count int
|
||||
}
|
||||
|
||||
func NewBlackHole() *BlackHole {
|
||||
ch := &BlackHole{
|
||||
input: make(chan interface{}),
|
||||
length: make(chan int),
|
||||
}
|
||||
go ch.discard()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *BlackHole) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *BlackHole) Len() int {
|
||||
val, open := <-ch.length
|
||||
if open {
|
||||
return val
|
||||
} else {
|
||||
return ch.count
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *BlackHole) Cap() BufferCap {
|
||||
return Infinity
|
||||
}
|
||||
|
||||
func (ch *BlackHole) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
func (ch *BlackHole) discard() {
|
||||
for {
|
||||
select {
|
||||
case _, open := <-ch.input:
|
||||
if !open {
|
||||
close(ch.length)
|
||||
return
|
||||
}
|
||||
ch.count++
|
||||
case ch.length <- ch.count:
|
||||
}
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/eapache/channels/black_hole_test.go
generated
vendored
Normal file
26
vendor/github.com/eapache/channels/black_hole_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBlackHole(t *testing.T) {
|
||||
discard := NewBlackHole()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
discard.In() <- i
|
||||
}
|
||||
|
||||
discard.Close()
|
||||
|
||||
if discard.Len() != 1000 {
|
||||
t.Error("blackhole expected 1000 was", discard.Len())
|
||||
}
|
||||
|
||||
// no asserts here, this is just for the race detector's benefit
|
||||
ch := NewBlackHole()
|
||||
go ch.Len()
|
||||
go ch.Cap()
|
||||
|
||||
go func() {
|
||||
ch.In() <- nil
|
||||
}()
|
||||
}
|
||||
277
vendor/github.com/eapache/channels/channels.go
generated
vendored
Normal file
277
vendor/github.com/eapache/channels/channels.go
generated
vendored
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
Package channels provides a collection of helper functions, interfaces and implementations for
|
||||
working with and extending the capabilities of golang's existing channels. The main interface of
|
||||
interest is Channel, though sub-interfaces are also provided for cases where the full Channel interface
|
||||
cannot be met (for example, InChannel for write-only channels).
|
||||
|
||||
For integration with native typed golang channels, functions Wrap and Unwrap are provided which do the
|
||||
appropriate type conversions. The NativeChannel, NativeInChannel and NativeOutChannel type definitions
|
||||
are also provided for use with native channels which already carry values of type interface{}.
|
||||
|
||||
The heart of the package consists of several distinct implementations of the Channel interface, including
|
||||
channels backed by special buffers (resizable, infinite, ring buffers, etc) and other useful types. A
|
||||
"black hole" channel for discarding unwanted values (similar in purpose to ioutil.Discard or /dev/null)
|
||||
rounds out the set.
|
||||
|
||||
Helper functions for operating on Channels include Pipe and Tee (which behave much like their Unix
|
||||
namesakes), as well as Multiplex and Distribute. "Weak" versions of these functions also exist, which
|
||||
do not close their output channel(s) on completion.
|
||||
|
||||
Due to limitations of Go's type system, importing this library directly is often not practical for
|
||||
production code. It serves equally well, however, as a reference guide and template for implementing
|
||||
many common idioms; if you use it in this way I would appreciate the inclusion of some sort of credit
|
||||
in the resulting code.
|
||||
|
||||
Warning: several types in this package provide so-called "infinite" buffers. Be *very* careful using
|
||||
these, as no buffer is truly infinite - if such a buffer grows too large your program will run out of
|
||||
memory and crash. Caveat emptor.
|
||||
*/
|
||||
package channels
|
||||
|
||||
import "reflect"
|
||||
|
||||
// BufferCap represents the capacity of the buffer backing a channel. Valid values consist of all
|
||||
// positive integers, as well as the special values below.
|
||||
type BufferCap int
|
||||
|
||||
const (
|
||||
// None is the capacity for channels that have no buffer at all.
|
||||
None BufferCap = 0
|
||||
// Infinity is the capacity for channels with no limit on their buffer size.
|
||||
Infinity BufferCap = -1
|
||||
)
|
||||
|
||||
// Buffer is an interface for any channel that provides access to query the state of its buffer.
|
||||
// Even unbuffered channels can implement this interface by simply returning 0 from Len() and None from Cap().
|
||||
type Buffer interface {
|
||||
Len() int // The number of elements currently buffered.
|
||||
Cap() BufferCap // The maximum number of elements that can be buffered.
|
||||
}
|
||||
|
||||
// SimpleInChannel is an interface representing a writeable channel that does not necessarily
|
||||
// implement the Buffer interface.
|
||||
type SimpleInChannel interface {
|
||||
In() chan<- interface{} // The writeable end of the channel.
|
||||
Close() // Closes the channel. It is an error to write to In() after calling Close().
|
||||
}
|
||||
|
||||
// InChannel is an interface representing a writeable channel with a buffer.
|
||||
type InChannel interface {
|
||||
SimpleInChannel
|
||||
Buffer
|
||||
}
|
||||
|
||||
// SimpleOutChannel is an interface representing a readable channel that does not necessarily
|
||||
// implement the Buffer interface.
|
||||
type SimpleOutChannel interface {
|
||||
Out() <-chan interface{} // The readable end of the channel.
|
||||
}
|
||||
|
||||
// OutChannel is an interface representing a readable channel implementing the Buffer interface.
|
||||
type OutChannel interface {
|
||||
SimpleOutChannel
|
||||
Buffer
|
||||
}
|
||||
|
||||
// SimpleChannel is an interface representing a channel that is both readable and writeable,
|
||||
// but does not necessarily implement the Buffer interface.
|
||||
type SimpleChannel interface {
|
||||
SimpleInChannel
|
||||
SimpleOutChannel
|
||||
}
|
||||
|
||||
// Channel is an interface representing a channel that is readable, writeable and implements
|
||||
// the Buffer interface
|
||||
type Channel interface {
|
||||
SimpleChannel
|
||||
Buffer
|
||||
}
|
||||
|
||||
func pipe(input SimpleOutChannel, output SimpleInChannel, closeWhenDone bool) {
|
||||
for elem := range input.Out() {
|
||||
output.In() <- elem
|
||||
}
|
||||
if closeWhenDone {
|
||||
output.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func multiplex(output SimpleInChannel, inputs []SimpleOutChannel, closeWhenDone bool) {
|
||||
inputCount := len(inputs)
|
||||
cases := make([]reflect.SelectCase, inputCount)
|
||||
for i := range cases {
|
||||
cases[i].Dir = reflect.SelectRecv
|
||||
cases[i].Chan = reflect.ValueOf(inputs[i].Out())
|
||||
}
|
||||
for inputCount > 0 {
|
||||
chosen, recv, recvOK := reflect.Select(cases)
|
||||
if recvOK {
|
||||
output.In() <- recv.Interface()
|
||||
} else {
|
||||
cases[chosen].Chan = reflect.ValueOf(nil)
|
||||
inputCount--
|
||||
}
|
||||
}
|
||||
if closeWhenDone {
|
||||
output.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func tee(input SimpleOutChannel, outputs []SimpleInChannel, closeWhenDone bool) {
|
||||
cases := make([]reflect.SelectCase, len(outputs))
|
||||
for i := range cases {
|
||||
cases[i].Dir = reflect.SelectSend
|
||||
}
|
||||
for elem := range input.Out() {
|
||||
for i := range cases {
|
||||
cases[i].Chan = reflect.ValueOf(outputs[i].In())
|
||||
cases[i].Send = reflect.ValueOf(elem)
|
||||
}
|
||||
for _ = range cases {
|
||||
chosen, _, _ := reflect.Select(cases)
|
||||
cases[chosen].Chan = reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
if closeWhenDone {
|
||||
for i := range outputs {
|
||||
outputs[i].Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func distribute(input SimpleOutChannel, outputs []SimpleInChannel, closeWhenDone bool) {
|
||||
cases := make([]reflect.SelectCase, len(outputs))
|
||||
for i := range cases {
|
||||
cases[i].Dir = reflect.SelectSend
|
||||
cases[i].Chan = reflect.ValueOf(outputs[i].In())
|
||||
}
|
||||
for elem := range input.Out() {
|
||||
for i := range cases {
|
||||
cases[i].Send = reflect.ValueOf(elem)
|
||||
}
|
||||
reflect.Select(cases)
|
||||
}
|
||||
if closeWhenDone {
|
||||
for i := range outputs {
|
||||
outputs[i].Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pipe connects the input channel to the output channel so that
|
||||
// they behave as if a single channel.
|
||||
func Pipe(input SimpleOutChannel, output SimpleInChannel) {
|
||||
go pipe(input, output, true)
|
||||
}
|
||||
|
||||
// Multiplex takes an arbitrary number of input channels and multiplexes their output into a single output
|
||||
// channel. When all input channels have been closed, the output channel is closed. Multiplex with a single
|
||||
// input channel is equivalent to Pipe (though slightly less efficient).
|
||||
func Multiplex(output SimpleInChannel, inputs ...SimpleOutChannel) {
|
||||
if len(inputs) == 0 {
|
||||
panic("channels: Multiplex requires at least one input")
|
||||
}
|
||||
go multiplex(output, inputs, true)
|
||||
}
|
||||
|
||||
// Tee (like its Unix namesake) takes a single input channel and an arbitrary number of output channels
|
||||
// and duplicates each input into every output. When the input channel is closed, all outputs channels are closed.
|
||||
// Tee with a single output channel is equivalent to Pipe (though slightly less efficient).
|
||||
func Tee(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||
if len(outputs) == 0 {
|
||||
panic("channels: Tee requires at least one output")
|
||||
}
|
||||
go tee(input, outputs, true)
|
||||
}
|
||||
|
||||
// Distribute takes a single input channel and an arbitrary number of output channels and duplicates each input
|
||||
// into *one* available output. If multiple outputs are waiting for a value, one is chosen at random. When the
|
||||
// input channel is closed, all outputs channels are closed. Distribute with a single output channel is
|
||||
// equivalent to Pipe (though slightly less efficient).
|
||||
func Distribute(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||
if len(outputs) == 0 {
|
||||
panic("channels: Distribute requires at least one output")
|
||||
}
|
||||
go distribute(input, outputs, true)
|
||||
}
|
||||
|
||||
// WeakPipe behaves like Pipe (connecting the two channels) except that it does not close
|
||||
// the output channel when the input channel is closed.
|
||||
func WeakPipe(input SimpleOutChannel, output SimpleInChannel) {
|
||||
go pipe(input, output, false)
|
||||
}
|
||||
|
||||
// WeakMultiplex behaves like Multiplex (multiplexing multiple inputs into a single output) except that it does not close
|
||||
// the output channel when the input channels are closed.
|
||||
func WeakMultiplex(output SimpleInChannel, inputs ...SimpleOutChannel) {
|
||||
if len(inputs) == 0 {
|
||||
panic("channels: WeakMultiplex requires at least one input")
|
||||
}
|
||||
go multiplex(output, inputs, false)
|
||||
}
|
||||
|
||||
// WeakTee behaves like Tee (duplicating a single input into multiple outputs) except that it does not close
|
||||
// the output channels when the input channel is closed.
|
||||
func WeakTee(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||
if len(outputs) == 0 {
|
||||
panic("channels: WeakTee requires at least one output")
|
||||
}
|
||||
go tee(input, outputs, false)
|
||||
}
|
||||
|
||||
// WeakDistribute behaves like Distribute (distributing a single input amongst multiple outputs) except that
|
||||
// it does not close the output channels when the input channel is closed.
|
||||
func WeakDistribute(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||
if len(outputs) == 0 {
|
||||
panic("channels: WeakDistribute requires at least one output")
|
||||
}
|
||||
go distribute(input, outputs, false)
|
||||
}
|
||||
|
||||
// Wrap takes any readable channel type (chan or <-chan but not chan<-) and
|
||||
// exposes it as a SimpleOutChannel for easy integration with existing channel sources.
|
||||
// It panics if the input is not a readable channel.
|
||||
func Wrap(ch interface{}) SimpleOutChannel {
|
||||
t := reflect.TypeOf(ch)
|
||||
if t.Kind() != reflect.Chan || t.ChanDir()&reflect.RecvDir == 0 {
|
||||
panic("channels: input to Wrap must be readable channel")
|
||||
}
|
||||
realChan := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
v := reflect.ValueOf(ch)
|
||||
for {
|
||||
x, ok := v.Recv()
|
||||
if !ok {
|
||||
close(realChan)
|
||||
return
|
||||
}
|
||||
realChan <- x.Interface()
|
||||
}
|
||||
}()
|
||||
|
||||
return NativeOutChannel(realChan)
|
||||
}
|
||||
|
||||
// Unwrap takes a SimpleOutChannel and uses reflection to pipe it to a typed native channel for
|
||||
// easy integration with existing channel sources. Output can be any writable channel type (chan or chan<-).
|
||||
// It panics if the output is not a writable channel, or if a value is received that cannot be sent on the
|
||||
// output channel.
|
||||
func Unwrap(input SimpleOutChannel, output interface{}) {
|
||||
t := reflect.TypeOf(output)
|
||||
if t.Kind() != reflect.Chan || t.ChanDir()&reflect.SendDir == 0 {
|
||||
panic("channels: input to Unwrap must be readable channel")
|
||||
}
|
||||
|
||||
go func() {
|
||||
v := reflect.ValueOf(output)
|
||||
for {
|
||||
x, ok := <-input.Out()
|
||||
if !ok {
|
||||
v.Close()
|
||||
return
|
||||
}
|
||||
v.Send(reflect.ValueOf(x))
|
||||
}
|
||||
}()
|
||||
}
|
||||
265
vendor/github.com/eapache/channels/channels_test.go
generated
vendored
Normal file
265
vendor/github.com/eapache/channels/channels_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testChannel(t *testing.T, name string, ch Channel) {
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
val := <-ch.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal(name, "expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelPair(t *testing.T, name string, in InChannel, out OutChannel) {
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
in.In() <- i
|
||||
}
|
||||
in.Close()
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
val := <-out.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("pair", name, "expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelConcurrentAccessors(t *testing.T, name string, ch Channel) {
|
||||
// no asserts here, this is just for the race detector's benefit
|
||||
go ch.Len()
|
||||
go ch.Cap()
|
||||
|
||||
go func() {
|
||||
ch.In() <- nil
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-ch.Out()
|
||||
}()
|
||||
}
|
||||
|
||||
func TestPipe(t *testing.T) {
|
||||
a := NewNativeChannel(None)
|
||||
b := NewNativeChannel(None)
|
||||
|
||||
Pipe(a, b)
|
||||
|
||||
testChannelPair(t, "pipe", a, b)
|
||||
}
|
||||
|
||||
func TestWeakPipe(t *testing.T) {
|
||||
a := NewNativeChannel(None)
|
||||
b := NewNativeChannel(None)
|
||||
|
||||
WeakPipe(a, b)
|
||||
|
||||
testChannelPair(t, "pipe", a, b)
|
||||
}
|
||||
|
||||
func testMultiplex(t *testing.T, multi func(output SimpleInChannel, inputs ...SimpleOutChannel)) {
|
||||
a := NewNativeChannel(None)
|
||||
b := NewNativeChannel(None)
|
||||
|
||||
multi(b, a)
|
||||
|
||||
testChannelPair(t, "simple multiplex", a, b)
|
||||
|
||||
a = NewNativeChannel(None)
|
||||
inputs := []Channel{
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
}
|
||||
|
||||
multi(a, inputs[0], inputs[1], inputs[2], inputs[3])
|
||||
|
||||
go func() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
for i := 0; i < 1000; i++ {
|
||||
inputs[rand.Intn(len(inputs))].In() <- i
|
||||
}
|
||||
for i := range inputs {
|
||||
inputs[i].Close()
|
||||
}
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
val := <-a.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("multiplexing expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiplex(t *testing.T) {
|
||||
testMultiplex(t, Multiplex)
|
||||
}
|
||||
|
||||
func TestWeakMultiplex(t *testing.T) {
|
||||
testMultiplex(t, WeakMultiplex)
|
||||
}
|
||||
|
||||
func testTee(t *testing.T, tee func(input SimpleOutChannel, outputs ...SimpleInChannel)) {
|
||||
a := NewNativeChannel(None)
|
||||
b := NewNativeChannel(None)
|
||||
|
||||
tee(a, b)
|
||||
|
||||
testChannelPair(t, "simple tee", a, b)
|
||||
|
||||
a = NewNativeChannel(None)
|
||||
outputs := []Channel{
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
}
|
||||
|
||||
tee(a, outputs[0], outputs[1], outputs[2], outputs[3])
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
a.In() <- i
|
||||
}
|
||||
a.Close()
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
for _, output := range outputs {
|
||||
val := <-output.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("teeing expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTee(t *testing.T) {
|
||||
testTee(t, Tee)
|
||||
}
|
||||
|
||||
func TestWeakTee(t *testing.T) {
|
||||
testTee(t, WeakTee)
|
||||
}
|
||||
|
||||
func testDistribute(t *testing.T, dist func(input SimpleOutChannel, outputs ...SimpleInChannel)) {
|
||||
a := NewNativeChannel(None)
|
||||
b := NewNativeChannel(None)
|
||||
|
||||
dist(a, b)
|
||||
|
||||
testChannelPair(t, "simple distribute", a, b)
|
||||
|
||||
a = NewNativeChannel(None)
|
||||
outputs := []Channel{
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
NewNativeChannel(None),
|
||||
}
|
||||
|
||||
dist(a, outputs[0], outputs[1], outputs[2], outputs[3])
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
a.In() <- i
|
||||
}
|
||||
a.Close()
|
||||
}()
|
||||
|
||||
received := make([]bool, 1000)
|
||||
for _ = range received {
|
||||
var val interface{}
|
||||
select {
|
||||
case val = <-outputs[0].Out():
|
||||
case val = <-outputs[1].Out():
|
||||
case val = <-outputs[2].Out():
|
||||
case val = <-outputs[3].Out():
|
||||
}
|
||||
if received[val.(int)] {
|
||||
t.Fatal("distribute got value twice", val.(int))
|
||||
}
|
||||
received[val.(int)] = true
|
||||
}
|
||||
for i := range received {
|
||||
if !received[i] {
|
||||
t.Fatal("distribute missed", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistribute(t *testing.T) {
|
||||
testDistribute(t, Distribute)
|
||||
}
|
||||
|
||||
func TestWeakDistribute(t *testing.T) {
|
||||
testDistribute(t, WeakDistribute)
|
||||
}
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
rawChan := make(chan int, 5)
|
||||
ch := Wrap(rawChan)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
rawChan <- i
|
||||
}
|
||||
close(rawChan)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
x := (<-ch.Out()).(int)
|
||||
if x != i {
|
||||
t.Error("Wrapped value", x, "was expecting", i)
|
||||
}
|
||||
}
|
||||
_, ok := <-ch.Out()
|
||||
if ok {
|
||||
t.Error("Wrapped channel didn't close")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnwrap(t *testing.T) {
|
||||
rawChan := make(chan int)
|
||||
ch := NewNativeChannel(5)
|
||||
Unwrap(ch, rawChan)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
x := <-rawChan
|
||||
if x != i {
|
||||
t.Error("Unwrapped value", x, "was expecting", i)
|
||||
}
|
||||
}
|
||||
_, ok := <-rawChan
|
||||
if ok {
|
||||
t.Error("Unwrapped channel didn't close")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleChannel() {
|
||||
var ch Channel
|
||||
|
||||
ch = NewInfiniteChannel()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
ch.In() <- nil
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch.Out()
|
||||
}
|
||||
}
|
||||
72
vendor/github.com/eapache/channels/infinite_channel.go
generated
vendored
Normal file
72
vendor/github.com/eapache/channels/infinite_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package channels
|
||||
|
||||
import "github.com/eapache/queue"
|
||||
|
||||
// InfiniteChannel implements the Channel interface with an infinite buffer between the input and the output.
|
||||
type InfiniteChannel struct {
|
||||
input, output chan interface{}
|
||||
length chan int
|
||||
buffer *queue.Queue
|
||||
}
|
||||
|
||||
func NewInfiniteChannel() *InfiniteChannel {
|
||||
ch := &InfiniteChannel{
|
||||
input: make(chan interface{}),
|
||||
output: make(chan interface{}),
|
||||
length: make(chan int),
|
||||
buffer: queue.New(),
|
||||
}
|
||||
go ch.infiniteBuffer()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) Out() <-chan interface{} {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) Len() int {
|
||||
return <-ch.length
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) Cap() BufferCap {
|
||||
return Infinity
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
func (ch *InfiniteChannel) infiniteBuffer() {
|
||||
var input, output chan interface{}
|
||||
var next interface{}
|
||||
input = ch.input
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if open {
|
||||
ch.buffer.Add(elem)
|
||||
} else {
|
||||
input = nil
|
||||
}
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
case ch.length <- ch.buffer.Length():
|
||||
}
|
||||
|
||||
if ch.buffer.Length() > 0 {
|
||||
output = ch.output
|
||||
next = ch.buffer.Peek()
|
||||
} else {
|
||||
output = nil
|
||||
next = nil
|
||||
}
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
close(ch.length)
|
||||
}
|
||||
48
vendor/github.com/eapache/channels/infinite_channel_test.go
generated
vendored
Normal file
48
vendor/github.com/eapache/channels/infinite_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInfiniteChannel(t *testing.T) {
|
||||
var ch Channel
|
||||
|
||||
ch = NewInfiniteChannel()
|
||||
testChannel(t, "infinite channel", ch)
|
||||
|
||||
ch = NewInfiniteChannel()
|
||||
testChannelPair(t, "infinite channel", ch, ch)
|
||||
|
||||
ch = NewInfiniteChannel()
|
||||
testChannelConcurrentAccessors(t, "infinite channel", ch)
|
||||
}
|
||||
|
||||
func BenchmarkInfiniteChannelSerial(b *testing.B) {
|
||||
ch := NewInfiniteChannel()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ch.In() <- nil
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-ch.Out()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInfiniteChannelParallel(b *testing.B) {
|
||||
ch := NewInfiniteChannel()
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-ch.Out()
|
||||
}
|
||||
ch.Close()
|
||||
}()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ch.In() <- nil
|
||||
}
|
||||
<-ch.Out()
|
||||
}
|
||||
|
||||
func BenchmarkInfiniteChannelTickTock(b *testing.B) {
|
||||
ch := NewInfiniteChannel()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ch.In() <- nil
|
||||
<-ch.Out()
|
||||
}
|
||||
}
|
||||
92
vendor/github.com/eapache/channels/native_channel.go
generated
vendored
Normal file
92
vendor/github.com/eapache/channels/native_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package channels
|
||||
|
||||
// NativeInChannel implements the InChannel interface by wrapping a native go write-only channel.
|
||||
type NativeInChannel chan<- interface{}
|
||||
|
||||
func (ch NativeInChannel) In() chan<- interface{} {
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch NativeInChannel) Len() int {
|
||||
return len(ch)
|
||||
}
|
||||
|
||||
func (ch NativeInChannel) Cap() BufferCap {
|
||||
return BufferCap(cap(ch))
|
||||
}
|
||||
|
||||
func (ch NativeInChannel) Close() {
|
||||
close(ch)
|
||||
}
|
||||
|
||||
// NativeOutChannel implements the OutChannel interface by wrapping a native go read-only channel.
|
||||
type NativeOutChannel <-chan interface{}
|
||||
|
||||
func (ch NativeOutChannel) Out() <-chan interface{} {
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch NativeOutChannel) Len() int {
|
||||
return len(ch)
|
||||
}
|
||||
|
||||
func (ch NativeOutChannel) Cap() BufferCap {
|
||||
return BufferCap(cap(ch))
|
||||
}
|
||||
|
||||
// NativeChannel implements the Channel interface by wrapping a native go channel.
|
||||
type NativeChannel chan interface{}
|
||||
|
||||
// NewNativeChannel makes a new NativeChannel with the given buffer size. Just a convenience wrapper
|
||||
// to avoid having to cast the result of make().
|
||||
func NewNativeChannel(size BufferCap) NativeChannel {
|
||||
return make(chan interface{}, size)
|
||||
}
|
||||
|
||||
func (ch NativeChannel) In() chan<- interface{} {
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch NativeChannel) Out() <-chan interface{} {
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch NativeChannel) Len() int {
|
||||
return len(ch)
|
||||
}
|
||||
|
||||
func (ch NativeChannel) Cap() BufferCap {
|
||||
return BufferCap(cap(ch))
|
||||
}
|
||||
|
||||
func (ch NativeChannel) Close() {
|
||||
close(ch)
|
||||
}
|
||||
|
||||
// DeadChannel is a placeholder implementation of the Channel interface with no buffer
|
||||
// that is never ready for reading or writing. Closing a dead channel is a no-op.
|
||||
// Behaves almost like NativeChannel(nil) except that closing a nil NativeChannel will panic.
|
||||
type DeadChannel struct{}
|
||||
|
||||
func NewDeadChannel() DeadChannel {
|
||||
return DeadChannel{}
|
||||
}
|
||||
|
||||
func (ch DeadChannel) In() chan<- interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch DeadChannel) Out() <-chan interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch DeadChannel) Len() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ch DeadChannel) Cap() BufferCap {
|
||||
return BufferCap(0)
|
||||
}
|
||||
|
||||
func (ch DeadChannel) Close() {
|
||||
}
|
||||
58
vendor/github.com/eapache/channels/native_channel_test.go
generated
vendored
Normal file
58
vendor/github.com/eapache/channels/native_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNativeChannels(t *testing.T) {
|
||||
var ch Channel
|
||||
|
||||
ch = NewNativeChannel(None)
|
||||
testChannel(t, "bufferless native channel", ch)
|
||||
|
||||
ch = NewNativeChannel(None)
|
||||
testChannelPair(t, "bufferless native channel", ch, ch)
|
||||
|
||||
ch = NewNativeChannel(5)
|
||||
testChannel(t, "5-buffer native channel", ch)
|
||||
|
||||
ch = NewNativeChannel(5)
|
||||
testChannelPair(t, "5-buffer native channel", ch, ch)
|
||||
|
||||
ch = NewNativeChannel(None)
|
||||
testChannelConcurrentAccessors(t, "native channel", ch)
|
||||
}
|
||||
|
||||
func TestNativeInOutChannels(t *testing.T) {
|
||||
ch1 := make(chan interface{})
|
||||
ch2 := make(chan interface{})
|
||||
|
||||
Pipe(NativeOutChannel(ch1), NativeInChannel(ch2))
|
||||
NativeInChannel(ch1).Close()
|
||||
}
|
||||
|
||||
func TestDeadChannel(t *testing.T) {
|
||||
ch := NewDeadChannel()
|
||||
|
||||
if ch.Len() != 0 {
|
||||
t.Error("dead channel length not 0")
|
||||
}
|
||||
if ch.Cap() != 0 {
|
||||
t.Error("dead channel cap not 0")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch.Out():
|
||||
t.Error("read from a dead channel")
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case ch.In() <- nil:
|
||||
t.Error("wrote to a dead channel")
|
||||
default:
|
||||
}
|
||||
|
||||
ch.Close()
|
||||
|
||||
ch = NewDeadChannel()
|
||||
testChannelConcurrentAccessors(t, "dead channel", ch)
|
||||
}
|
||||
113
vendor/github.com/eapache/channels/overflowing_channel.go
generated
vendored
Normal file
113
vendor/github.com/eapache/channels/overflowing_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package channels
|
||||
|
||||
import "github.com/eapache/queue"
|
||||
|
||||
// OverflowingChannel implements the Channel interface in a way that never blocks the writer.
|
||||
// Specifically, if a value is written to an OverflowingChannel when its buffer is full
|
||||
// (or, in an unbuffered case, when the recipient is not ready) then that value is simply discarded.
|
||||
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
|
||||
// the writer before the reader, so caveat emptor.
|
||||
// For the opposite behaviour (discarding the oldest element, not the newest) see RingChannel.
|
||||
type OverflowingChannel struct {
|
||||
input, output chan interface{}
|
||||
length chan int
|
||||
buffer *queue.Queue
|
||||
size BufferCap
|
||||
}
|
||||
|
||||
func NewOverflowingChannel(size BufferCap) *OverflowingChannel {
|
||||
if size < 0 && size != Infinity {
|
||||
panic("channels: invalid negative size in NewOverflowingChannel")
|
||||
}
|
||||
ch := &OverflowingChannel{
|
||||
input: make(chan interface{}),
|
||||
output: make(chan interface{}),
|
||||
length: make(chan int),
|
||||
size: size,
|
||||
}
|
||||
if size == None {
|
||||
go ch.overflowingDirect()
|
||||
} else {
|
||||
ch.buffer = queue.New()
|
||||
go ch.overflowingBuffer()
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *OverflowingChannel) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *OverflowingChannel) Out() <-chan interface{} {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
func (ch *OverflowingChannel) Len() int {
|
||||
if ch.size == None {
|
||||
return 0
|
||||
} else {
|
||||
return <-ch.length
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *OverflowingChannel) Cap() BufferCap {
|
||||
return ch.size
|
||||
}
|
||||
|
||||
func (ch *OverflowingChannel) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
// for entirely unbuffered cases
|
||||
func (ch *OverflowingChannel) overflowingDirect() {
|
||||
for elem := range ch.input {
|
||||
// if we can't write it immediately, drop it and move on
|
||||
select {
|
||||
case ch.output <- elem:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(ch.output)
|
||||
}
|
||||
|
||||
// for all buffered cases
|
||||
func (ch *OverflowingChannel) overflowingBuffer() {
|
||||
var input, output chan interface{}
|
||||
var next interface{}
|
||||
input = ch.input
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
// Prefer to write if possible, which is surprisingly effective in reducing
|
||||
// dropped elements due to overflow. The naive read/write select chooses randomly
|
||||
// when both channels are ready, which produces unnecessary drops 50% of the time.
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
default:
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if open {
|
||||
if ch.size == Infinity || ch.buffer.Length() < int(ch.size) {
|
||||
ch.buffer.Add(elem)
|
||||
}
|
||||
} else {
|
||||
input = nil
|
||||
}
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
case ch.length <- ch.buffer.Length():
|
||||
}
|
||||
}
|
||||
|
||||
if ch.buffer.Length() > 0 {
|
||||
output = ch.output
|
||||
next = ch.buffer.Peek()
|
||||
} else {
|
||||
output = nil
|
||||
next = nil
|
||||
}
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
close(ch.length)
|
||||
}
|
||||
49
vendor/github.com/eapache/channels/overflowing_channel_test.go
generated
vendored
Normal file
49
vendor/github.com/eapache/channels/overflowing_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOverflowingChannel(t *testing.T) {
|
||||
var ch Channel
|
||||
|
||||
ch = NewOverflowingChannel(Infinity) // yes this is rather silly, but it should work
|
||||
testChannel(t, "infinite overflowing channel", ch)
|
||||
|
||||
ch = NewOverflowingChannel(None)
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
}()
|
||||
prev := -1
|
||||
for i := range ch.Out() {
|
||||
if prev >= i.(int) {
|
||||
t.Fatal("overflowing channel prev", prev, "but got", i.(int))
|
||||
}
|
||||
}
|
||||
|
||||
ch = NewOverflowingChannel(10)
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
for i := 0; i < 10; i++ {
|
||||
val := <-ch.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("overflowing channel expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
if val, open := <-ch.Out(); open == true {
|
||||
t.Fatal("overflowing channel expected closed but got", val)
|
||||
}
|
||||
|
||||
ch = NewOverflowingChannel(None)
|
||||
ch.In() <- 0
|
||||
ch.Close()
|
||||
if val, open := <-ch.Out(); open == true {
|
||||
t.Fatal("overflowing channel expected closed but got", val)
|
||||
}
|
||||
|
||||
ch = NewOverflowingChannel(2)
|
||||
testChannelConcurrentAccessors(t, "overflowing channel", ch)
|
||||
}
|
||||
109
vendor/github.com/eapache/channels/resizable_channel.go
generated
vendored
Normal file
109
vendor/github.com/eapache/channels/resizable_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package channels
|
||||
|
||||
import "github.com/eapache/queue"
|
||||
|
||||
// ResizableChannel implements the Channel interface with a resizable buffer between the input and the output.
|
||||
// The channel initially has a buffer size of 1, but can be resized by calling Resize().
|
||||
//
|
||||
// Resizing to a buffer capacity of None is, unfortunately, not supported and will panic
|
||||
// (see https://github.com/eapache/channels/issues/1).
|
||||
// Resizing back and forth between a finite and infinite buffer is fully supported.
|
||||
type ResizableChannel struct {
|
||||
input, output chan interface{}
|
||||
length chan int
|
||||
capacity, resize chan BufferCap
|
||||
size BufferCap
|
||||
buffer *queue.Queue
|
||||
}
|
||||
|
||||
func NewResizableChannel() *ResizableChannel {
|
||||
ch := &ResizableChannel{
|
||||
input: make(chan interface{}),
|
||||
output: make(chan interface{}),
|
||||
length: make(chan int),
|
||||
capacity: make(chan BufferCap),
|
||||
resize: make(chan BufferCap),
|
||||
size: 1,
|
||||
buffer: queue.New(),
|
||||
}
|
||||
go ch.magicBuffer()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) Out() <-chan interface{} {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) Len() int {
|
||||
return <-ch.length
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) Cap() BufferCap {
|
||||
val, open := <-ch.capacity
|
||||
if open {
|
||||
return val
|
||||
} else {
|
||||
return ch.size
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) Resize(newSize BufferCap) {
|
||||
if newSize == None {
|
||||
panic("channels: ResizableChannel does not support unbuffered behaviour")
|
||||
}
|
||||
if newSize < 0 && newSize != Infinity {
|
||||
panic("channels: invalid negative size trying to resize channel")
|
||||
}
|
||||
ch.resize <- newSize
|
||||
}
|
||||
|
||||
func (ch *ResizableChannel) magicBuffer() {
|
||||
var input, output, nextInput chan interface{}
|
||||
var next interface{}
|
||||
nextInput = ch.input
|
||||
input = nextInput
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if open {
|
||||
ch.buffer.Add(elem)
|
||||
} else {
|
||||
input = nil
|
||||
nextInput = nil
|
||||
}
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
case ch.size = <-ch.resize:
|
||||
case ch.length <- ch.buffer.Length():
|
||||
case ch.capacity <- ch.size:
|
||||
}
|
||||
|
||||
if ch.buffer.Length() == 0 {
|
||||
output = nil
|
||||
next = nil
|
||||
} else {
|
||||
output = ch.output
|
||||
next = ch.buffer.Peek()
|
||||
}
|
||||
|
||||
if ch.size != Infinity && ch.buffer.Length() >= int(ch.size) {
|
||||
input = nil
|
||||
} else {
|
||||
input = nextInput
|
||||
}
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
close(ch.resize)
|
||||
close(ch.length)
|
||||
close(ch.capacity)
|
||||
}
|
||||
61
vendor/github.com/eapache/channels/resizable_channel_test.go
generated
vendored
Normal file
61
vendor/github.com/eapache/channels/resizable_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResizableChannel(t *testing.T) {
|
||||
var ch *ResizableChannel
|
||||
|
||||
ch = NewResizableChannel()
|
||||
testChannel(t, "default resizable channel", ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
testChannelPair(t, "default resizable channel", ch, ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
ch.Resize(Infinity)
|
||||
testChannel(t, "infinite resizable channel", ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
ch.Resize(Infinity)
|
||||
testChannelPair(t, "infinite resizable channel", ch, ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
ch.Resize(5)
|
||||
testChannel(t, "5-buffer resizable channel", ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
ch.Resize(5)
|
||||
testChannelPair(t, "5-buffer resizable channel", ch, ch)
|
||||
|
||||
ch = NewResizableChannel()
|
||||
testChannelConcurrentAccessors(t, "resizable channel", ch)
|
||||
}
|
||||
|
||||
func TestResizableChannelOnline(t *testing.T) {
|
||||
stopper := make(chan bool)
|
||||
ch := NewResizableChannel()
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
<-stopper
|
||||
ch.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.Resize(BufferCap(rand.Intn(50) + 1))
|
||||
}
|
||||
close(stopper)
|
||||
}()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
val := <-ch.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("resizable channel expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
114
vendor/github.com/eapache/channels/ring_channel.go
generated
vendored
Normal file
114
vendor/github.com/eapache/channels/ring_channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package channels
|
||||
|
||||
import "github.com/eapache/queue"
|
||||
|
||||
// RingChannel implements the Channel interface in a way that never blocks the writer.
|
||||
// Specifically, if a value is written to a RingChannel when its buffer is full then the oldest
|
||||
// value in the buffer is discarded to make room (just like a standard ring-buffer).
|
||||
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
|
||||
// the writer before the reader, so caveat emptor.
|
||||
// For the opposite behaviour (discarding the newest element, not the oldest) see OverflowingChannel.
|
||||
type RingChannel struct {
|
||||
input, output chan interface{}
|
||||
length chan int
|
||||
buffer *queue.Queue
|
||||
size BufferCap
|
||||
}
|
||||
|
||||
func NewRingChannel(size BufferCap) *RingChannel {
|
||||
if size < 0 && size != Infinity {
|
||||
panic("channels: invalid negative size in NewRingChannel")
|
||||
}
|
||||
ch := &RingChannel{
|
||||
input: make(chan interface{}),
|
||||
output: make(chan interface{}),
|
||||
buffer: queue.New(),
|
||||
size: size,
|
||||
}
|
||||
if size == None {
|
||||
go ch.overflowingDirect()
|
||||
} else {
|
||||
ch.length = make(chan int)
|
||||
go ch.ringBuffer()
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *RingChannel) In() chan<- interface{} {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *RingChannel) Out() <-chan interface{} {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
func (ch *RingChannel) Len() int {
|
||||
if ch.size == None {
|
||||
return 0
|
||||
} else {
|
||||
return <-ch.length
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *RingChannel) Cap() BufferCap {
|
||||
return ch.size
|
||||
}
|
||||
|
||||
func (ch *RingChannel) Close() {
|
||||
close(ch.input)
|
||||
}
|
||||
|
||||
// for entirely unbuffered cases
|
||||
func (ch *RingChannel) overflowingDirect() {
|
||||
for elem := range ch.input {
|
||||
// if we can't write it immediately, drop it and move on
|
||||
select {
|
||||
case ch.output <- elem:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(ch.output)
|
||||
}
|
||||
|
||||
// for all buffered cases
|
||||
func (ch *RingChannel) ringBuffer() {
|
||||
var input, output chan interface{}
|
||||
var next interface{}
|
||||
input = ch.input
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
// Prefer to write if possible, which is surprisingly effective in reducing
|
||||
// dropped elements due to overflow. The naive read/write select chooses randomly
|
||||
// when both channels are ready, which produces unnecessary drops 50% of the time.
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
default:
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if open {
|
||||
ch.buffer.Add(elem)
|
||||
if ch.size != Infinity && ch.buffer.Length() > int(ch.size) {
|
||||
ch.buffer.Remove()
|
||||
}
|
||||
} else {
|
||||
input = nil
|
||||
}
|
||||
case output <- next:
|
||||
ch.buffer.Remove()
|
||||
case ch.length <- ch.buffer.Length():
|
||||
}
|
||||
}
|
||||
|
||||
if ch.buffer.Length() > 0 {
|
||||
output = ch.output
|
||||
next = ch.buffer.Peek()
|
||||
} else {
|
||||
output = nil
|
||||
next = nil
|
||||
}
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
close(ch.length)
|
||||
}
|
||||
49
vendor/github.com/eapache/channels/ring_channel_test.go
generated
vendored
Normal file
49
vendor/github.com/eapache/channels/ring_channel_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRingChannel(t *testing.T) {
|
||||
var ch Channel
|
||||
|
||||
ch = NewRingChannel(Infinity) // yes this is rather silly, but it should work
|
||||
testChannel(t, "infinite ring-buffer channel", ch)
|
||||
|
||||
ch = NewRingChannel(None)
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
}()
|
||||
prev := -1
|
||||
for i := range ch.Out() {
|
||||
if prev >= i.(int) {
|
||||
t.Fatal("ring channel prev", prev, "but got", i.(int))
|
||||
}
|
||||
}
|
||||
|
||||
ch = NewRingChannel(10)
|
||||
for i := 0; i < 1000; i++ {
|
||||
ch.In() <- i
|
||||
}
|
||||
ch.Close()
|
||||
for i := 990; i < 1000; i++ {
|
||||
val := <-ch.Out()
|
||||
if i != val.(int) {
|
||||
t.Fatal("ring channel expected", i, "but got", val.(int))
|
||||
}
|
||||
}
|
||||
if val, open := <-ch.Out(); open == true {
|
||||
t.Fatal("ring channel expected closed but got", val)
|
||||
}
|
||||
|
||||
ch = NewRingChannel(None)
|
||||
ch.In() <- 0
|
||||
ch.Close()
|
||||
if val, open := <-ch.Out(); open == true {
|
||||
t.Fatal("ring channel expected closed but got", val)
|
||||
}
|
||||
|
||||
ch = NewRingChannel(2)
|
||||
testChannelConcurrentAccessors(t, "ring channel", ch)
|
||||
}
|
||||
167
vendor/github.com/eapache/channels/shared_buffer.go
generated
vendored
Normal file
167
vendor/github.com/eapache/channels/shared_buffer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/eapache/queue"
|
||||
)
|
||||
|
||||
//sharedBufferChannel implements SimpleChannel and is created by the public
|
||||
//SharedBuffer type below
|
||||
type sharedBufferChannel struct {
|
||||
in chan interface{}
|
||||
out chan interface{}
|
||||
buf *queue.Queue
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (sch *sharedBufferChannel) In() chan<- interface{} {
|
||||
return sch.in
|
||||
}
|
||||
|
||||
func (sch *sharedBufferChannel) Out() <-chan interface{} {
|
||||
return sch.out
|
||||
}
|
||||
|
||||
func (sch *sharedBufferChannel) Close() {
|
||||
close(sch.in)
|
||||
}
|
||||
|
||||
//SharedBuffer implements the Buffer interface, and permits multiple SimpleChannel instances to "share" a single buffer.
|
||||
//Each channel spawned by NewChannel has its own internal queue (so values flowing through do not get mixed up with
|
||||
//other channels) but the total number of elements buffered by all spawned channels is limited to a single capacity. This
|
||||
//means *all* such channels block and unblock for writing together. The primary use case is for implementing pipeline-style
|
||||
//parallelism with goroutines, limiting the total number of elements in the pipeline without limiting the number of elements
|
||||
//at any particular step.
|
||||
type SharedBuffer struct {
|
||||
cases []reflect.SelectCase // 2n+1 of these; [0] is for control, [1,3,5...] for recv, [2,4,6...] for send
|
||||
chans []*sharedBufferChannel // n of these
|
||||
count int
|
||||
size BufferCap
|
||||
in chan *sharedBufferChannel
|
||||
}
|
||||
|
||||
func NewSharedBuffer(size BufferCap) *SharedBuffer {
|
||||
if size < 0 && size != Infinity {
|
||||
panic("channels: invalid negative size in NewSharedBuffer")
|
||||
} else if size == None {
|
||||
panic("channels: SharedBuffer does not support unbuffered behaviour")
|
||||
}
|
||||
|
||||
buf := &SharedBuffer{
|
||||
size: size,
|
||||
in: make(chan *sharedBufferChannel),
|
||||
}
|
||||
|
||||
buf.cases = append(buf.cases, reflect.SelectCase{
|
||||
Dir: reflect.SelectRecv,
|
||||
Chan: reflect.ValueOf(buf.in),
|
||||
})
|
||||
|
||||
go buf.mainLoop()
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
//NewChannel spawns and returns a new channel sharing the underlying buffer.
|
||||
func (buf *SharedBuffer) NewChannel() SimpleChannel {
|
||||
ch := &sharedBufferChannel{
|
||||
in: make(chan interface{}),
|
||||
out: make(chan interface{}),
|
||||
buf: queue.New(),
|
||||
}
|
||||
buf.in <- ch
|
||||
return ch
|
||||
}
|
||||
|
||||
//Close shuts down the SharedBuffer. It is an error to call Close while channels are still using
|
||||
//the buffer (I'm not really sure what would happen if you do so).
|
||||
func (buf *SharedBuffer) Close() {
|
||||
// TODO: what if there are still active channels using this buffer?
|
||||
close(buf.in)
|
||||
}
|
||||
|
||||
func (buf *SharedBuffer) mainLoop() {
|
||||
for {
|
||||
i, val, ok := reflect.Select(buf.cases)
|
||||
|
||||
if i == 0 {
|
||||
if !ok {
|
||||
//Close was called on the SharedBuffer itself
|
||||
return
|
||||
}
|
||||
|
||||
//NewChannel was called on the SharedBuffer
|
||||
ch := val.Interface().(*sharedBufferChannel)
|
||||
buf.chans = append(buf.chans, ch)
|
||||
buf.cases = append(buf.cases,
|
||||
reflect.SelectCase{Dir: reflect.SelectRecv},
|
||||
reflect.SelectCase{Dir: reflect.SelectSend},
|
||||
)
|
||||
if buf.size == Infinity || buf.count < int(buf.size) {
|
||||
buf.cases[len(buf.cases)-2].Chan = reflect.ValueOf(ch.in)
|
||||
}
|
||||
} else if i%2 == 0 {
|
||||
//Send
|
||||
if buf.count == int(buf.size) {
|
||||
//room in the buffer again, re-enable all recv cases
|
||||
for j := range buf.chans {
|
||||
if !buf.chans[j].closed {
|
||||
buf.cases[(j*2)+1].Chan = reflect.ValueOf(buf.chans[j].in)
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.count--
|
||||
ch := buf.chans[(i-1)/2]
|
||||
if ch.buf.Length() > 0 {
|
||||
buf.cases[i].Send = reflect.ValueOf(ch.buf.Peek())
|
||||
ch.buf.Remove()
|
||||
} else {
|
||||
//nothing left for this channel to send, disable sending
|
||||
buf.cases[i].Chan = reflect.Value{}
|
||||
buf.cases[i].Send = reflect.Value{}
|
||||
if ch.closed {
|
||||
// and it was closed, so close the output channel
|
||||
//TODO: shrink slice
|
||||
close(ch.out)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ch := buf.chans[i/2]
|
||||
if ok {
|
||||
//Receive
|
||||
buf.count++
|
||||
if ch.buf.Length() == 0 && !buf.cases[i+1].Chan.IsValid() {
|
||||
//this channel now has something to send
|
||||
buf.cases[i+1].Chan = reflect.ValueOf(ch.out)
|
||||
buf.cases[i+1].Send = val
|
||||
} else {
|
||||
ch.buf.Add(val.Interface())
|
||||
}
|
||||
if buf.count == int(buf.size) {
|
||||
//buffer full, disable recv cases
|
||||
for j := range buf.chans {
|
||||
buf.cases[(j*2)+1].Chan = reflect.Value{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Close
|
||||
buf.cases[i].Chan = reflect.Value{}
|
||||
ch.closed = true
|
||||
if ch.buf.Length() == 0 && !buf.cases[i+1].Chan.IsValid() {
|
||||
//nothing pending, close the out channel right away
|
||||
//TODO: shrink slice
|
||||
close(ch.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (buf *SharedBuffer) Len() int {
|
||||
return buf.count
|
||||
}
|
||||
|
||||
func (buf *SharedBuffer) Cap() BufferCap {
|
||||
return buf.size
|
||||
}
|
||||
127
vendor/github.com/eapache/channels/shared_buffer_test.go
generated
vendored
Normal file
127
vendor/github.com/eapache/channels/shared_buffer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package channels
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSharedBufferSingleton(t *testing.T) {
|
||||
buf := NewSharedBuffer(3)
|
||||
|
||||
ch := buf.NewChannel()
|
||||
for i := 0; i < 5; i++ {
|
||||
ch.In() <- (*int)(nil)
|
||||
ch.In() <- (*int)(nil)
|
||||
ch.In() <- (*int)(nil)
|
||||
select {
|
||||
case ch.In() <- (*int)(nil):
|
||||
t.Error("Wrote to full shared-buffer")
|
||||
default:
|
||||
}
|
||||
|
||||
<-ch.Out()
|
||||
<-ch.Out()
|
||||
<-ch.Out()
|
||||
select {
|
||||
case <-ch.Out():
|
||||
t.Error("Read from empty shared-buffer")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
ch.Close()
|
||||
buf.Close()
|
||||
}
|
||||
|
||||
func TestSharedBufferMultiple(t *testing.T) {
|
||||
buf := NewSharedBuffer(3)
|
||||
|
||||
ch1 := buf.NewChannel()
|
||||
ch2 := buf.NewChannel()
|
||||
|
||||
ch1.In() <- (*int)(nil)
|
||||
ch1.In() <- (*int)(nil)
|
||||
ch1.In() <- (*int)(nil)
|
||||
|
||||
select {
|
||||
case ch2.In() <- (*int)(nil):
|
||||
t.Error("Wrote to full shared-buffer")
|
||||
case <-ch2.Out():
|
||||
t.Error("Read from empty channel")
|
||||
default:
|
||||
}
|
||||
|
||||
<-ch1.Out()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
ch2.In() <- (*int)(nil)
|
||||
|
||||
select {
|
||||
case ch1.In() <- (*int)(nil):
|
||||
t.Error("Wrote to full shared-buffer")
|
||||
case ch2.In() <- (*int)(nil):
|
||||
t.Error("Wrote to full shared-buffer")
|
||||
default:
|
||||
}
|
||||
|
||||
<-ch2.Out()
|
||||
}
|
||||
|
||||
<-ch1.Out()
|
||||
<-ch1.Out()
|
||||
|
||||
ch1.Close()
|
||||
ch2.Close()
|
||||
buf.Close()
|
||||
}
|
||||
|
||||
func TestSharedBufferConcurrent(t *testing.T) {
|
||||
const threads = 10
|
||||
const iters = 200
|
||||
|
||||
buf := NewSharedBuffer(3)
|
||||
done := make(chan bool)
|
||||
|
||||
for i := 0; i < threads; i++ {
|
||||
go func() {
|
||||
ch := buf.NewChannel()
|
||||
for i := 0; i < iters; i++ {
|
||||
ch.In() <- i
|
||||
val := <-ch.Out()
|
||||
if val.(int) != i {
|
||||
t.Error("Mismatched value out of channel")
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < threads; i++ {
|
||||
<-done
|
||||
}
|
||||
close(done)
|
||||
buf.Close()
|
||||
}
|
||||
|
||||
func ExampleSharedBuffer() {
|
||||
// never more than 3 elements in the pipeline at once
|
||||
buf := NewSharedBuffer(3)
|
||||
|
||||
ch1 := buf.NewChannel()
|
||||
ch2 := buf.NewChannel()
|
||||
|
||||
// or, instead of a straight pipe, implement your pipeline step
|
||||
Pipe(ch1, ch2)
|
||||
|
||||
// inputs
|
||||
go func() {
|
||||
for i := 0; i < 20; i++ {
|
||||
ch1.In() <- i
|
||||
}
|
||||
ch1.Close()
|
||||
}()
|
||||
|
||||
for _ = range ch2.Out() {
|
||||
// outputs
|
||||
}
|
||||
|
||||
buf.Close()
|
||||
}
|
||||
23
vendor/github.com/eapache/queue/.gitignore
generated
vendored
Normal file
23
vendor/github.com/eapache/queue/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
7
vendor/github.com/eapache/queue/.travis.yml
generated
vendored
Normal file
7
vendor/github.com/eapache/queue/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
21
vendor/github.com/eapache/queue/LICENSE
generated
vendored
Normal file
21
vendor/github.com/eapache/queue/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Evan Huus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
vendor/github.com/eapache/queue/README.md
generated
vendored
Normal file
16
vendor/github.com/eapache/queue/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Queue
|
||||
=====
|
||||
|
||||
[](https://travis-ci.org/eapache/queue)
|
||||
[](https://godoc.org/github.com/eapache/queue)
|
||||
[](https://eapache.github.io/conduct.html)
|
||||
|
||||
A fast Golang queue using a ring-buffer, based on the version suggested by Dariusz Górecki.
|
||||
Using this instead of other, simpler, queue implementations (slice+append or linked list) provides
|
||||
substantial memory and time benefits, and fewer GC pauses.
|
||||
|
||||
The queue implemented here is as fast as it is in part because it is *not* thread-safe.
|
||||
|
||||
Follows semantic versioning using https://gopkg.in/ - import from
|
||||
[`gopkg.in/eapache/queue.v1`](https://gopkg.in/eapache/queue.v1)
|
||||
for guaranteed API stability.
|
||||
102
vendor/github.com/eapache/queue/queue.go
generated
vendored
Normal file
102
vendor/github.com/eapache/queue/queue.go
generated
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki.
|
||||
Using this instead of other, simpler, queue implementations (slice+append or linked list) provides
|
||||
substantial memory and time benefits, and fewer GC pauses.
|
||||
|
||||
The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe.
|
||||
*/
|
||||
package queue
|
||||
|
||||
// minQueueLen is smallest capacity that queue may have.
|
||||
// Must be power of 2 for bitwise modulus: x % n == x & (n - 1).
|
||||
const minQueueLen = 16
|
||||
|
||||
// Queue represents a single instance of the queue data structure.
|
||||
type Queue struct {
|
||||
buf []interface{}
|
||||
head, tail, count int
|
||||
}
|
||||
|
||||
// New constructs and returns a new Queue.
|
||||
func New() *Queue {
|
||||
return &Queue{
|
||||
buf: make([]interface{}, minQueueLen),
|
||||
}
|
||||
}
|
||||
|
||||
// Length returns the number of elements currently stored in the queue.
|
||||
func (q *Queue) Length() int {
|
||||
return q.count
|
||||
}
|
||||
|
||||
// resizes the queue to fit exactly twice its current contents
|
||||
// this can result in shrinking if the queue is less than half-full
|
||||
func (q *Queue) resize() {
|
||||
newBuf := make([]interface{}, q.count<<1)
|
||||
|
||||
if q.tail > q.head {
|
||||
copy(newBuf, q.buf[q.head:q.tail])
|
||||
} else {
|
||||
n := copy(newBuf, q.buf[q.head:])
|
||||
copy(newBuf[n:], q.buf[:q.tail])
|
||||
}
|
||||
|
||||
q.head = 0
|
||||
q.tail = q.count
|
||||
q.buf = newBuf
|
||||
}
|
||||
|
||||
// Add puts an element on the end of the queue.
|
||||
func (q *Queue) Add(elem interface{}) {
|
||||
if q.count == len(q.buf) {
|
||||
q.resize()
|
||||
}
|
||||
|
||||
q.buf[q.tail] = elem
|
||||
// bitwise modulus
|
||||
q.tail = (q.tail + 1) & (len(q.buf) - 1)
|
||||
q.count++
|
||||
}
|
||||
|
||||
// Peek returns the element at the head of the queue. This call panics
|
||||
// if the queue is empty.
|
||||
func (q *Queue) Peek() interface{} {
|
||||
if q.count <= 0 {
|
||||
panic("queue: Peek() called on empty queue")
|
||||
}
|
||||
return q.buf[q.head]
|
||||
}
|
||||
|
||||
// Get returns the element at index i in the queue. If the index is
|
||||
// invalid, the call will panic. This method accepts both positive and
|
||||
// negative index values. Index 0 refers to the first element, and
|
||||
// index -1 refers to the last.
|
||||
func (q *Queue) Get(i int) interface{} {
|
||||
// If indexing backwards, convert to positive index.
|
||||
if i < 0 {
|
||||
i += q.count
|
||||
}
|
||||
if i < 0 || i >= q.count {
|
||||
panic("queue: Get() called with index out of range")
|
||||
}
|
||||
// bitwise modulus
|
||||
return q.buf[(q.head+i)&(len(q.buf)-1)]
|
||||
}
|
||||
|
||||
// Remove removes and returns the element from the front of the queue. If the
|
||||
// queue is empty, the call will panic.
|
||||
func (q *Queue) Remove() interface{} {
|
||||
if q.count <= 0 {
|
||||
panic("queue: Remove() called on empty queue")
|
||||
}
|
||||
ret := q.buf[q.head]
|
||||
q.buf[q.head] = nil
|
||||
// bitwise modulus
|
||||
q.head = (q.head + 1) & (len(q.buf) - 1)
|
||||
q.count--
|
||||
// Resize down if buffer 1/4 full.
|
||||
if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) {
|
||||
q.resize()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
178
vendor/github.com/eapache/queue/queue_test.go
generated
vendored
Normal file
178
vendor/github.com/eapache/queue/queue_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package queue
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQueueSimple(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
for i := 0; i < minQueueLen; i++ {
|
||||
q.Add(i)
|
||||
}
|
||||
for i := 0; i < minQueueLen; i++ {
|
||||
if q.Peek().(int) != i {
|
||||
t.Error("peek", i, "had value", q.Peek())
|
||||
}
|
||||
x := q.Remove()
|
||||
if x != i {
|
||||
t.Error("remove", i, "had value", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueWrapping(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
for i := 0; i < minQueueLen; i++ {
|
||||
q.Add(i)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
q.Remove()
|
||||
q.Add(minQueueLen + i)
|
||||
}
|
||||
|
||||
for i := 0; i < minQueueLen; i++ {
|
||||
if q.Peek().(int) != i+3 {
|
||||
t.Error("peek", i, "had value", q.Peek())
|
||||
}
|
||||
q.Remove()
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueLength(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
if q.Length() != 0 {
|
||||
t.Error("empty queue length not 0")
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
q.Add(i)
|
||||
if q.Length() != i+1 {
|
||||
t.Error("adding: queue with", i, "elements has length", q.Length())
|
||||
}
|
||||
}
|
||||
for i := 0; i < 1000; i++ {
|
||||
q.Remove()
|
||||
if q.Length() != 1000-i-1 {
|
||||
t.Error("removing: queue with", 1000-i-i, "elements has length", q.Length())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueGet(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
q.Add(i)
|
||||
for j := 0; j < q.Length(); j++ {
|
||||
if q.Get(j).(int) != j {
|
||||
t.Errorf("index %d doesn't contain %d", j, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueGetNegative(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
q.Add(i)
|
||||
for j := 1; j <= q.Length(); j++ {
|
||||
if q.Get(-j).(int) != q.Length()-j {
|
||||
t.Errorf("index %d doesn't contain %d", -j, q.Length()-j)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueGetOutOfRangePanics(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
q.Add(1)
|
||||
q.Add(2)
|
||||
q.Add(3)
|
||||
|
||||
assertPanics(t, "should panic when negative index", func() {
|
||||
q.Get(-4)
|
||||
})
|
||||
|
||||
assertPanics(t, "should panic when index greater than length", func() {
|
||||
q.Get(4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueuePeekOutOfRangePanics(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
assertPanics(t, "should panic when peeking empty queue", func() {
|
||||
q.Peek()
|
||||
})
|
||||
|
||||
q.Add(1)
|
||||
q.Remove()
|
||||
|
||||
assertPanics(t, "should panic when peeking emptied queue", func() {
|
||||
q.Peek()
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueueRemoveOutOfRangePanics(t *testing.T) {
|
||||
q := New()
|
||||
|
||||
assertPanics(t, "should panic when removing empty queue", func() {
|
||||
q.Remove()
|
||||
})
|
||||
|
||||
q.Add(1)
|
||||
q.Remove()
|
||||
|
||||
assertPanics(t, "should panic when removing emptied queue", func() {
|
||||
q.Remove()
|
||||
})
|
||||
}
|
||||
|
||||
func assertPanics(t *testing.T, name string, f func()) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("%s: didn't panic as expected", name)
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
// General warning: Go's benchmark utility (go test -bench .) increases the number of
|
||||
// iterations until the benchmarks take a reasonable amount of time to run; memory usage
|
||||
// is *NOT* considered. On my machine, these benchmarks hit around ~1GB before they've had
|
||||
// enough, but if you have less than that available and start swapping, then all bets are off.
|
||||
|
||||
func BenchmarkQueueSerial(b *testing.B) {
|
||||
q := New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Add(nil)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Peek()
|
||||
q.Remove()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQueueGet(b *testing.B) {
|
||||
q := New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Add(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQueueTickTock(b *testing.B) {
|
||||
q := New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Add(nil)
|
||||
q.Peek()
|
||||
q.Remove()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue