mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete event directory
This commit is contained in:
parent
dd461e5209
commit
0e5c09c795
14 changed files with 0 additions and 2469 deletions
217
event/event.go
217
event/event.go
|
|
@ -1,217 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package event deals with subscriptions to real-time events.
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TypeMuxEvent is a time-tagged notification pushed to subscribers.
|
|
||||||
type TypeMuxEvent struct {
|
|
||||||
Time time.Time
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Deprecated: use Feed
|
|
||||||
type TypeMux struct {
|
|
||||||
mutex sync.RWMutex
|
|
||||||
subm map[reflect.Type][]*TypeMuxSubscription
|
|
||||||
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{}) *TypeMuxSubscription {
|
|
||||||
sub := newsub(mux)
|
|
||||||
mux.mutex.Lock()
|
|
||||||
defer mux.mutex.Unlock()
|
|
||||||
if mux.stopped {
|
|
||||||
// set the status to closed so that calling Unsubscribe after this
|
|
||||||
// call will short circuit.
|
|
||||||
sub.closed = true
|
|
||||||
close(sub.postC)
|
|
||||||
} else {
|
|
||||||
if mux.subm == nil {
|
|
||||||
mux.subm = make(map[reflect.Type][]*TypeMuxSubscription, len(types))
|
|
||||||
}
|
|
||||||
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([]*TypeMuxSubscription, 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 {
|
|
||||||
event := &TypeMuxEvent{
|
|
||||||
Time: time.Now(),
|
|
||||||
Data: ev,
|
|
||||||
}
|
|
||||||
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(event)
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
defer mux.mutex.Unlock()
|
|
||||||
for _, subs := range mux.subm {
|
|
||||||
for _, sub := range subs {
|
|
||||||
sub.closewait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mux.subm = nil
|
|
||||||
mux.stopped = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mux *TypeMux) del(s *TypeMuxSubscription) {
|
|
||||||
mux.mutex.Lock()
|
|
||||||
defer mux.mutex.Unlock()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func find(slice []*TypeMuxSubscription, item *TypeMuxSubscription) int {
|
|
||||||
for i, v := range slice {
|
|
||||||
if v == item {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func posdelete(slice []*TypeMuxSubscription, pos int) []*TypeMuxSubscription {
|
|
||||||
news := make([]*TypeMuxSubscription, len(slice)-1)
|
|
||||||
copy(news[:pos], slice[:pos])
|
|
||||||
copy(news[pos:], slice[pos+1:])
|
|
||||||
return news
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeMuxSubscription is a subscription established through TypeMux.
|
|
||||||
type TypeMuxSubscription struct {
|
|
||||||
mux *TypeMux
|
|
||||||
created time.Time
|
|
||||||
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 *TypeMuxEvent
|
|
||||||
postC chan<- *TypeMuxEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func newsub(mux *TypeMux) *TypeMuxSubscription {
|
|
||||||
c := make(chan *TypeMuxEvent)
|
|
||||||
return &TypeMuxSubscription{
|
|
||||||
mux: mux,
|
|
||||||
created: time.Now(),
|
|
||||||
readC: c,
|
|
||||||
postC: c,
|
|
||||||
closing: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeMuxSubscription) Chan() <-chan *TypeMuxEvent {
|
|
||||||
return s.readC
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeMuxSubscription) Unsubscribe() {
|
|
||||||
s.mux.del(s)
|
|
||||||
s.closewait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeMuxSubscription) Closed() bool {
|
|
||||||
s.closeMu.Lock()
|
|
||||||
defer s.closeMu.Unlock()
|
|
||||||
return s.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeMuxSubscription) closewait() {
|
|
||||||
s.closeMu.Lock()
|
|
||||||
defer s.closeMu.Unlock()
|
|
||||||
if s.closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(s.closing)
|
|
||||||
s.closed = true
|
|
||||||
|
|
||||||
s.postMu.Lock()
|
|
||||||
defer s.postMu.Unlock()
|
|
||||||
close(s.postC)
|
|
||||||
s.postC = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TypeMuxSubscription) deliver(event *TypeMuxEvent) {
|
|
||||||
// Short circuit delivery if stale event
|
|
||||||
if s.created.After(event.Time) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Otherwise deliver the event
|
|
||||||
s.postMu.RLock()
|
|
||||||
defer s.postMu.RUnlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case s.postC <- event:
|
|
||||||
case <-s.closing:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testEvent int
|
|
||||||
|
|
||||||
func TestSubCloseUnsub(t *testing.T) {
|
|
||||||
// the point of this test is **not** to panic
|
|
||||||
var mux TypeMux
|
|
||||||
mux.Stop()
|
|
||||||
sub := mux.Subscribe(0)
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Data.(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) {
|
|
||||||
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) {
|
|
||||||
s := mux.Subscribe(testEvent(0))
|
|
||||||
go func() {
|
|
||||||
for range s.Chan() {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPost1000(b *testing.B) {
|
|
||||||
var (
|
|
||||||
mux = new(TypeMux)
|
|
||||||
subscribed, done sync.WaitGroup
|
|
||||||
nsubs = 1000
|
|
||||||
)
|
|
||||||
subscribed.Add(nsubs)
|
|
||||||
done.Add(nsubs)
|
|
||||||
for i := 0; i < nsubs; i++ {
|
|
||||||
go func() {
|
|
||||||
s := mux.Subscribe(testEvent(0))
|
|
||||||
subscribed.Done()
|
|
||||||
for range s.Chan() {
|
|
||||||
}
|
|
||||||
done.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
subscribed.Wait()
|
|
||||||
|
|
||||||
// The actual benchmark.
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
mux.Post(testEvent(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
mux.Stop()
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPostConcurrent(b *testing.B) {
|
|
||||||
var mux = new(TypeMux)
|
|
||||||
defer mux.Stop()
|
|
||||||
emptySubscriber(mux)
|
|
||||||
emptySubscriber(mux)
|
|
||||||
emptySubscriber(mux)
|
|
||||||
|
|
||||||
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{})
|
|
||||||
defer close(c)
|
|
||||||
closed := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
for range c {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
select {
|
|
||||||
case c <- i:
|
|
||||||
case <-closed:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFeed_acknowledgedEvents() {
|
|
||||||
// This example shows how the return value of Send can be used for request/reply
|
|
||||||
// interaction between event consumers and producers.
|
|
||||||
var feed event.Feed
|
|
||||||
type ackedEvent struct {
|
|
||||||
i int
|
|
||||||
ack chan<- struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumers wait for events on the feed and acknowledge processing.
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
ch := make(chan ackedEvent, 100)
|
|
||||||
sub := feed.Subscribe(ch)
|
|
||||||
go func() {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-ch:
|
|
||||||
fmt.Println(ev.i) // "process" the event
|
|
||||||
ev.ack <- struct{}{}
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The producer sends values of type ackedEvent with increasing values of i.
|
|
||||||
// It waits for all consumers to acknowledge before sending the next event.
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
acksignal := make(chan struct{})
|
|
||||||
n := feed.Send(ackedEvent{i, acksignal})
|
|
||||||
for ack := 0; ack < n; ack++ {
|
|
||||||
<-acksignal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// 0
|
|
||||||
// 0
|
|
||||||
// 0
|
|
||||||
// 1
|
|
||||||
// 1
|
|
||||||
// 1
|
|
||||||
// 2
|
|
||||||
// 2
|
|
||||||
// 2
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example demonstrates how SubscriptionScope can be used to control the lifetime of
|
|
||||||
// subscriptions.
|
|
||||||
//
|
|
||||||
// Our example program consists of two servers, each of which performs a calculation when
|
|
||||||
// requested. The servers also allow subscribing to results of all computations.
|
|
||||||
type divServer struct{ results event.Feed }
|
|
||||||
type mulServer struct{ results event.Feed }
|
|
||||||
|
|
||||||
func (s *divServer) do(a, b int) int {
|
|
||||||
r := a / b
|
|
||||||
s.results.Send(r)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mulServer) do(a, b int) int {
|
|
||||||
r := a * b
|
|
||||||
s.results.Send(r)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// The servers are contained in an App. The app controls the servers and exposes them
|
|
||||||
// through its API.
|
|
||||||
type App struct {
|
|
||||||
divServer
|
|
||||||
mulServer
|
|
||||||
scope event.SubscriptionScope
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *App) Calc(op byte, a, b int) int {
|
|
||||||
switch op {
|
|
||||||
case '/':
|
|
||||||
return s.divServer.do(a, b)
|
|
||||||
case '*':
|
|
||||||
return s.mulServer.do(a, b)
|
|
||||||
default:
|
|
||||||
panic("invalid op")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The app's SubscribeResults method starts sending calculation results to the given
|
|
||||||
// channel. Subscriptions created through this method are tied to the lifetime of the App
|
|
||||||
// because they are registered in the scope.
|
|
||||||
func (s *App) SubscribeResults(op byte, ch chan<- int) event.Subscription {
|
|
||||||
switch op {
|
|
||||||
case '/':
|
|
||||||
return s.scope.Track(s.divServer.results.Subscribe(ch))
|
|
||||||
case '*':
|
|
||||||
return s.scope.Track(s.mulServer.results.Subscribe(ch))
|
|
||||||
default:
|
|
||||||
panic("invalid op")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the App, closing all subscriptions created through SubscribeResults.
|
|
||||||
func (s *App) Stop() {
|
|
||||||
s.scope.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleSubscriptionScope() {
|
|
||||||
// Create the app.
|
|
||||||
var (
|
|
||||||
app App
|
|
||||||
wg sync.WaitGroup
|
|
||||||
divs = make(chan int)
|
|
||||||
muls = make(chan int)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run a subscriber in the background.
|
|
||||||
divsub := app.SubscribeResults('/', divs)
|
|
||||||
mulsub := app.SubscribeResults('*', muls)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
defer fmt.Println("subscriber exited")
|
|
||||||
defer divsub.Unsubscribe()
|
|
||||||
defer mulsub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case result := <-divs:
|
|
||||||
fmt.Println("division happened:", result)
|
|
||||||
case result := <-muls:
|
|
||||||
fmt.Println("multiplication happened:", result)
|
|
||||||
case <-divsub.Err():
|
|
||||||
return
|
|
||||||
case <-mulsub.Err():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Interact with the app.
|
|
||||||
app.Calc('/', 22, 11)
|
|
||||||
app.Calc('*', 3, 4)
|
|
||||||
|
|
||||||
// Stop the app. This shuts down the subscriptions, causing the subscriber to exit.
|
|
||||||
app.Stop()
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// division happened: 2
|
|
||||||
// multiplication happened: 12
|
|
||||||
// subscriber exited
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNewSubscription() {
|
|
||||||
// Create a subscription that sends 10 integers on ch.
|
|
||||||
ch := make(chan int)
|
|
||||||
sub := event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
select {
|
|
||||||
case ch <- i:
|
|
||||||
case <-quit:
|
|
||||||
fmt.Println("unsubscribed")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// This is the consumer. It reads 5 integers, then aborts the subscription.
|
|
||||||
// Note that Unsubscribe waits until the producer has shut down.
|
|
||||||
for i := range ch {
|
|
||||||
fmt.Println(i)
|
|
||||||
if i == 4 {
|
|
||||||
sub.Unsubscribe()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// 0
|
|
||||||
// 1
|
|
||||||
// 2
|
|
||||||
// 3
|
|
||||||
// 4
|
|
||||||
// unsubscribed
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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.Data)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
238
event/feed.go
238
event/feed.go
|
|
@ -1,238 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errBadChannel = errors.New("event: Subscribe argument does not have sendable channel type")
|
|
||||||
|
|
||||||
// Feed implements one-to-many subscriptions where the carrier of events is a channel.
|
|
||||||
// Values sent to a Feed are delivered to all subscribed channels simultaneously.
|
|
||||||
//
|
|
||||||
// Feeds can only be used with a single type. The type is determined by the first Send or
|
|
||||||
// Subscribe operation. Subsequent calls to these methods panic if the type does not
|
|
||||||
// match.
|
|
||||||
//
|
|
||||||
// The zero value is ready to use.
|
|
||||||
type Feed struct {
|
|
||||||
once sync.Once // ensures that init only runs once
|
|
||||||
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
|
|
||||||
removeSub chan interface{} // interrupts Send
|
|
||||||
sendCases caseList // the active set of select cases used by Send
|
|
||||||
|
|
||||||
// The inbox holds newly subscribed channels until they are added to sendCases.
|
|
||||||
mu sync.Mutex
|
|
||||||
inbox caseList
|
|
||||||
etype reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the index of the first actual subscription channel in sendCases.
|
|
||||||
// sendCases[0] is a SelectRecv case for the removeSub channel.
|
|
||||||
const firstSubSendCase = 1
|
|
||||||
|
|
||||||
type feedTypeError struct {
|
|
||||||
got, want reflect.Type
|
|
||||||
op string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e feedTypeError) Error() string {
|
|
||||||
return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Feed) init(etype reflect.Type) {
|
|
||||||
f.etype = etype
|
|
||||||
f.removeSub = make(chan interface{})
|
|
||||||
f.sendLock = make(chan struct{}, 1)
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel
|
|
||||||
// until the subscription is canceled. All channels added must have the same element type.
|
|
||||||
//
|
|
||||||
// The channel should have ample buffer space to avoid blocking other subscribers.
|
|
||||||
// Slow subscribers are not dropped.
|
|
||||||
func (f *Feed) Subscribe(channel interface{}) Subscription {
|
|
||||||
chanval := reflect.ValueOf(channel)
|
|
||||||
chantyp := chanval.Type()
|
|
||||||
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
|
|
||||||
panic(errBadChannel)
|
|
||||||
}
|
|
||||||
sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)}
|
|
||||||
|
|
||||||
f.once.Do(func() { f.init(chantyp.Elem()) })
|
|
||||||
if f.etype != chantyp.Elem() {
|
|
||||||
panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)})
|
|
||||||
}
|
|
||||||
|
|
||||||
f.mu.Lock()
|
|
||||||
defer f.mu.Unlock()
|
|
||||||
// Add the select case to the inbox.
|
|
||||||
// The next Send will add it to f.sendCases.
|
|
||||||
cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
|
|
||||||
f.inbox = append(f.inbox, cas)
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Feed) remove(sub *feedSub) {
|
|
||||||
// Delete from inbox first, which covers channels
|
|
||||||
// that have not been added to f.sendCases yet.
|
|
||||||
ch := sub.channel.Interface()
|
|
||||||
f.mu.Lock()
|
|
||||||
index := f.inbox.find(ch)
|
|
||||||
if index != -1 {
|
|
||||||
f.inbox = f.inbox.delete(index)
|
|
||||||
f.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case f.removeSub <- ch:
|
|
||||||
// Send will remove the channel from f.sendCases.
|
|
||||||
case <-f.sendLock:
|
|
||||||
// No Send is in progress, delete the channel now that we have the send lock.
|
|
||||||
f.sendCases = f.sendCases.delete(f.sendCases.find(ch))
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send delivers to all subscribed channels simultaneously.
|
|
||||||
// It returns the number of subscribers that the value was sent to.
|
|
||||||
func (f *Feed) Send(value interface{}) (nsent int) {
|
|
||||||
rvalue := reflect.ValueOf(value)
|
|
||||||
|
|
||||||
f.once.Do(func() { f.init(rvalue.Type()) })
|
|
||||||
if f.etype != rvalue.Type() {
|
|
||||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
|
||||||
}
|
|
||||||
|
|
||||||
<-f.sendLock
|
|
||||||
|
|
||||||
// Add new cases from the inbox after taking the send lock.
|
|
||||||
f.mu.Lock()
|
|
||||||
f.sendCases = append(f.sendCases, f.inbox...)
|
|
||||||
f.inbox = nil
|
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
// Set the sent value on all channels.
|
|
||||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
|
||||||
f.sendCases[i].Send = rvalue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
|
|
||||||
// of sendCases. When a send succeeds, the corresponding case moves to the end of
|
|
||||||
// 'cases' and it shrinks by one element.
|
|
||||||
cases := f.sendCases
|
|
||||||
for {
|
|
||||||
// Fast path: try sending without blocking before adding to the select set.
|
|
||||||
// This should usually succeed if subscribers are fast enough and have free
|
|
||||||
// buffer space.
|
|
||||||
for i := firstSubSendCase; i < len(cases); i++ {
|
|
||||||
if cases[i].Chan.TrySend(rvalue) {
|
|
||||||
nsent++
|
|
||||||
cases = cases.deactivate(i)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(cases) == firstSubSendCase {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Select on all the receivers, waiting for them to unblock.
|
|
||||||
chosen, recv, _ := reflect.Select(cases)
|
|
||||||
if chosen == 0 /* <-f.removeSub */ {
|
|
||||||
index := f.sendCases.find(recv.Interface())
|
|
||||||
f.sendCases = f.sendCases.delete(index)
|
|
||||||
if index >= 0 && index < len(cases) {
|
|
||||||
// Shrink 'cases' too because the removed case was still active.
|
|
||||||
cases = f.sendCases[:len(cases)-1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cases = cases.deactivate(chosen)
|
|
||||||
nsent++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget about the sent value and hand off the send lock.
|
|
||||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
|
||||||
f.sendCases[i].Send = reflect.Value{}
|
|
||||||
}
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
return nsent
|
|
||||||
}
|
|
||||||
|
|
||||||
type feedSub struct {
|
|
||||||
feed *Feed
|
|
||||||
channel reflect.Value
|
|
||||||
errOnce sync.Once
|
|
||||||
err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *feedSub) Unsubscribe() {
|
|
||||||
sub.errOnce.Do(func() {
|
|
||||||
sub.feed.remove(sub)
|
|
||||||
close(sub.err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *feedSub) Err() <-chan error {
|
|
||||||
return sub.err
|
|
||||||
}
|
|
||||||
|
|
||||||
type caseList []reflect.SelectCase
|
|
||||||
|
|
||||||
// find returns the index of a case containing the given channel.
|
|
||||||
func (cs caseList) find(channel interface{}) int {
|
|
||||||
for i, cas := range cs {
|
|
||||||
if cas.Chan.Interface() == channel {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete removes the given case from cs.
|
|
||||||
func (cs caseList) delete(index int) caseList {
|
|
||||||
return append(cs[:index], cs[index+1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deactivate moves the case at index into the non-accessible portion of the cs slice.
|
|
||||||
func (cs caseList) deactivate(index int) caseList {
|
|
||||||
last := len(cs) - 1
|
|
||||||
cs[index], cs[last] = cs[last], cs[index]
|
|
||||||
return cs[:last]
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (cs caseList) String() string {
|
|
||||||
// s := "["
|
|
||||||
// for i, cas := range cs {
|
|
||||||
// if i != 0 {
|
|
||||||
// s += ", "
|
|
||||||
// }
|
|
||||||
// switch cas.Dir {
|
|
||||||
// case reflect.SelectSend:
|
|
||||||
// s += fmt.Sprintf("%v<-", cas.Chan.Interface())
|
|
||||||
// case reflect.SelectRecv:
|
|
||||||
// s += fmt.Sprintf("<-%v", cas.Chan.Interface())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return s + "]"
|
|
||||||
// }
|
|
||||||
|
|
@ -1,335 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFeedPanics(t *testing.T) {
|
|
||||||
{
|
|
||||||
var f Feed
|
|
||||||
f.Send(2)
|
|
||||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
|
||||||
if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var f Feed
|
|
||||||
ch := make(chan int)
|
|
||||||
f.Subscribe(ch)
|
|
||||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
|
||||||
if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var f Feed
|
|
||||||
f.Send(2)
|
|
||||||
want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))}
|
|
||||||
if err := checkPanic(want, func() { f.Subscribe(make(chan uint64)) }); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var f Feed
|
|
||||||
if err := checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var f Feed
|
|
||||||
if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPanic(want error, fn func()) (err error) {
|
|
||||||
defer func() {
|
|
||||||
panic := recover()
|
|
||||||
if panic == nil {
|
|
||||||
err = errors.New("didn't panic")
|
|
||||||
} else if !reflect.DeepEqual(panic, want) {
|
|
||||||
err = fmt.Errorf("panicked with wrong error: got %q, want %q", panic, want)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fn()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeed(t *testing.T) {
|
|
||||||
var feed Feed
|
|
||||||
var done, subscribed sync.WaitGroup
|
|
||||||
subscriber := func(i int) {
|
|
||||||
defer done.Done()
|
|
||||||
|
|
||||||
subchan := make(chan int)
|
|
||||||
sub := feed.Subscribe(subchan)
|
|
||||||
timeout := time.NewTimer(2 * time.Second)
|
|
||||||
defer timeout.Stop()
|
|
||||||
subscribed.Done()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case v := <-subchan:
|
|
||||||
if v != 1 {
|
|
||||||
t.Errorf("%d: received value %d, want 1", i, v)
|
|
||||||
}
|
|
||||||
case <-timeout.C:
|
|
||||||
t.Errorf("%d: receive timeout", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case _, ok := <-sub.Err():
|
|
||||||
if ok {
|
|
||||||
t.Errorf("%d: error channel not closed after unsubscribe", i)
|
|
||||||
}
|
|
||||||
case <-timeout.C:
|
|
||||||
t.Errorf("%d: unsubscribe timeout", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = 1000
|
|
||||||
done.Add(n)
|
|
||||||
subscribed.Add(n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
go subscriber(i)
|
|
||||||
}
|
|
||||||
subscribed.Wait()
|
|
||||||
if nsent := feed.Send(1); nsent != n {
|
|
||||||
t.Errorf("first send delivered %d times, want %d", nsent, n)
|
|
||||||
}
|
|
||||||
if nsent := feed.Send(2); nsent != 0 {
|
|
||||||
t.Errorf("second send delivered %d times, want 0", nsent)
|
|
||||||
}
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedSubscribeSameChannel(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed Feed
|
|
||||||
done sync.WaitGroup
|
|
||||||
ch = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch)
|
|
||||||
sub2 = feed.Subscribe(ch)
|
|
||||||
_ = feed.Subscribe(ch)
|
|
||||||
)
|
|
||||||
expectSends := func(value, n int) {
|
|
||||||
if nsent := feed.Send(value); nsent != n {
|
|
||||||
t.Errorf("send delivered %d times, want %d", nsent, n)
|
|
||||||
}
|
|
||||||
done.Done()
|
|
||||||
}
|
|
||||||
expectRecv := func(wantValue, n int) {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
if v := <-ch; v != wantValue {
|
|
||||||
t.Errorf("received %d, want %d", v, wantValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(1, 3)
|
|
||||||
expectRecv(1, 3)
|
|
||||||
done.Wait()
|
|
||||||
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(2, 2)
|
|
||||||
expectRecv(2, 2)
|
|
||||||
done.Wait()
|
|
||||||
|
|
||||||
sub2.Unsubscribe()
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(3, 1)
|
|
||||||
expectRecv(3, 1)
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedSubscribeBlockedPost(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed Feed
|
|
||||||
nsends = 2000
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
defer wg.Wait()
|
|
||||||
|
|
||||||
feed.Subscribe(ch1)
|
|
||||||
wg.Add(nsends)
|
|
||||||
for i := 0; i < nsends; i++ {
|
|
||||||
go func() {
|
|
||||||
feed.Send(99)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
sub2 := feed.Subscribe(ch2)
|
|
||||||
defer sub2.Unsubscribe()
|
|
||||||
|
|
||||||
// We're done when ch1 has received N times.
|
|
||||||
// The number of receives on ch2 depends on scheduling.
|
|
||||||
for i := 0; i < nsends; {
|
|
||||||
select {
|
|
||||||
case <-ch1:
|
|
||||||
i++
|
|
||||||
case <-ch2:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedUnsubscribeBlockedPost(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed Feed
|
|
||||||
nsends = 200
|
|
||||||
chans = make([]chan int, 2000)
|
|
||||||
subs = make([]Subscription, len(chans))
|
|
||||||
bchan = make(chan int)
|
|
||||||
bsub = feed.Subscribe(bchan)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
for i := range chans {
|
|
||||||
chans[i] = make(chan int, nsends)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue up some Sends. None of these can make progress while bchan isn't read.
|
|
||||||
wg.Add(nsends)
|
|
||||||
for i := 0; i < nsends; i++ {
|
|
||||||
go func() {
|
|
||||||
feed.Send(99)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// Subscribe the other channels.
|
|
||||||
for i, ch := range chans {
|
|
||||||
subs[i] = feed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
// Unsubscribe them again.
|
|
||||||
for _, sub := range subs {
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
// Unblock the Sends.
|
|
||||||
bsub.Unsubscribe()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks that unsubscribing a channel during Send works even if that
|
|
||||||
// channel has already been sent on.
|
|
||||||
func TestFeedUnsubscribeSentChan(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed Feed
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch1)
|
|
||||||
sub2 = feed.Subscribe(ch2)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
defer sub2.Unsubscribe()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
feed.Send(0)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for the value on ch1.
|
|
||||||
<-ch1
|
|
||||||
// Unsubscribe ch1, removing it from the send cases.
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
|
|
||||||
// Receive ch2, finishing Send.
|
|
||||||
<-ch2
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// Send again. This should send to ch2 only, so the wait group will unblock
|
|
||||||
// as soon as a value is received on ch2.
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
feed.Send(0)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
<-ch2
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedUnsubscribeFromInbox(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed Feed
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch1)
|
|
||||||
sub2 = feed.Subscribe(ch1)
|
|
||||||
sub3 = feed.Subscribe(ch2)
|
|
||||||
)
|
|
||||||
if len(feed.inbox) != 3 {
|
|
||||||
t.Errorf("inbox length != 3 after subscribe")
|
|
||||||
}
|
|
||||||
if len(feed.sendCases) != 1 {
|
|
||||||
t.Errorf("sendCases is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
sub2.Unsubscribe()
|
|
||||||
sub3.Unsubscribe()
|
|
||||||
if len(feed.inbox) != 0 {
|
|
||||||
t.Errorf("inbox is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
if len(feed.sendCases) != 1 {
|
|
||||||
t.Errorf("sendCases is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFeedSend1000(b *testing.B) {
|
|
||||||
var (
|
|
||||||
done sync.WaitGroup
|
|
||||||
feed Feed
|
|
||||||
nsubs = 1000
|
|
||||||
)
|
|
||||||
subscriber := func(ch <-chan int) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
<-ch
|
|
||||||
}
|
|
||||||
done.Done()
|
|
||||||
}
|
|
||||||
done.Add(nsubs)
|
|
||||||
for i := 0; i < nsubs; i++ {
|
|
||||||
ch := make(chan int, 200)
|
|
||||||
feed.Subscribe(ch)
|
|
||||||
go subscriber(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual benchmark.
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if feed.Send(i) != nsubs {
|
|
||||||
panic("wrong number of sends")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
164
event/feedof.go
164
event/feedof.go
|
|
@ -1,164 +0,0 @@
|
||||||
// Copyright 2022 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FeedOf implements one-to-many subscriptions where the carrier of events is a channel.
|
|
||||||
// Values sent to a Feed are delivered to all subscribed channels simultaneously.
|
|
||||||
//
|
|
||||||
// The zero value is ready to use.
|
|
||||||
type FeedOf[T any] struct {
|
|
||||||
once sync.Once // ensures that init only runs once
|
|
||||||
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
|
|
||||||
removeSub chan chan<- T // interrupts Send
|
|
||||||
sendCases caseList // the active set of select cases used by Send
|
|
||||||
|
|
||||||
// The inbox holds newly subscribed channels until they are added to sendCases.
|
|
||||||
mu sync.Mutex
|
|
||||||
inbox caseList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FeedOf[T]) init() {
|
|
||||||
f.removeSub = make(chan chan<- T)
|
|
||||||
f.sendLock = make(chan struct{}, 1)
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel
|
|
||||||
// until the subscription is canceled.
|
|
||||||
//
|
|
||||||
// The channel should have ample buffer space to avoid blocking other subscribers. Slow
|
|
||||||
// subscribers are not dropped.
|
|
||||||
func (f *FeedOf[T]) Subscribe(channel chan<- T) Subscription {
|
|
||||||
f.once.Do(f.init)
|
|
||||||
|
|
||||||
chanval := reflect.ValueOf(channel)
|
|
||||||
sub := &feedOfSub[T]{feed: f, channel: channel, err: make(chan error, 1)}
|
|
||||||
|
|
||||||
// Add the select case to the inbox.
|
|
||||||
// The next Send will add it to f.sendCases.
|
|
||||||
f.mu.Lock()
|
|
||||||
defer f.mu.Unlock()
|
|
||||||
cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
|
|
||||||
f.inbox = append(f.inbox, cas)
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FeedOf[T]) remove(sub *feedOfSub[T]) {
|
|
||||||
// Delete from inbox first, which covers channels
|
|
||||||
// that have not been added to f.sendCases yet.
|
|
||||||
f.mu.Lock()
|
|
||||||
index := f.inbox.find(sub.channel)
|
|
||||||
if index != -1 {
|
|
||||||
f.inbox = f.inbox.delete(index)
|
|
||||||
f.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case f.removeSub <- sub.channel:
|
|
||||||
// Send will remove the channel from f.sendCases.
|
|
||||||
case <-f.sendLock:
|
|
||||||
// No Send is in progress, delete the channel now that we have the send lock.
|
|
||||||
f.sendCases = f.sendCases.delete(f.sendCases.find(sub.channel))
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send delivers to all subscribed channels simultaneously.
|
|
||||||
// It returns the number of subscribers that the value was sent to.
|
|
||||||
func (f *FeedOf[T]) Send(value T) (nsent int) {
|
|
||||||
rvalue := reflect.ValueOf(value)
|
|
||||||
|
|
||||||
f.once.Do(f.init)
|
|
||||||
<-f.sendLock
|
|
||||||
|
|
||||||
// Add new cases from the inbox after taking the send lock.
|
|
||||||
f.mu.Lock()
|
|
||||||
f.sendCases = append(f.sendCases, f.inbox...)
|
|
||||||
f.inbox = nil
|
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
// Set the sent value on all channels.
|
|
||||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
|
||||||
f.sendCases[i].Send = rvalue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
|
|
||||||
// of sendCases. When a send succeeds, the corresponding case moves to the end of
|
|
||||||
// 'cases' and it shrinks by one element.
|
|
||||||
cases := f.sendCases
|
|
||||||
for {
|
|
||||||
// Fast path: try sending without blocking before adding to the select set.
|
|
||||||
// This should usually succeed if subscribers are fast enough and have free
|
|
||||||
// buffer space.
|
|
||||||
for i := firstSubSendCase; i < len(cases); i++ {
|
|
||||||
if cases[i].Chan.TrySend(rvalue) {
|
|
||||||
nsent++
|
|
||||||
cases = cases.deactivate(i)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(cases) == firstSubSendCase {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Select on all the receivers, waiting for them to unblock.
|
|
||||||
chosen, recv, _ := reflect.Select(cases)
|
|
||||||
if chosen == 0 /* <-f.removeSub */ {
|
|
||||||
index := f.sendCases.find(recv.Interface())
|
|
||||||
f.sendCases = f.sendCases.delete(index)
|
|
||||||
if index >= 0 && index < len(cases) {
|
|
||||||
// Shrink 'cases' too because the removed case was still active.
|
|
||||||
cases = f.sendCases[:len(cases)-1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cases = cases.deactivate(chosen)
|
|
||||||
nsent++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget about the sent value and hand off the send lock.
|
|
||||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
|
||||||
f.sendCases[i].Send = reflect.Value{}
|
|
||||||
}
|
|
||||||
f.sendLock <- struct{}{}
|
|
||||||
return nsent
|
|
||||||
}
|
|
||||||
|
|
||||||
type feedOfSub[T any] struct {
|
|
||||||
feed *FeedOf[T]
|
|
||||||
channel chan<- T
|
|
||||||
errOnce sync.Once
|
|
||||||
err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *feedOfSub[T]) Unsubscribe() {
|
|
||||||
sub.errOnce.Do(func() {
|
|
||||||
sub.feed.remove(sub)
|
|
||||||
close(sub.err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *feedOfSub[T]) Err() <-chan error {
|
|
||||||
return sub.err
|
|
||||||
}
|
|
||||||
|
|
@ -1,279 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFeedOf(t *testing.T) {
|
|
||||||
var feed FeedOf[int]
|
|
||||||
var done, subscribed sync.WaitGroup
|
|
||||||
subscriber := func(i int) {
|
|
||||||
defer done.Done()
|
|
||||||
|
|
||||||
subchan := make(chan int)
|
|
||||||
sub := feed.Subscribe(subchan)
|
|
||||||
timeout := time.NewTimer(2 * time.Second)
|
|
||||||
defer timeout.Stop()
|
|
||||||
subscribed.Done()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case v := <-subchan:
|
|
||||||
if v != 1 {
|
|
||||||
t.Errorf("%d: received value %d, want 1", i, v)
|
|
||||||
}
|
|
||||||
case <-timeout.C:
|
|
||||||
t.Errorf("%d: receive timeout", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case _, ok := <-sub.Err():
|
|
||||||
if ok {
|
|
||||||
t.Errorf("%d: error channel not closed after unsubscribe", i)
|
|
||||||
}
|
|
||||||
case <-timeout.C:
|
|
||||||
t.Errorf("%d: unsubscribe timeout", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = 1000
|
|
||||||
done.Add(n)
|
|
||||||
subscribed.Add(n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
go subscriber(i)
|
|
||||||
}
|
|
||||||
subscribed.Wait()
|
|
||||||
if nsent := feed.Send(1); nsent != n {
|
|
||||||
t.Errorf("first send delivered %d times, want %d", nsent, n)
|
|
||||||
}
|
|
||||||
if nsent := feed.Send(2); nsent != 0 {
|
|
||||||
t.Errorf("second send delivered %d times, want 0", nsent)
|
|
||||||
}
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedOfSubscribeSameChannel(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed FeedOf[int]
|
|
||||||
done sync.WaitGroup
|
|
||||||
ch = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch)
|
|
||||||
sub2 = feed.Subscribe(ch)
|
|
||||||
_ = feed.Subscribe(ch)
|
|
||||||
)
|
|
||||||
expectSends := func(value, n int) {
|
|
||||||
if nsent := feed.Send(value); nsent != n {
|
|
||||||
t.Errorf("send delivered %d times, want %d", nsent, n)
|
|
||||||
}
|
|
||||||
done.Done()
|
|
||||||
}
|
|
||||||
expectRecv := func(wantValue, n int) {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
if v := <-ch; v != wantValue {
|
|
||||||
t.Errorf("received %d, want %d", v, wantValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(1, 3)
|
|
||||||
expectRecv(1, 3)
|
|
||||||
done.Wait()
|
|
||||||
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(2, 2)
|
|
||||||
expectRecv(2, 2)
|
|
||||||
done.Wait()
|
|
||||||
|
|
||||||
sub2.Unsubscribe()
|
|
||||||
|
|
||||||
done.Add(1)
|
|
||||||
go expectSends(3, 1)
|
|
||||||
expectRecv(3, 1)
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedOfSubscribeBlockedPost(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed FeedOf[int]
|
|
||||||
nsends = 2000
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
defer wg.Wait()
|
|
||||||
|
|
||||||
feed.Subscribe(ch1)
|
|
||||||
wg.Add(nsends)
|
|
||||||
for i := 0; i < nsends; i++ {
|
|
||||||
go func() {
|
|
||||||
feed.Send(99)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
sub2 := feed.Subscribe(ch2)
|
|
||||||
defer sub2.Unsubscribe()
|
|
||||||
|
|
||||||
// We're done when ch1 has received N times.
|
|
||||||
// The number of receives on ch2 depends on scheduling.
|
|
||||||
for i := 0; i < nsends; {
|
|
||||||
select {
|
|
||||||
case <-ch1:
|
|
||||||
i++
|
|
||||||
case <-ch2:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedOfUnsubscribeBlockedPost(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed FeedOf[int]
|
|
||||||
nsends = 200
|
|
||||||
chans = make([]chan int, 2000)
|
|
||||||
subs = make([]Subscription, len(chans))
|
|
||||||
bchan = make(chan int)
|
|
||||||
bsub = feed.Subscribe(bchan)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
for i := range chans {
|
|
||||||
chans[i] = make(chan int, nsends)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue up some Sends. None of these can make progress while bchan isn't read.
|
|
||||||
wg.Add(nsends)
|
|
||||||
for i := 0; i < nsends; i++ {
|
|
||||||
go func() {
|
|
||||||
feed.Send(99)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// Subscribe the other channels.
|
|
||||||
for i, ch := range chans {
|
|
||||||
subs[i] = feed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
// Unsubscribe them again.
|
|
||||||
for _, sub := range subs {
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
// Unblock the Sends.
|
|
||||||
bsub.Unsubscribe()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks that unsubscribing a channel during Send works even if that
|
|
||||||
// channel has already been sent on.
|
|
||||||
func TestFeedOfUnsubscribeSentChan(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed FeedOf[int]
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch1)
|
|
||||||
sub2 = feed.Subscribe(ch2)
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
defer sub2.Unsubscribe()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
feed.Send(0)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for the value on ch1.
|
|
||||||
<-ch1
|
|
||||||
// Unsubscribe ch1, removing it from the send cases.
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
|
|
||||||
// Receive ch2, finishing Send.
|
|
||||||
<-ch2
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// Send again. This should send to ch2 only, so the wait group will unblock
|
|
||||||
// as soon as a value is received on ch2.
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
feed.Send(0)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
<-ch2
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeedOfUnsubscribeFromInbox(t *testing.T) {
|
|
||||||
var (
|
|
||||||
feed FeedOf[int]
|
|
||||||
ch1 = make(chan int)
|
|
||||||
ch2 = make(chan int)
|
|
||||||
sub1 = feed.Subscribe(ch1)
|
|
||||||
sub2 = feed.Subscribe(ch1)
|
|
||||||
sub3 = feed.Subscribe(ch2)
|
|
||||||
)
|
|
||||||
if len(feed.inbox) != 3 {
|
|
||||||
t.Errorf("inbox length != 3 after subscribe")
|
|
||||||
}
|
|
||||||
if len(feed.sendCases) != 1 {
|
|
||||||
t.Errorf("sendCases is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
sub2.Unsubscribe()
|
|
||||||
sub3.Unsubscribe()
|
|
||||||
if len(feed.inbox) != 0 {
|
|
||||||
t.Errorf("inbox is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
if len(feed.sendCases) != 1 {
|
|
||||||
t.Errorf("sendCases is non-empty after unsubscribe")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFeedOfSend1000(b *testing.B) {
|
|
||||||
var (
|
|
||||||
done sync.WaitGroup
|
|
||||||
feed FeedOf[int]
|
|
||||||
nsubs = 1000
|
|
||||||
)
|
|
||||||
subscriber := func(ch <-chan int) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
<-ch
|
|
||||||
}
|
|
||||||
done.Done()
|
|
||||||
}
|
|
||||||
done.Add(nsubs)
|
|
||||||
for i := 0; i < nsubs; i++ {
|
|
||||||
ch := make(chan int, 200)
|
|
||||||
feed.Subscribe(ch)
|
|
||||||
go subscriber(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual benchmark.
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if feed.Send(i) != nsubs {
|
|
||||||
panic("wrong number of sends")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
done.Wait()
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
// JoinSubscriptions joins multiple subscriptions to be able to track them as
|
|
||||||
// one entity and collectively cancel them of consume any errors from them.
|
|
||||||
func JoinSubscriptions(subs ...Subscription) Subscription {
|
|
||||||
return NewSubscription(func(unsubbed <-chan struct{}) error {
|
|
||||||
// Unsubscribe all subscriptions before returning
|
|
||||||
defer func() {
|
|
||||||
for _, sub := range subs {
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Wait for an error on any of the subscriptions and propagate up
|
|
||||||
errc := make(chan error, len(subs))
|
|
||||||
for i := range subs {
|
|
||||||
go func(sub Subscription) {
|
|
||||||
select {
|
|
||||||
case err := <-sub.Err():
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
}
|
|
||||||
case <-unsubbed:
|
|
||||||
}
|
|
||||||
}(subs[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return err
|
|
||||||
case <-unsubbed:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMultisub(t *testing.T) {
|
|
||||||
// Create a double subscription and ensure events propagate through
|
|
||||||
var (
|
|
||||||
feed1 Feed
|
|
||||||
feed2 Feed
|
|
||||||
)
|
|
||||||
sink1 := make(chan int, 1)
|
|
||||||
sink2 := make(chan int, 1)
|
|
||||||
|
|
||||||
sub1 := feed1.Subscribe(sink1)
|
|
||||||
sub2 := feed2.Subscribe(sink2)
|
|
||||||
|
|
||||||
sub := JoinSubscriptions(sub1, sub2)
|
|
||||||
|
|
||||||
feed1.Send(1)
|
|
||||||
select {
|
|
||||||
case n := <-sink1:
|
|
||||||
if n != 1 {
|
|
||||||
t.Errorf("sink 1 delivery mismatch: have %d, want %d", n, 1)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Error("sink 1 missing delivery")
|
|
||||||
}
|
|
||||||
|
|
||||||
feed2.Send(2)
|
|
||||||
select {
|
|
||||||
case n := <-sink2:
|
|
||||||
if n != 2 {
|
|
||||||
t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Error("sink 2 missing delivery")
|
|
||||||
}
|
|
||||||
// Unsubscribe and ensure no more events are delivered
|
|
||||||
sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case <-sub.Err():
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
t.Error("multisub didn't propagate closure")
|
|
||||||
}
|
|
||||||
|
|
||||||
feed1.Send(11)
|
|
||||||
select {
|
|
||||||
case n := <-sink1:
|
|
||||||
t.Errorf("sink 1 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
feed2.Send(22)
|
|
||||||
select {
|
|
||||||
case n := <-sink2:
|
|
||||||
t.Errorf("sink 2 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMutisubPartialUnsubscribe(t *testing.T) {
|
|
||||||
// Create a double subscription but terminate one half, ensuring no error
|
|
||||||
// is propagated yet up to the outer subscription
|
|
||||||
var (
|
|
||||||
feed1 Feed
|
|
||||||
feed2 Feed
|
|
||||||
)
|
|
||||||
sink1 := make(chan int, 1)
|
|
||||||
sink2 := make(chan int, 1)
|
|
||||||
|
|
||||||
sub1 := feed1.Subscribe(sink1)
|
|
||||||
sub2 := feed2.Subscribe(sink2)
|
|
||||||
|
|
||||||
sub := JoinSubscriptions(sub1, sub2)
|
|
||||||
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case <-sub.Err():
|
|
||||||
t.Error("multisub propagated closure")
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
}
|
|
||||||
// Ensure that events cross only the second feed
|
|
||||||
feed1.Send(1)
|
|
||||||
select {
|
|
||||||
case n := <-sink1:
|
|
||||||
t.Errorf("sink 1 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
feed2.Send(2)
|
|
||||||
select {
|
|
||||||
case n := <-sink2:
|
|
||||||
if n != 2 {
|
|
||||||
t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Error("sink 2 missing delivery")
|
|
||||||
}
|
|
||||||
// Unsubscribe and ensure no more events are delivered
|
|
||||||
sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case <-sub.Err():
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
t.Error("multisub didn't propagate closure")
|
|
||||||
}
|
|
||||||
|
|
||||||
feed1.Send(11)
|
|
||||||
select {
|
|
||||||
case n := <-sink1:
|
|
||||||
t.Errorf("sink 1 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
feed2.Send(22)
|
|
||||||
select {
|
|
||||||
case n := <-sink2:
|
|
||||||
t.Errorf("sink 2 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultisubFullUnsubscribe(t *testing.T) {
|
|
||||||
// Create a double subscription and terminate the multi sub, ensuring an
|
|
||||||
// error is propagated up.
|
|
||||||
var (
|
|
||||||
feed1 Feed
|
|
||||||
feed2 Feed
|
|
||||||
)
|
|
||||||
sink1 := make(chan int, 1)
|
|
||||||
sink2 := make(chan int, 1)
|
|
||||||
|
|
||||||
sub1 := feed1.Subscribe(sink1)
|
|
||||||
sub2 := feed2.Subscribe(sink2)
|
|
||||||
|
|
||||||
sub := JoinSubscriptions(sub1, sub2)
|
|
||||||
sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case <-sub.Err():
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
t.Error("multisub didn't propagate closure")
|
|
||||||
}
|
|
||||||
// Ensure no more events are delivered
|
|
||||||
feed1.Send(1)
|
|
||||||
select {
|
|
||||||
case n := <-sink1:
|
|
||||||
t.Errorf("sink 1 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
feed2.Send(2)
|
|
||||||
select {
|
|
||||||
case n := <-sink2:
|
|
||||||
t.Errorf("sink 2 unexpected delivery: %d", n)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,298 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Subscription represents a stream of events. The carrier of the events is typically a
|
|
||||||
// channel, but isn't part of the interface.
|
|
||||||
//
|
|
||||||
// Subscriptions can fail while established. Failures are reported through an error
|
|
||||||
// channel. It receives a value if there is an issue with the subscription (e.g. the
|
|
||||||
// network connection delivering the events has been closed). Only one value will ever be
|
|
||||||
// sent.
|
|
||||||
//
|
|
||||||
// The error channel is closed when the subscription ends successfully (i.e. when the
|
|
||||||
// source of events is closed). It is also closed when Unsubscribe is called.
|
|
||||||
//
|
|
||||||
// The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all
|
|
||||||
// cases to ensure that resources related to the subscription are released. It can be
|
|
||||||
// called any number of times.
|
|
||||||
type Subscription interface {
|
|
||||||
Err() <-chan error // returns the error channel
|
|
||||||
Unsubscribe() // cancels sending of events, closing the error channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSubscription runs a producer function as a subscription in a new goroutine. The
|
|
||||||
// channel given to the producer is closed when Unsubscribe is called. If fn returns an
|
|
||||||
// error, it is sent on the subscription's error channel.
|
|
||||||
func NewSubscription(producer func(<-chan struct{}) error) Subscription {
|
|
||||||
s := &funcSub{unsub: make(chan struct{}), err: make(chan error, 1)}
|
|
||||||
go func() {
|
|
||||||
defer close(s.err)
|
|
||||||
err := producer(s.unsub)
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if !s.unsubscribed {
|
|
||||||
if err != nil {
|
|
||||||
s.err <- err
|
|
||||||
}
|
|
||||||
s.unsubscribed = true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcSub struct {
|
|
||||||
unsub chan struct{}
|
|
||||||
err chan error
|
|
||||||
mu sync.Mutex
|
|
||||||
unsubscribed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *funcSub) Unsubscribe() {
|
|
||||||
s.mu.Lock()
|
|
||||||
if s.unsubscribed {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.unsubscribed = true
|
|
||||||
close(s.unsub)
|
|
||||||
s.mu.Unlock()
|
|
||||||
// Wait for producer shutdown.
|
|
||||||
<-s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *funcSub) Err() <-chan error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resubscribe calls fn repeatedly to keep a subscription established. When the
|
|
||||||
// subscription is established, Resubscribe waits for it to fail and calls fn again. This
|
|
||||||
// process repeats until Unsubscribe is called or the active subscription ends
|
|
||||||
// successfully.
|
|
||||||
//
|
|
||||||
// Resubscribe applies backoff between calls to fn. The time between calls is adapted
|
|
||||||
// based on the error rate, but will never exceed backoffMax.
|
|
||||||
func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription {
|
|
||||||
return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) {
|
|
||||||
return fn(ctx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ResubscribeFunc attempts to establish a subscription.
|
|
||||||
type ResubscribeFunc func(context.Context) (Subscription, error)
|
|
||||||
|
|
||||||
// ResubscribeErr calls fn repeatedly to keep a subscription established. When the
|
|
||||||
// subscription is established, ResubscribeErr waits for it to fail and calls fn again. This
|
|
||||||
// process repeats until Unsubscribe is called or the active subscription ends
|
|
||||||
// successfully.
|
|
||||||
//
|
|
||||||
// The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr,
|
|
||||||
// the error of the failing subscription is available to the callback for logging
|
|
||||||
// purposes.
|
|
||||||
//
|
|
||||||
// ResubscribeErr applies backoff between calls to fn. The time between calls is adapted
|
|
||||||
// based on the error rate, but will never exceed backoffMax.
|
|
||||||
func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription {
|
|
||||||
s := &resubscribeSub{
|
|
||||||
waitTime: backoffMax / 10,
|
|
||||||
backoffMax: backoffMax,
|
|
||||||
fn: fn,
|
|
||||||
err: make(chan error),
|
|
||||||
unsub: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
go s.loop()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ResubscribeErrFunc attempts to establish a subscription.
|
|
||||||
// For every call but the first, the second argument to this function is
|
|
||||||
// the error that occurred with the previous subscription.
|
|
||||||
type ResubscribeErrFunc func(context.Context, error) (Subscription, error)
|
|
||||||
|
|
||||||
type resubscribeSub struct {
|
|
||||||
fn ResubscribeErrFunc
|
|
||||||
err chan error
|
|
||||||
unsub chan struct{}
|
|
||||||
unsubOnce sync.Once
|
|
||||||
lastTry mclock.AbsTime
|
|
||||||
lastSubErr error
|
|
||||||
waitTime, backoffMax time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) Unsubscribe() {
|
|
||||||
s.unsubOnce.Do(func() {
|
|
||||||
s.unsub <- struct{}{}
|
|
||||||
<-s.err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) Err() <-chan error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) loop() {
|
|
||||||
defer close(s.err)
|
|
||||||
var done bool
|
|
||||||
for !done {
|
|
||||||
sub := s.subscribe()
|
|
||||||
if sub == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
done = s.waitForError(sub)
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) subscribe() Subscription {
|
|
||||||
subscribed := make(chan error)
|
|
||||||
var sub Subscription
|
|
||||||
for {
|
|
||||||
s.lastTry = mclock.Now()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
go func() {
|
|
||||||
rsub, err := s.fn(ctx, s.lastSubErr)
|
|
||||||
sub = rsub
|
|
||||||
subscribed <- err
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case err := <-subscribed:
|
|
||||||
cancel()
|
|
||||||
if err == nil {
|
|
||||||
if sub == nil {
|
|
||||||
panic("event: ResubscribeFunc returned nil subscription and no error")
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
// Subscribing failed, wait before launching the next try.
|
|
||||||
if s.backoffWait() {
|
|
||||||
return nil // unsubscribed during wait
|
|
||||||
}
|
|
||||||
case <-s.unsub:
|
|
||||||
cancel()
|
|
||||||
<-subscribed // avoid leaking the s.fn goroutine.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) waitForError(sub Subscription) bool {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
select {
|
|
||||||
case err := <-sub.Err():
|
|
||||||
s.lastSubErr = err
|
|
||||||
return err == nil
|
|
||||||
case <-s.unsub:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *resubscribeSub) backoffWait() bool {
|
|
||||||
if time.Duration(mclock.Now()-s.lastTry) > s.backoffMax {
|
|
||||||
s.waitTime = s.backoffMax / 10
|
|
||||||
} else {
|
|
||||||
s.waitTime *= 2
|
|
||||||
if s.waitTime > s.backoffMax {
|
|
||||||
s.waitTime = s.backoffMax
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.NewTimer(s.waitTime)
|
|
||||||
defer t.Stop()
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
return false
|
|
||||||
case <-s.unsub:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once.
|
|
||||||
//
|
|
||||||
// For code that handle more than one subscription, a scope can be used to conveniently
|
|
||||||
// unsubscribe all of them with a single call. The example demonstrates a typical use in a
|
|
||||||
// larger program.
|
|
||||||
//
|
|
||||||
// The zero value is ready to use.
|
|
||||||
type SubscriptionScope struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
subs map[*scopeSub]struct{}
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type scopeSub struct {
|
|
||||||
sc *SubscriptionScope
|
|
||||||
s Subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track starts tracking a subscription. If the scope is closed, Track returns nil. The
|
|
||||||
// returned subscription is a wrapper. Unsubscribing the wrapper removes it from the
|
|
||||||
// scope.
|
|
||||||
func (sc *SubscriptionScope) Track(s Subscription) Subscription {
|
|
||||||
sc.mu.Lock()
|
|
||||||
defer sc.mu.Unlock()
|
|
||||||
if sc.closed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if sc.subs == nil {
|
|
||||||
sc.subs = make(map[*scopeSub]struct{})
|
|
||||||
}
|
|
||||||
ss := &scopeSub{sc, s}
|
|
||||||
sc.subs[ss] = struct{}{}
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close calls Unsubscribe on all tracked subscriptions and prevents further additions to
|
|
||||||
// the tracked set. Calls to Track after Close return nil.
|
|
||||||
func (sc *SubscriptionScope) Close() {
|
|
||||||
sc.mu.Lock()
|
|
||||||
defer sc.mu.Unlock()
|
|
||||||
if sc.closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sc.closed = true
|
|
||||||
for s := range sc.subs {
|
|
||||||
s.s.Unsubscribe()
|
|
||||||
}
|
|
||||||
sc.subs = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns the number of tracked subscriptions.
|
|
||||||
// It is meant to be used for debugging.
|
|
||||||
func (sc *SubscriptionScope) Count() int {
|
|
||||||
sc.mu.Lock()
|
|
||||||
defer sc.mu.Unlock()
|
|
||||||
return len(sc.subs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *scopeSub) Unsubscribe() {
|
|
||||||
s.s.Unsubscribe()
|
|
||||||
s.sc.mu.Lock()
|
|
||||||
defer s.sc.mu.Unlock()
|
|
||||||
delete(s.sc.subs, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *scopeSub) Err() <-chan error {
|
|
||||||
return s.s.Err()
|
|
||||||
}
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errInts = errors.New("error in subscribeInts")
|
|
||||||
|
|
||||||
func subscribeInts(max, fail int, c chan<- int) Subscription {
|
|
||||||
return NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
for i := 0; i < max; i++ {
|
|
||||||
if i >= fail {
|
|
||||||
return errInts
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case c <- i:
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewSubscriptionError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
channel := make(chan int)
|
|
||||||
sub := subscribeInts(10, 2, channel)
|
|
||||||
loop:
|
|
||||||
for want := 0; want < 10; want++ {
|
|
||||||
select {
|
|
||||||
case got := <-channel:
|
|
||||||
if got != want {
|
|
||||||
t.Fatalf("wrong int %d, want %d", got, want)
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
if err != errInts {
|
|
||||||
t.Fatalf("wrong error: got %q, want %q", err, errInts)
|
|
||||||
}
|
|
||||||
if want != 2 {
|
|
||||||
t.Fatalf("got errInts at int %d, should be received at 2", want)
|
|
||||||
}
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sub.Unsubscribe()
|
|
||||||
|
|
||||||
err, ok := <-sub.Err()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("got non-nil error after Unsubscribe")
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
t.Fatal("channel still open after Unsubscribe")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubscribe(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var i int
|
|
||||||
nfails := 6
|
|
||||||
sub := Resubscribe(100*time.Millisecond, func(ctx context.Context) (Subscription, error) {
|
|
||||||
// fmt.Printf("call #%d @ %v\n", i, time.Now())
|
|
||||||
i++
|
|
||||||
if i == 2 {
|
|
||||||
// Delay the second failure a bit to reset the resubscribe interval.
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if i < nfails {
|
|
||||||
return nil, errors.New("oops")
|
|
||||||
}
|
|
||||||
sub := NewSubscription(func(unsubscribed <-chan struct{}) error { return nil })
|
|
||||||
return sub, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
<-sub.Err()
|
|
||||||
if i != nfails {
|
|
||||||
t.Fatalf("resubscribe function called %d times, want %d times", i, nfails)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubscribeAbort(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
sub := Resubscribe(0, func(ctx context.Context) (Subscription, error) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
done <- nil
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
done <- errors.New("context given to resubscribe function not canceled within 2s")
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
sub.Unsubscribe()
|
|
||||||
if err := <-done; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubscribeWithErrorHandler(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var i int
|
|
||||||
nfails := 6
|
|
||||||
subErrs := make([]string, 0)
|
|
||||||
sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) {
|
|
||||||
i++
|
|
||||||
var lastErrVal string
|
|
||||||
if lastErr != nil {
|
|
||||||
lastErrVal = lastErr.Error()
|
|
||||||
}
|
|
||||||
subErrs = append(subErrs, lastErrVal)
|
|
||||||
sub := NewSubscription(func(unsubscribed <-chan struct{}) error {
|
|
||||||
if i < nfails {
|
|
||||||
return fmt.Errorf("err-%v", i)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return sub, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
<-sub.Err()
|
|
||||||
if i != nfails {
|
|
||||||
t.Fatalf("resubscribe function called %d times, want %d times", i, nfails)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedSubErrs := []string{"", "err-1", "err-2", "err-3", "err-4", "err-5"}
|
|
||||||
if !reflect.DeepEqual(subErrs, expectedSubErrs) {
|
|
||||||
t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubscribeWithCompletedSubscription(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
quitProducerAck := make(chan struct{})
|
|
||||||
quitProducer := make(chan struct{})
|
|
||||||
|
|
||||||
sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) {
|
|
||||||
return NewSubscription(func(unsubscribed <-chan struct{}) error {
|
|
||||||
select {
|
|
||||||
case <-quitProducer:
|
|
||||||
quitProducerAck <- struct{}{}
|
|
||||||
return nil
|
|
||||||
case <-unsubscribed:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ensure producer has started and exited before Unsubscribe
|
|
||||||
close(quitProducer)
|
|
||||||
<-quitProducerAck
|
|
||||||
sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue