mirror of https://github.com/ethereum/go-ethereum
commit
df2b70853f
@ -0,0 +1,10 @@ |
|||||||
|
package ethchain |
||||||
|
|
||||||
|
type TxEvent struct { |
||||||
|
Type int // TxPre || TxPost
|
||||||
|
Tx *Transaction |
||||||
|
} |
||||||
|
|
||||||
|
type NewBlockEvent struct { |
||||||
|
Block *Block |
||||||
|
} |
@ -1,28 +0,0 @@ |
|||||||
## Reactor |
|
||||||
|
|
||||||
Reactor is the internal broadcast engine that allows components to be notified of ethereum stack events such as finding new blocks or change in state. |
|
||||||
Event notification is handled via subscription: |
|
||||||
|
|
||||||
var blockChan = make(chan ethreact.Event, 10) |
|
||||||
reactor.Subscribe("newBlock", blockChan) |
|
||||||
|
|
||||||
ethreact.Event broadcast on the channel are |
|
||||||
|
|
||||||
type Event struct { |
|
||||||
Resource interface{} |
|
||||||
Name string |
|
||||||
} |
|
||||||
|
|
||||||
Resource is polimorphic depending on the event type and should be typecast before use, e.g: |
|
||||||
|
|
||||||
b := <-blockChan: |
|
||||||
block := b.Resource.(*ethchain.Block) |
|
||||||
|
|
||||||
Events are guaranteed to be broadcast in order but the broadcast never blocks or leaks which means while the subscribing event channel is blocked (e.g., full if buffered) further messages will be skipped. |
|
||||||
|
|
||||||
The engine allows arbitrary events to be posted and subscribed to. |
|
||||||
|
|
||||||
ethereum.Reactor().Post("newBlock", newBlock) |
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,183 +0,0 @@ |
|||||||
package ethreact |
|
||||||
|
|
||||||
import ( |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/ethereum/eth-go/ethlog" |
|
||||||
) |
|
||||||
|
|
||||||
var logger = ethlog.NewLogger("REACTOR") |
|
||||||
|
|
||||||
const ( |
|
||||||
eventBufferSize int = 10 |
|
||||||
) |
|
||||||
|
|
||||||
type EventHandler struct { |
|
||||||
lock sync.RWMutex |
|
||||||
name string |
|
||||||
chans []chan Event |
|
||||||
} |
|
||||||
|
|
||||||
// Post the Event with the reactor resource on the channels
|
|
||||||
// currently subscribed to the event
|
|
||||||
func (e *EventHandler) Post(event Event) { |
|
||||||
e.lock.RLock() |
|
||||||
defer e.lock.RUnlock() |
|
||||||
|
|
||||||
// if we want to preserve order pushing to subscibed channels
|
|
||||||
// dispatching should be syncrounous
|
|
||||||
// this means if subscribed event channel is blocked
|
|
||||||
// the reactor dispatch will be blocked, so we need to mitigate by skipping
|
|
||||||
// rogue blocking subscribers
|
|
||||||
for i, ch := range e.chans { |
|
||||||
select { |
|
||||||
case ch <- event: |
|
||||||
default: |
|
||||||
logger.Debugf("subscribing channel %d to event %s blocked. skipping\n", i, event.Name) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Add a subscriber to this event
|
|
||||||
func (e *EventHandler) Add(ch chan Event) { |
|
||||||
e.lock.Lock() |
|
||||||
defer e.lock.Unlock() |
|
||||||
|
|
||||||
e.chans = append(e.chans, ch) |
|
||||||
} |
|
||||||
|
|
||||||
// Remove a subscriber
|
|
||||||
func (e *EventHandler) Remove(ch chan Event) int { |
|
||||||
e.lock.Lock() |
|
||||||
defer e.lock.Unlock() |
|
||||||
|
|
||||||
for i, c := range e.chans { |
|
||||||
if c == ch { |
|
||||||
e.chans = append(e.chans[:i], e.chans[i+1:]...) |
|
||||||
} |
|
||||||
} |
|
||||||
return len(e.chans) |
|
||||||
} |
|
||||||
|
|
||||||
// Basic reactor event
|
|
||||||
type Event struct { |
|
||||||
Resource interface{} |
|
||||||
Name string |
|
||||||
} |
|
||||||
|
|
||||||
// The reactor basic engine. Acts as bridge
|
|
||||||
// between the events and the subscribers/posters
|
|
||||||
type ReactorEngine struct { |
|
||||||
lock sync.RWMutex |
|
||||||
eventChannel chan Event |
|
||||||
eventHandlers map[string]*EventHandler |
|
||||||
quit chan chan error |
|
||||||
running bool |
|
||||||
drained chan bool |
|
||||||
} |
|
||||||
|
|
||||||
func New() *ReactorEngine { |
|
||||||
return &ReactorEngine{ |
|
||||||
eventHandlers: make(map[string]*EventHandler), |
|
||||||
eventChannel: make(chan Event, eventBufferSize), |
|
||||||
quit: make(chan chan error, 1), |
|
||||||
drained: make(chan bool, 1), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) Start() { |
|
||||||
reactor.lock.Lock() |
|
||||||
defer reactor.lock.Unlock() |
|
||||||
if !reactor.running { |
|
||||||
go func() { |
|
||||||
for { |
|
||||||
select { |
|
||||||
case status := <-reactor.quit: |
|
||||||
reactor.lock.Lock() |
|
||||||
defer reactor.lock.Unlock() |
|
||||||
reactor.running = false |
|
||||||
logger.Infoln("stopped") |
|
||||||
status <- nil |
|
||||||
return |
|
||||||
case event := <-reactor.eventChannel: |
|
||||||
// needs to be called syncronously to keep order of events
|
|
||||||
reactor.dispatch(event) |
|
||||||
default: |
|
||||||
reactor.drained <- true // blocking till message is coming in
|
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
reactor.running = true |
|
||||||
logger.Infoln("started") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) Stop() { |
|
||||||
if reactor.running { |
|
||||||
status := make(chan error) |
|
||||||
reactor.quit <- status |
|
||||||
select { |
|
||||||
case <-reactor.drained: |
|
||||||
default: |
|
||||||
} |
|
||||||
<-status |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) Flush() { |
|
||||||
<-reactor.drained |
|
||||||
} |
|
||||||
|
|
||||||
// Subscribe a channel to the specified event
|
|
||||||
func (reactor *ReactorEngine) Subscribe(event string, eventChannel chan Event) { |
|
||||||
reactor.lock.Lock() |
|
||||||
defer reactor.lock.Unlock() |
|
||||||
|
|
||||||
eventHandler := reactor.eventHandlers[event] |
|
||||||
// Create a new event handler if one isn't available
|
|
||||||
if eventHandler == nil { |
|
||||||
eventHandler = &EventHandler{name: event} |
|
||||||
reactor.eventHandlers[event] = eventHandler |
|
||||||
} |
|
||||||
// Add the events channel to reactor event handler
|
|
||||||
eventHandler.Add(eventChannel) |
|
||||||
logger.Debugf("added new subscription to %s", event) |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) Unsubscribe(event string, eventChannel chan Event) { |
|
||||||
reactor.lock.Lock() |
|
||||||
defer reactor.lock.Unlock() |
|
||||||
|
|
||||||
eventHandler := reactor.eventHandlers[event] |
|
||||||
if eventHandler != nil { |
|
||||||
len := eventHandler.Remove(eventChannel) |
|
||||||
if len == 0 { |
|
||||||
reactor.eventHandlers[event] = nil |
|
||||||
} |
|
||||||
logger.Debugf("removed subscription to %s", event) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) Post(event string, resource interface{}) { |
|
||||||
reactor.lock.Lock() |
|
||||||
defer reactor.lock.Unlock() |
|
||||||
|
|
||||||
if reactor.running { |
|
||||||
reactor.eventChannel <- Event{Resource: resource, Name: event} |
|
||||||
select { |
|
||||||
case <-reactor.drained: |
|
||||||
default: |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (reactor *ReactorEngine) dispatch(event Event) { |
|
||||||
name := event.Name |
|
||||||
eventHandler := reactor.eventHandlers[name] |
|
||||||
// if no subscriptions to this event type - no event handler created
|
|
||||||
// then noone to notify
|
|
||||||
if eventHandler != nil { |
|
||||||
// needs to be called syncronously
|
|
||||||
eventHandler.Post(event) |
|
||||||
} |
|
||||||
} |
|
@ -1,63 +0,0 @@ |
|||||||
package ethreact |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestReactorAdd(t *testing.T) { |
|
||||||
reactor := New() |
|
||||||
ch := make(chan Event) |
|
||||||
reactor.Subscribe("test", ch) |
|
||||||
if reactor.eventHandlers["test"] == nil { |
|
||||||
t.Error("Expected new eventHandler to be created") |
|
||||||
} |
|
||||||
reactor.Unsubscribe("test", ch) |
|
||||||
if reactor.eventHandlers["test"] != nil { |
|
||||||
t.Error("Expected eventHandler to be removed") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestReactorEvent(t *testing.T) { |
|
||||||
var name string |
|
||||||
reactor := New() |
|
||||||
// Buffer the channel, so it doesn't block for this test
|
|
||||||
cap := 20 |
|
||||||
ch := make(chan Event, cap) |
|
||||||
reactor.Subscribe("even", ch) |
|
||||||
reactor.Subscribe("odd", ch) |
|
||||||
reactor.Post("even", "disappears") // should not broadcast if engine not started
|
|
||||||
reactor.Start() |
|
||||||
for i := 0; i < cap; i++ { |
|
||||||
if i%2 == 0 { |
|
||||||
name = "even" |
|
||||||
} else { |
|
||||||
name = "odd" |
|
||||||
} |
|
||||||
reactor.Post(name, i) |
|
||||||
} |
|
||||||
reactor.Post("test", cap) // this should not block
|
|
||||||
i := 0 |
|
||||||
reactor.Flush() |
|
||||||
close(ch) |
|
||||||
for event := range ch { |
|
||||||
fmt.Printf("%d: %v", i, event) |
|
||||||
if i%2 == 0 { |
|
||||||
name = "even" |
|
||||||
} else { |
|
||||||
name = "odd" |
|
||||||
} |
|
||||||
if val, ok := event.Resource.(int); ok { |
|
||||||
if i != val || event.Name != name { |
|
||||||
t.Error("Expected event %d to be of type %s and resource %d, got ", i, name, i, val) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Error("Unable to cast") |
|
||||||
} |
|
||||||
i++ |
|
||||||
} |
|
||||||
if i != cap { |
|
||||||
t.Error("excpected exactly %d events, got ", i) |
|
||||||
} |
|
||||||
reactor.Stop() |
|
||||||
} |
|
@ -0,0 +1,183 @@ |
|||||||
|
// Package event implements an event multiplexer.
|
||||||
|
package event |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
// Subscription is implemented by event subscriptions.
|
||||||
|
type Subscription interface { |
||||||
|
// Chan returns a channel that carries events.
|
||||||
|
// Implementations should return the same channel
|
||||||
|
// for any subsequent calls to Chan.
|
||||||
|
Chan() <-chan interface{} |
||||||
|
|
||||||
|
// Unsubscribe stops delivery of events to a subscription.
|
||||||
|
// The event channel is closed.
|
||||||
|
// Unsubscribe can be called more than once.
|
||||||
|
Unsubscribe() |
||||||
|
} |
||||||
|
|
||||||
|
// A TypeMux dispatches events to registered receivers. Receivers can be
|
||||||
|
// registered to handle events of certain type. Any operation
|
||||||
|
// called after mux is stopped will return ErrMuxClosed.
|
||||||
|
//
|
||||||
|
// The zero value is ready to use.
|
||||||
|
type TypeMux struct { |
||||||
|
mutex sync.RWMutex |
||||||
|
subm map[reflect.Type][]*muxsub |
||||||
|
stopped bool |
||||||
|
} |
||||||
|
|
||||||
|
// ErrMuxClosed is returned when Posting on a closed TypeMux.
|
||||||
|
var ErrMuxClosed = errors.New("event: mux closed") |
||||||
|
|
||||||
|
// Subscribe creates a subscription for events of the given types. The
|
||||||
|
// subscription's channel is closed when it is unsubscribed
|
||||||
|
// or the mux is closed.
|
||||||
|
func (mux *TypeMux) Subscribe(types ...interface{}) Subscription { |
||||||
|
sub := newsub(mux) |
||||||
|
mux.mutex.Lock() |
||||||
|
defer mux.mutex.Unlock() |
||||||
|
if mux.stopped { |
||||||
|
close(sub.postC) |
||||||
|
} else { |
||||||
|
if mux.subm == nil { |
||||||
|
mux.subm = make(map[reflect.Type][]*muxsub) |
||||||
|
} |
||||||
|
for _, t := range types { |
||||||
|
rtyp := reflect.TypeOf(t) |
||||||
|
oldsubs := mux.subm[rtyp] |
||||||
|
if find(oldsubs, sub) != -1 { |
||||||
|
panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp)) |
||||||
|
} |
||||||
|
subs := make([]*muxsub, len(oldsubs)+1) |
||||||
|
copy(subs, oldsubs) |
||||||
|
subs[len(oldsubs)] = sub |
||||||
|
mux.subm[rtyp] = subs |
||||||
|
} |
||||||
|
} |
||||||
|
return sub |
||||||
|
} |
||||||
|
|
||||||
|
// Post sends an event to all receivers registered for the given type.
|
||||||
|
// It returns ErrMuxClosed if the mux has been stopped.
|
||||||
|
func (mux *TypeMux) Post(ev interface{}) error { |
||||||
|
rtyp := reflect.TypeOf(ev) |
||||||
|
mux.mutex.RLock() |
||||||
|
if mux.stopped { |
||||||
|
mux.mutex.RUnlock() |
||||||
|
return ErrMuxClosed |
||||||
|
} |
||||||
|
subs := mux.subm[rtyp] |
||||||
|
mux.mutex.RUnlock() |
||||||
|
for _, sub := range subs { |
||||||
|
sub.deliver(ev) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Stop closes a mux. The mux can no longer be used.
|
||||||
|
// Future Post calls will fail with ErrMuxClosed.
|
||||||
|
// Stop blocks until all current deliveries have finished.
|
||||||
|
func (mux *TypeMux) Stop() { |
||||||
|
mux.mutex.Lock() |
||||||
|
for _, subs := range mux.subm { |
||||||
|
for _, sub := range subs { |
||||||
|
sub.closewait() |
||||||
|
} |
||||||
|
} |
||||||
|
mux.subm = nil |
||||||
|
mux.stopped = true |
||||||
|
mux.mutex.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func (mux *TypeMux) del(s *muxsub) { |
||||||
|
mux.mutex.Lock() |
||||||
|
for typ, subs := range mux.subm { |
||||||
|
if pos := find(subs, s); pos >= 0 { |
||||||
|
if len(subs) == 1 { |
||||||
|
delete(mux.subm, typ) |
||||||
|
} else { |
||||||
|
mux.subm[typ] = posdelete(subs, pos) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
s.mux.mutex.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func find(slice []*muxsub, item *muxsub) int { |
||||||
|
for i, v := range slice { |
||||||
|
if v == item { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func posdelete(slice []*muxsub, pos int) []*muxsub { |
||||||
|
news := make([]*muxsub, len(slice)-1) |
||||||
|
copy(news[:pos], slice[:pos]) |
||||||
|
copy(news[pos:], slice[pos+1:]) |
||||||
|
return news |
||||||
|
} |
||||||
|
|
||||||
|
type muxsub struct { |
||||||
|
mux *TypeMux |
||||||
|
closeMu sync.Mutex |
||||||
|
closing chan struct{} |
||||||
|
closed bool |
||||||
|
|
||||||
|
// these two are the same channel. they are stored separately so
|
||||||
|
// postC can be set to nil without affecting the return value of
|
||||||
|
// Chan.
|
||||||
|
postMu sync.RWMutex |
||||||
|
readC <-chan interface{} |
||||||
|
postC chan<- interface{} |
||||||
|
} |
||||||
|
|
||||||
|
func newsub(mux *TypeMux) *muxsub { |
||||||
|
c := make(chan interface{}) |
||||||
|
return &muxsub{ |
||||||
|
mux: mux, |
||||||
|
readC: c, |
||||||
|
postC: c, |
||||||
|
closing: make(chan struct{}), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *muxsub) Chan() <-chan interface{} { |
||||||
|
return s.readC |
||||||
|
} |
||||||
|
|
||||||
|
func (s *muxsub) Unsubscribe() { |
||||||
|
s.mux.del(s) |
||||||
|
s.closewait() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *muxsub) closewait() { |
||||||
|
s.closeMu.Lock() |
||||||
|
defer s.closeMu.Unlock() |
||||||
|
if s.closed { |
||||||
|
return |
||||||
|
} |
||||||
|
close(s.closing) |
||||||
|
s.closed = true |
||||||
|
|
||||||
|
s.postMu.Lock() |
||||||
|
close(s.postC) |
||||||
|
s.postC = nil |
||||||
|
s.postMu.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *muxsub) deliver(ev interface{}) { |
||||||
|
s.postMu.RLock() |
||||||
|
select { |
||||||
|
case s.postC <- ev: |
||||||
|
case <-s.closing: |
||||||
|
} |
||||||
|
s.postMu.RUnlock() |
||||||
|
} |
@ -0,0 +1,176 @@ |
|||||||
|
package event |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/rand" |
||||||
|
"sync" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type testEvent int |
||||||
|
|
||||||
|
func TestSub(t *testing.T) { |
||||||
|
mux := new(TypeMux) |
||||||
|
defer mux.Stop() |
||||||
|
|
||||||
|
sub := mux.Subscribe(testEvent(0)) |
||||||
|
go func() { |
||||||
|
if err := mux.Post(testEvent(5)); err != nil { |
||||||
|
t.Errorf("Post returned unexpected error: %v", err) |
||||||
|
} |
||||||
|
}() |
||||||
|
ev := <-sub.Chan() |
||||||
|
|
||||||
|
if ev.(testEvent) != testEvent(5) { |
||||||
|
t.Errorf("Got %v (%T), expected event %v (%T)", |
||||||
|
ev, ev, testEvent(5), testEvent(5)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestMuxErrorAfterStop(t *testing.T) { |
||||||
|
mux := new(TypeMux) |
||||||
|
mux.Stop() |
||||||
|
|
||||||
|
sub := mux.Subscribe(testEvent(0)) |
||||||
|
if _, isopen := <-sub.Chan(); isopen { |
||||||
|
t.Errorf("subscription channel was not closed") |
||||||
|
} |
||||||
|
if err := mux.Post(testEvent(0)); err != ErrMuxClosed { |
||||||
|
t.Errorf("Post error mismatch, got: %s, expected: %s", err, ErrMuxClosed) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestUnsubscribeUnblockPost(t *testing.T) { |
||||||
|
mux := new(TypeMux) |
||||||
|
defer mux.Stop() |
||||||
|
|
||||||
|
sub := mux.Subscribe(testEvent(0)) |
||||||
|
unblocked := make(chan bool) |
||||||
|
go func() { |
||||||
|
mux.Post(testEvent(5)) |
||||||
|
unblocked <- true |
||||||
|
}() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-unblocked: |
||||||
|
t.Errorf("Post returned before Unsubscribe") |
||||||
|
default: |
||||||
|
sub.Unsubscribe() |
||||||
|
<-unblocked |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestSubscribeDuplicateType(t *testing.T) { |
||||||
|
mux := new(TypeMux) |
||||||
|
expected := "event: duplicate type event.testEvent in Subscribe" |
||||||
|
|
||||||
|
defer func() { |
||||||
|
err := recover() |
||||||
|
if err == nil { |
||||||
|
t.Errorf("Subscribe didn't panic for duplicate type") |
||||||
|
} else if err != expected { |
||||||
|
t.Errorf("panic mismatch: got %#v, expected %#v", err, expected) |
||||||
|
} |
||||||
|
}() |
||||||
|
mux.Subscribe(testEvent(1), testEvent(2)) |
||||||
|
} |
||||||
|
|
||||||
|
func TestMuxConcurrent(t *testing.T) { |
||||||
|
rand.Seed(time.Now().Unix()) |
||||||
|
mux := new(TypeMux) |
||||||
|
defer mux.Stop() |
||||||
|
|
||||||
|
recv := make(chan int) |
||||||
|
poster := func() { |
||||||
|
for { |
||||||
|
err := mux.Post(testEvent(0)) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
sub := func(i int) { |
||||||
|
time.Sleep(time.Duration(rand.Intn(99)) * time.Millisecond) |
||||||
|
sub := mux.Subscribe(testEvent(0)) |
||||||
|
<-sub.Chan() |
||||||
|
sub.Unsubscribe() |
||||||
|
recv <- i |
||||||
|
} |
||||||
|
|
||||||
|
go poster() |
||||||
|
go poster() |
||||||
|
go poster() |
||||||
|
nsubs := 1000 |
||||||
|
for i := 0; i < nsubs; i++ { |
||||||
|
go sub(i) |
||||||
|
} |
||||||
|
|
||||||
|
// wait until everyone has been served
|
||||||
|
counts := make(map[int]int, nsubs) |
||||||
|
for i := 0; i < nsubs; i++ { |
||||||
|
counts[<-recv]++ |
||||||
|
} |
||||||
|
for i, count := range counts { |
||||||
|
if count != 1 { |
||||||
|
t.Errorf("receiver %d called %d times, expected only 1 call", i, count) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func emptySubscriber(mux *TypeMux, types ...interface{}) { |
||||||
|
s := mux.Subscribe(testEvent(0)) |
||||||
|
go func() { |
||||||
|
for _ = range s.Chan() { |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
func BenchmarkPost3(b *testing.B) { |
||||||
|
var mux = new(TypeMux) |
||||||
|
defer mux.Stop() |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
mux.Post(testEvent(0)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func BenchmarkPostConcurrent(b *testing.B) { |
||||||
|
var mux = new(TypeMux) |
||||||
|
defer mux.Stop() |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
emptySubscriber(mux, testEvent(0)) |
||||||
|
|
||||||
|
var wg sync.WaitGroup |
||||||
|
poster := func() { |
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
mux.Post(testEvent(0)) |
||||||
|
} |
||||||
|
wg.Done() |
||||||
|
} |
||||||
|
wg.Add(5) |
||||||
|
for i := 0; i < 5; i++ { |
||||||
|
go poster() |
||||||
|
} |
||||||
|
wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
// for comparison
|
||||||
|
func BenchmarkChanSend(b *testing.B) { |
||||||
|
c := make(chan interface{}) |
||||||
|
closed := make(chan struct{}) |
||||||
|
go func() { |
||||||
|
for _ = range c { |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
select { |
||||||
|
case c <- i: |
||||||
|
case <-closed: |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package event |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
func ExampleTypeMux() { |
||||||
|
type someEvent struct{ I int } |
||||||
|
type otherEvent struct{ S string } |
||||||
|
type yetAnotherEvent struct{ X, Y int } |
||||||
|
|
||||||
|
var mux TypeMux |
||||||
|
|
||||||
|
// Start a subscriber.
|
||||||
|
done := make(chan struct{}) |
||||||
|
sub := mux.Subscribe(someEvent{}, otherEvent{}) |
||||||
|
go func() { |
||||||
|
for event := range sub.Chan() { |
||||||
|
fmt.Printf("Received: %#v\n", event) |
||||||
|
} |
||||||
|
fmt.Println("done") |
||||||
|
close(done) |
||||||
|
}() |
||||||
|
|
||||||
|
// Post some events.
|
||||||
|
mux.Post(someEvent{5}) |
||||||
|
mux.Post(yetAnotherEvent{X: 3, Y: 4}) |
||||||
|
mux.Post(someEvent{6}) |
||||||
|
mux.Post(otherEvent{"whoa"}) |
||||||
|
|
||||||
|
// Stop closes all subscription channels.
|
||||||
|
// The subscriber goroutine will print "done"
|
||||||
|
// and exit.
|
||||||
|
mux.Stop() |
||||||
|
|
||||||
|
// Wait for subscriber to return.
|
||||||
|
<-done |
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Received: event.someEvent{I:5}
|
||||||
|
// Received: event.someEvent{I:6}
|
||||||
|
// Received: event.otherEvent{S:"whoa"}
|
||||||
|
// done
|
||||||
|
} |
@ -1,83 +0,0 @@ |
|||||||
package eventer |
|
||||||
|
|
||||||
import "sync" |
|
||||||
|
|
||||||
// Basic receiver interface.
|
|
||||||
type Receiver interface { |
|
||||||
Send(Event) |
|
||||||
} |
|
||||||
|
|
||||||
// Receiver as channel
|
|
||||||
type Channel chan Event |
|
||||||
|
|
||||||
func (self Channel) Send(ev Event) { |
|
||||||
self <- ev |
|
||||||
} |
|
||||||
|
|
||||||
// Receiver as function
|
|
||||||
type Function func(ev Event) |
|
||||||
|
|
||||||
func (self Function) Send(ev Event) { |
|
||||||
self(ev) |
|
||||||
} |
|
||||||
|
|
||||||
type Event struct { |
|
||||||
Type string |
|
||||||
Data interface{} |
|
||||||
} |
|
||||||
|
|
||||||
type Channels map[string][]Receiver |
|
||||||
|
|
||||||
type EventMachine struct { |
|
||||||
mu sync.RWMutex |
|
||||||
channels Channels |
|
||||||
} |
|
||||||
|
|
||||||
func New() *EventMachine { |
|
||||||
return &EventMachine{channels: make(Channels)} |
|
||||||
} |
|
||||||
|
|
||||||
func (self *EventMachine) add(typ string, r Receiver) { |
|
||||||
self.mu.Lock() |
|
||||||
self.channels[typ] = append(self.channels[typ], r) |
|
||||||
self.mu.Unlock() |
|
||||||
} |
|
||||||
|
|
||||||
// Generalised methods for the known receiver types
|
|
||||||
// * Channel
|
|
||||||
// * Function
|
|
||||||
func (self *EventMachine) On(typ string, r interface{}) { |
|
||||||
if eventFunc, ok := r.(func(Event)); ok { |
|
||||||
self.RegisterFunc(typ, eventFunc) |
|
||||||
} else if eventChan, ok := r.(Channel); ok { |
|
||||||
self.RegisterChannel(typ, eventChan) |
|
||||||
} else { |
|
||||||
panic("Invalid type for EventMachine::On") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (self *EventMachine) RegisterChannel(typ string, c Channel) { |
|
||||||
self.add(typ, c) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *EventMachine) RegisterFunc(typ string, f Function) { |
|
||||||
self.add(typ, f) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *EventMachine) Register(typ string) Channel { |
|
||||||
c := make(Channel, 1) |
|
||||||
self.add(typ, c) |
|
||||||
return c |
|
||||||
} |
|
||||||
|
|
||||||
func (self *EventMachine) Post(typ string, data interface{}) { |
|
||||||
self.mu.RLock() |
|
||||||
if self.channels[typ] != nil { |
|
||||||
ev := Event{typ, data} |
|
||||||
for _, receiver := range self.channels[typ] { |
|
||||||
// Blocking is OK. These are internals and need to be handled
|
|
||||||
receiver.Send(ev) |
|
||||||
} |
|
||||||
} |
|
||||||
self.mu.RUnlock() |
|
||||||
} |
|
@ -1,113 +0,0 @@ |
|||||||
package eventer |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/rand" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func TestChannel(t *testing.T) { |
|
||||||
eventer := New() |
|
||||||
|
|
||||||
c := make(Channel, 1) |
|
||||||
eventer.RegisterChannel("test", c) |
|
||||||
eventer.Post("test", "hello world") |
|
||||||
|
|
||||||
res := <-c |
|
||||||
|
|
||||||
if res.Data.(string) != "hello world" { |
|
||||||
t.Error("Expected event with data 'hello world'. Got", res.Data) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestFunction(t *testing.T) { |
|
||||||
eventer := New() |
|
||||||
|
|
||||||
var data string |
|
||||||
eventer.RegisterFunc("test", func(ev Event) { |
|
||||||
data = ev.Data.(string) |
|
||||||
}) |
|
||||||
eventer.Post("test", "hello world") |
|
||||||
|
|
||||||
if data != "hello world" { |
|
||||||
t.Error("Expected event with data 'hello world'. Got", data) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestRegister(t *testing.T) { |
|
||||||
eventer := New() |
|
||||||
|
|
||||||
c := eventer.Register("test") |
|
||||||
eventer.Post("test", "hello world") |
|
||||||
|
|
||||||
res := <-c |
|
||||||
|
|
||||||
if res.Data.(string) != "hello world" { |
|
||||||
t.Error("Expected event with data 'hello world'. Got", res.Data) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestOn(t *testing.T) { |
|
||||||
eventer := New() |
|
||||||
|
|
||||||
c := make(Channel, 1) |
|
||||||
eventer.On("test", c) |
|
||||||
|
|
||||||
var data string |
|
||||||
eventer.On("test", func(ev Event) { |
|
||||||
data = ev.Data.(string) |
|
||||||
}) |
|
||||||
eventer.Post("test", "hello world") |
|
||||||
|
|
||||||
res := <-c |
|
||||||
if res.Data.(string) != "hello world" { |
|
||||||
t.Error("Expected channel event with data 'hello world'. Got", res.Data) |
|
||||||
} |
|
||||||
|
|
||||||
if data != "hello world" { |
|
||||||
t.Error("Expected function event with data 'hello world'. Got", data) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestConcurrentUsage(t *testing.T) { |
|
||||||
rand.Seed(time.Now().Unix()) |
|
||||||
eventer := New() |
|
||||||
stop := make(chan struct{}) |
|
||||||
recv := make(chan int) |
|
||||||
poster := func() { |
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-stop: |
|
||||||
return |
|
||||||
default: |
|
||||||
eventer.Post("test", "hi") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
listener := func(i int) { |
|
||||||
time.Sleep(time.Duration(rand.Intn(99)) * time.Millisecond) |
|
||||||
c := eventer.Register("test") |
|
||||||
// wait for the first event
|
|
||||||
<-c |
|
||||||
recv <- i |
|
||||||
// keep receiving to prevent deadlock
|
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-stop: |
|
||||||
return |
|
||||||
case <-c: |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
nlisteners := 200 |
|
||||||
go poster() |
|
||||||
for i := 0; i < nlisteners; i++ { |
|
||||||
go listener(i) |
|
||||||
} |
|
||||||
// wait until everyone has been served
|
|
||||||
for i := 0; i < nlisteners; i++ { |
|
||||||
<-recv |
|
||||||
} |
|
||||||
close(stop) |
|
||||||
} |
|
@ -0,0 +1,11 @@ |
|||||||
|
package eth |
||||||
|
|
||||||
|
import "container/list" |
||||||
|
|
||||||
|
type PeerListEvent struct { |
||||||
|
Peers *list.List |
||||||
|
} |
||||||
|
|
||||||
|
type ChainSyncEvent struct { |
||||||
|
InSync bool |
||||||
|
} |
Loading…
Reference in new issue