util/eventbus: add debug hooks to snoop on bus traffic
Updates #15160 Signed-off-by: David Anderson <dave@tailscale.com>
This commit is contained in:
parent
dd7166cb8e
commit
e80d2b4ad1
@ -8,17 +8,28 @@
|
|||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type publishedEvent struct {
|
||||||
|
Event any
|
||||||
|
From *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type routedEvent struct {
|
||||||
|
Event any
|
||||||
|
From *Client
|
||||||
|
To []*Client
|
||||||
|
}
|
||||||
|
|
||||||
// Bus is an event bus that distributes published events to interested
|
// Bus is an event bus that distributes published events to interested
|
||||||
// subscribers.
|
// subscribers.
|
||||||
type Bus struct {
|
type Bus struct {
|
||||||
router *worker
|
router *worker
|
||||||
write chan publishedEvent
|
write chan publishedEvent
|
||||||
snapshot chan chan []publishedEvent
|
snapshot chan chan []publishedEvent
|
||||||
|
routeDebug hook[routedEvent]
|
||||||
|
|
||||||
topicsMu sync.Mutex // guards everything below.
|
topicsMu sync.Mutex // guards everything below.
|
||||||
topics map[reflect.Type][]*subscribeState
|
topics map[reflect.Type][]*subscribeState
|
||||||
@ -94,13 +105,23 @@ func (b *Bus) pump(ctx context.Context) {
|
|||||||
for !vals.Empty() {
|
for !vals.Empty() {
|
||||||
val := vals.Peek()
|
val := vals.Peek()
|
||||||
dests := b.dest(reflect.ValueOf(val.Event).Type())
|
dests := b.dest(reflect.ValueOf(val.Event).Type())
|
||||||
routed := time.Now()
|
|
||||||
|
if b.routeDebug.active() {
|
||||||
|
clients := make([]*Client, len(dests))
|
||||||
|
for i := range len(dests) {
|
||||||
|
clients[i] = dests[i].client
|
||||||
|
}
|
||||||
|
b.routeDebug.run(routedEvent{
|
||||||
|
Event: val.Event,
|
||||||
|
From: val.From,
|
||||||
|
To: clients,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for _, d := range dests {
|
for _, d := range dests {
|
||||||
evt := queuedEvent{
|
evt := queuedEvent{
|
||||||
Event: val.Event,
|
Event: val.Event,
|
||||||
From: val.From,
|
From: val.From,
|
||||||
Published: val.Published,
|
|
||||||
Routed: routed,
|
|
||||||
}
|
}
|
||||||
deliverOne:
|
deliverOne:
|
||||||
for {
|
for {
|
||||||
@ -113,6 +134,7 @@ func (b *Bus) pump(ctx context.Context) {
|
|||||||
break deliverOne
|
break deliverOne
|
||||||
case in := <-acceptCh():
|
case in := <-acceptCh():
|
||||||
vals.Add(in)
|
vals.Add(in)
|
||||||
|
in.From.publishDebug.run(in)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case ch := <-b.snapshot:
|
case ch := <-b.snapshot:
|
||||||
@ -129,8 +151,9 @@ func (b *Bus) pump(ctx context.Context) {
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case val := <-b.write:
|
case in := <-b.write:
|
||||||
vals.Add(val)
|
vals.Add(in)
|
||||||
|
in.From.publishDebug.run(in)
|
||||||
case ch := <-b.snapshot:
|
case ch := <-b.snapshot:
|
||||||
ch <- nil
|
ch <- nil
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
// Subscribers that share the same client receive events one at a
|
// Subscribers that share the same client receive events one at a
|
||||||
// time, in the order they were published.
|
// time, in the order they were published.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
name string
|
name string
|
||||||
bus *Bus
|
bus *Bus
|
||||||
|
publishDebug hook[publishedEvent]
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
pub set.Set[publisher]
|
pub set.Set[publisher]
|
||||||
|
@ -5,15 +5,8 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type publishedEvent struct {
|
|
||||||
Event any
|
|
||||||
From *Client
|
|
||||||
Published time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// publisher is a uniformly typed wrapper around Publisher[T], so that
|
// publisher is a uniformly typed wrapper around Publisher[T], so that
|
||||||
// debugging facilities can look at active publishers.
|
// debugging facilities can look at active publishers.
|
||||||
type publisher interface {
|
type publisher interface {
|
||||||
@ -60,9 +53,8 @@ func (p *Publisher[T]) Publish(v T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
evt := publishedEvent{
|
evt := publishedEvent{
|
||||||
Event: v,
|
Event: v,
|
||||||
From: p.client,
|
From: p.client,
|
||||||
Published: time.Now(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -8,14 +8,17 @@
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type deliveredEvent struct {
|
||||||
|
Event any
|
||||||
|
From *Client
|
||||||
|
To *Client
|
||||||
|
}
|
||||||
|
|
||||||
type queuedEvent struct {
|
type queuedEvent struct {
|
||||||
Event any
|
Event any
|
||||||
From *Client
|
From *Client
|
||||||
Published time.Time
|
|
||||||
Routed time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscriber is a uniformly typed wrapper around Subscriber[T], so
|
// subscriber is a uniformly typed wrapper around Subscriber[T], so
|
||||||
@ -46,6 +49,7 @@ type subscribeState struct {
|
|||||||
dispatcher *worker
|
dispatcher *worker
|
||||||
write chan queuedEvent
|
write chan queuedEvent
|
||||||
snapshot chan chan []queuedEvent
|
snapshot chan chan []queuedEvent
|
||||||
|
debug hook[deliveredEvent]
|
||||||
|
|
||||||
outputsMu sync.Mutex
|
outputsMu sync.Mutex
|
||||||
outputs map[reflect.Type]subscriber
|
outputs map[reflect.Type]subscriber
|
||||||
@ -82,6 +86,14 @@ func (q *subscribeState) pump(ctx context.Context) {
|
|||||||
if !sub.dispatch(ctx, &vals, acceptCh) {
|
if !sub.dispatch(ctx, &vals, acceptCh) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if q.debug.active() {
|
||||||
|
q.debug.run(deliveredEvent{
|
||||||
|
Event: val.Event,
|
||||||
|
From: val.From,
|
||||||
|
To: q.client,
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keep the cases in this select in sync with
|
// Keep the cases in this select in sync with
|
||||||
// Subscriber.dispatch below. The only different should be
|
// Subscriber.dispatch below. The only different should be
|
||||||
|
Loading…
Reference in New Issue
Block a user