10 Commits

Author SHA1 Message Date
Steven Allen
7b280b5c1b nit: fix with-node
1. It doesn't return an error and we weren't checking it anyways.
2. Avoid a goroutine unless we need it.
2019-06-27 20:13:36 +02:00
Steven Allen
df5be7d7dd Merge pull request #11 from libp2p/fix/things
Fix close deadlock and Sub type error
2019-06-27 20:13:09 +02:00
Łukasz Magiera
0c299185af Merge pull request #14 from libp2p/nit/simple-reflect
nit: avoid ValueOf
2019-06-27 19:40:02 +02:00
Steven Allen
12782426ed nit: avoid ValueOf
We only need TypeOf.
2019-06-27 19:20:43 +02:00
Łukasz Magiera
9c3fe0580a Merge pull request #13 from libp2p/feat/buffer-by-default
Enable buffering by default
2019-06-25 22:07:48 +02:00
Jakub Sztandera
0976d4b358 Enable buffering by default
License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
2019-06-25 20:47:02 +02:00
Łukasz Magiera
61257f90a2 Merge pull request #12 from libp2p/feat/better-bench
Improve benchmarks
2019-06-22 17:41:26 +02:00
Jakub Sztandera
454cbe5497 Make the test fail in reasonable time
License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
2019-06-22 15:45:09 +02:00
Łukasz Magiera
3fd76c7eac Add test for #10 2019-06-22 15:14:04 +02:00
Łukasz Magiera
525a0e67fe fix close deadlock and Sub type error 2019-06-22 12:05:03 +02:00
3 changed files with 98 additions and 19 deletions

View File

@@ -51,7 +51,7 @@ func NewBus() event.Bus {
}
}
func (b *basicBus) withNode(typ reflect.Type, cb func(*node), async func(*node)) error {
func (b *basicBus) withNode(typ reflect.Type, cb func(*node), async func(*node)) {
b.lk.Lock()
n, ok := b.nodes[typ]
@@ -65,12 +65,14 @@ func (b *basicBus) withNode(typ reflect.Type, cb func(*node), async func(*node))
cb(n)
go func() {
defer n.lk.Unlock()
async(n)
}()
return nil
if async == nil {
n.lk.Unlock()
} else {
go func() {
defer n.lk.Unlock()
async(n)
}()
}
}
func (b *basicBus) tryDropNode(typ reflect.Type) {
@@ -104,9 +106,21 @@ func (s *sub) Out() <-chan interface{} {
}
func (s *sub) Close() error {
close(s.ch)
stop := make(chan struct{})
go func() {
for {
select {
case <-s.ch:
case <-stop:
close(s.ch)
return
}
}
}()
for _, n := range s.nodes {
n.lk.Lock()
for i := 0; i < len(n.sinks); i++ {
if n.sinks[i] == s.ch {
n.sinks[i], n.sinks[len(n.sinks)-1] = n.sinks[len(n.sinks)-1], nil
@@ -114,12 +128,16 @@ func (s *sub) Close() error {
break
}
}
tryDrop := len(n.sinks) == 0 && atomic.LoadInt32(&n.nEmitters) == 0
n.lk.Unlock()
if tryDrop {
s.dropper(n.typ)
}
}
close(stop)
return nil
}
@@ -129,7 +147,7 @@ var _ event.Subscription = (*sub)(nil)
// publishers to get blocked. CancelFunc is guaranteed to return after last send
// to the channel
func (b *basicBus) Subscribe(evtTypes interface{}, opts ...event.SubscriptionOpt) (_ event.Subscription, err error) {
var settings subSettings
settings := subSettings(subSettingsDefault)
for _, opt := range opts {
if err := opt(&settings); err != nil {
return nil, err
@@ -148,14 +166,16 @@ func (b *basicBus) Subscribe(evtTypes interface{}, opts ...event.SubscriptionOpt
dropper: b.tryDropNode,
}
for _, etyp := range types {
if reflect.TypeOf(etyp).Kind() != reflect.Ptr {
return nil, errors.New("subscribe called with non-pointer type")
}
}
for i, etyp := range types {
typ := reflect.TypeOf(etyp)
if typ.Kind() != reflect.Ptr {
return nil, errors.New("subscribe called with non-pointer type")
}
err = b.withNode(typ.Elem(), func(n *node) {
b.withNode(typ.Elem(), func(n *node) {
n.sinks = append(n.sinks, out.ch)
out.nodes[i] = n
}, func(n *node) {
@@ -184,6 +204,7 @@ func (b *basicBus) Subscribe(evtTypes interface{}, opts ...event.SubscriptionOpt
// emit(EventT{})
func (b *basicBus) Emitter(evtType interface{}, opts ...event.EmitterOpt) (e event.Emitter, err error) {
var settings emitterSettings
for _, opt := range opts {
if err := opt(&settings); err != nil {
return nil, err
@@ -196,11 +217,11 @@ func (b *basicBus) Emitter(evtType interface{}, opts ...event.EmitterOpt) (e eve
}
typ = typ.Elem()
err = b.withNode(typ, func(n *node) {
b.withNode(typ, func(n *node) {
atomic.AddInt32(&n.nEmitters, 1)
n.keepLast = n.keepLast || settings.makeStateful
e = &emitter{n: n, typ: typ, dropper: b.tryDropNode}
}, func(_ *node) {})
}, nil)
return
}
@@ -229,9 +250,9 @@ func newNode(typ reflect.Type) *node {
}
func (n *node) emit(event interface{}) {
eval := reflect.ValueOf(event)
if eval.Type() != n.typ {
panic(fmt.Sprintf("Emit called with wrong type. expected: %s, got: %s", n.typ, eval.Type()))
typ := reflect.TypeOf(event)
if typ != n.typ {
panic(fmt.Sprintf("Emit called with wrong type. expected: %s, got: %s", n.typ, typ))
}
n.lk.RLock()

View File

@@ -25,6 +25,17 @@ func (EventA) String() string {
return "Oh, Hello"
}
func TestDefaultSubIsBuffered(t *testing.T) {
bus := NewBus()
s, err := bus.Subscribe(new(EventA))
if err != nil {
t.Fatal(err)
}
if cap(s.(*sub).ch) == 0 {
t.Fatalf("without any options subscribe should be buffered. was %d", cap(s.(*sub).ch))
}
}
func TestEmit(t *testing.T) {
bus := NewBus()
sub, err := bus.Subscribe(new(EventA))
@@ -297,6 +308,49 @@ func TestStateful(t *testing.T) {
}
}
func TestCloseBlocking(t *testing.T) {
bus := NewBus()
em, err := bus.Emitter(new(EventB))
if err != nil {
t.Fatal(err)
}
sub, err := bus.Subscribe(new(EventB))
if err != nil {
t.Fatal(err)
}
go func() {
em.Emit(EventB(159))
}()
time.Sleep(10 * time.Millisecond) // make sure that emit is blocked
sub.Close() // cancel sub
}
func panicOnTimeout(d time.Duration) {
<-time.After(d)
panic("timeout reached")
}
func TestSubFailFully(t *testing.T) {
bus := NewBus()
em, err := bus.Emitter(new(EventB))
if err != nil {
t.Fatal(err)
}
_, err = bus.Subscribe([]interface{}{new(EventB), 5})
if err == nil || err.Error() != "subscribe called with non-pointer type" {
t.Fatal(err)
}
go panicOnTimeout(5 * time.Second)
em.Emit(EventB(159)) // will hang if sub doesn't fail properly
}
func testMany(t testing.TB, subs, emits, msgs int, stateful bool) {
if race.WithRace() && subs+emits > 5000 {
t.SkipNow()

View File

@@ -4,6 +4,10 @@ type subSettings struct {
buffer int
}
var subSettingsDefault = subSettings{
buffer: 16,
}
func BufSize(n int) func(interface{}) error {
return func(s interface{}) error {
s.(*subSettings).buffer = n