-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add generics to subscriber and publisher and fix potential deadlock #602
Changes from all commits
0fde502
548db50
44112e1
df0be10
0cdc2b3
0180c2e
aaa853a
8eb7995
47ecbea
e615281
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,74 @@ | ||
package models | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
type Publisher struct { | ||
type Publisher[T any] struct { | ||
mux sync.RWMutex | ||
subscribers map[uuid.UUID]Subscriber | ||
subscribers map[Subscriber[T]]struct{} | ||
} | ||
|
||
func NewPublisher() *Publisher { | ||
return &Publisher{ | ||
func NewPublisher[T any]() *Publisher[T] { | ||
return &Publisher[T]{ | ||
mux: sync.RWMutex{}, | ||
subscribers: make(map[uuid.UUID]Subscriber), | ||
subscribers: make(map[Subscriber[T]]struct{}), | ||
} | ||
} | ||
|
||
func (p *Publisher) Publish(data any) { | ||
func (p *Publisher[T]) Publish(data T) { | ||
p.mux.RLock() | ||
defer p.mux.RUnlock() | ||
|
||
for _, s := range p.subscribers { | ||
for s := range p.subscribers { | ||
s.Notify(data) | ||
} | ||
} | ||
janezpodhostnik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
func (p *Publisher) Subscribe(s Subscriber) { | ||
func (p *Publisher[T]) Subscribe(s Subscriber[T]) { | ||
p.mux.Lock() | ||
defer p.mux.Unlock() | ||
|
||
p.subscribers[s.ID()] = s | ||
p.subscribers[s] = struct{}{} | ||
} | ||
|
||
func (p *Publisher) Unsubscribe(s Subscriber) { | ||
func (p *Publisher[T]) Unsubscribe(s Subscriber[T]) { | ||
p.mux.Lock() | ||
defer p.mux.Unlock() | ||
|
||
delete(p.subscribers, s.ID()) | ||
delete(p.subscribers, s) | ||
} | ||
|
||
type Subscriber interface { | ||
ID() uuid.UUID | ||
Notify(data any) | ||
type Subscriber[T any] interface { | ||
Notify(data T) | ||
Error() <-chan error | ||
} | ||
|
||
type Subscription struct { | ||
type Subscription[T any] struct { | ||
err chan error | ||
callback func(data any) error | ||
uuid uuid.UUID | ||
callback func(data T) error | ||
} | ||
|
||
func NewSubscription(callback func(any) error) *Subscription { | ||
return &Subscription{ | ||
func NewSubscription[T any](callback func(T) error) *Subscription[T] { | ||
return &Subscription[T]{ | ||
callback: callback, | ||
uuid: uuid.New(), | ||
err: make(chan error), | ||
} | ||
} | ||
|
||
func (b *Subscription) Notify(data any) { | ||
func (b *Subscription[T]) Notify(data T) { | ||
err := b.callback(data) | ||
if err != nil { | ||
b.err <- err | ||
select { | ||
case b.err <- err: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if the subscription cares about callback returning error or not. I think the subscription's responsibility is to deliver the data to the callback. If there is error, it's the callback's job to handle it, log it, or even crash. The callback has all the context of why this would error, so it's better being handled there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed! |
||
default: | ||
// TODO: handle this better! | ||
panic(fmt.Sprintf("failed to send error to subscription %v", err)) | ||
} | ||
} | ||
} | ||
|
||
func (b *Subscription) ID() uuid.UUID { | ||
return b.uuid | ||
} | ||
|
||
func (b *Subscription) Error() <-chan error { | ||
func (b *Subscription[T]) Error() <-chan error { | ||
return b.err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a bit hesitant about removing this assignment here. If I remember correctly, this was added by Gregor, due to some issue with the event streaming API. But on the other hand, the CI is passing, so maybe it's not needed after all 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the heads up, I'll take a look at the history, maybe there were some clues what specific problem this was solving.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't find anything, we can just merge it. It doesn't seem to break anything, and the E2E tests do exercise this part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@janezpodhostnik I did investigate this. What I found is:
has the format:
0x8b841deff1dbca0881c995ad77574d17
.I think Gregor changed it to
uuid
, to match the format of the event streaming API,e.g.:
69a20431-f601-43da-99f1-aa4bfd4d1bac
But we're using entirely the subscription functionality from
Geth
, so there's no need to go with theuuid
format.I think we're all good here. It would make sense though, to log the proper
subscription-id
, e.g.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed in 47ecbea. Please check if that is what you had in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, good idea adding both 👍