-
Notifications
You must be signed in to change notification settings - Fork 51
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
refactor: Use events to test network logic #2700
Conversation
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 need to head out but first pass looks really good. I love that you removed 1000 lines of code 🤘. I'll finish a more in depth review on Monday.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2700 +/- ##
===========================================
- Coverage 78.13% 78.11% -0.02%
===========================================
Files 311 310 -1
Lines 23238 23069 -169
===========================================
- Hits 18155 18019 -136
+ Misses 3699 3675 -24
+ Partials 1384 1375 -9
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 4 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
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 think we will need to chat about this one - it might be worth separating the changes made outside of the events
package from the rest - there are some very good changes in here that should not be delayed/confused with the events package implementation change.
Regarding the events package, it is now simpler, but it is also blocking (in user-space), and less reliable (it discards messages quite rapidly if the subscription buffer fills - the original buffer size was 100 + a 60 second timeout, it is now 10 items and no timeout) and as such I think I will require a bit of convincing in order to see this as a good change.
I do also disagree with the removal of the Channel
interface, it was used to allow us to swap out implementations without having to change the calling code. Whilst I think the original simple
channel is better than the proposal for our use-cases, it still needs to be improved upon long-term, and different use-cases within the defra code base would likely benefit from different Channel
implementations.
events/events.go
Outdated
Unsubscribe(Subscription[T]) | ||
// ConnectEvent is an event that is published when | ||
// a peer connection has changed status. | ||
type ConnectEvent = event.EvtPeerConnectednessChanged |
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.
suggestion: Since we are in the events
package, the Event
suffix isn't necessary. We could change events.ConnectEvent
, events.PubSubEvent
, events.UpdateEvent
, events.MergeEvent
to events.Connect
, events.PubSub
, events.Update
, events.Merge
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.
that would be neat. But for this, following best go practices, would be better to make package name singular event
.
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.
@nasdf you changed to event
but didn't do my suggestion. Did you just forget or did you decide you didn't like it? :)
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.
@fredcarle I'm considering moving the event definitions to their respective packages.
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.
Also a good option 👍
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.
The downside of defining the events in their respective packages is that if might result in unwanted dependencies or even circular dependencies depending in which package it is defined. MergeEvent
being defined in db
or net
for example.
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.
Good point. I've kept them in the package but renamed as you suggested.
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.
this is being discussed in another thread #2700 (comment) :)
events/events.go
Outdated
const ( | ||
// WildCardEventName is the alias used to subscribe to all events. | ||
WildCardEventName = "*" | ||
// MergeCompleteEventName is the name of the database merge complete event. | ||
MergeCompleteEventName = "db:merge-complete" | ||
// UpdateEventName is the name of the database update event. | ||
UpdateEventName = "db:update" | ||
// ResultsEventName is the name of the database results event. | ||
ResultsEventName = "db:results" | ||
// MergeRequestEventName is the name of the net merge request event. | ||
MergeRequestEventName = "net:merge" | ||
// PubSubEventName is the name of the network pubsub event. | ||
PubSubEventName = "net:pubsub" | ||
// ConnectEventName is the name of the network connect event. | ||
ConnectEventName = "net:connect" | ||
) |
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.
suggestion: wouldn't it be better to make name an enum (for more explicitness) and make every package (in this case db
and net
) own the events they generate.
This way this file won't need to change every time a new event in an unrelated package is added.
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'd like to keep the event names a human readable string for debugging purposes.
Having the events in one package does have the issue you mentioned, but it also makes it much easier to know what events can be subscribed to. There's also the issue of importing an entire package just for an event type.
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.
but the package that wants to listed to a specific event usually depends on the package that fires that event as it needs to receive the package-event-specific object.
I'd like to keep the event names a human readable string for debugging purposes
You can declare string enum:
type EventName string
and in db
package:
const (
// MergeCompleteEventName is the name of the database merge complete event.
MergeCompleteEventName = events.EventName("db:merge-complete")
// UpdateEventName is the name of the database update event.
UpdateEventName = events.EventName("db:update")
// ResultsEventName is the name of the database results event.
ResultsEventName = events.EventName("db:results")
)
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.
but the package that wants to listed to a specific event usually depends on the package that fires that event as it needs to receive the package-event-specific object.
This won't always be the case, and will cause problems since the db
package is internal.
You can declare string enum:
I've added an event.Name
type.
event/buffered_bus.go
Outdated
} | ||
b.commandChannel <- closeCommand{} | ||
// Wait for the close command to be handled, in order, before returning | ||
<-b.hasClosedChan |
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.
question: this is thread-safe only under the assumption that all the calls to bufferedBus
are done by the same thread. I'm not sure this is the case. Is it?
Otherwise the following scenario is possible:
- execution enters
Subscribe
method,b.isClosed == false
so it proceeds but pauses just beforeb.commandChannel <- subscribeCommand(sub)
- concurrently
Close
is being called, setsb.isClosed
totrue
, pushesb.commandChannel <- closeCommand{}
handleChannel
receivescloseCommand
and executesclose(b.commandChannel)
- now the thread from step 1 resumes, executes
b.commandChannel <- subscribeCommand(sub)
and causes a panic.
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've replaced the isClosed
atomic with a RWMutex
. Issue should be fixed now.
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.
That's quite a broad mutex, please don't merge yet
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.
It should only block when calling close.
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.
Okay, I think I understand, and there is no nicer way of handling this that I can spot off the top of my head.
todo: Can you please rename mutex
to closeLock
or similar, we really really don't want this to be used for anything else.
todo: Can please also document the lock on its declaration in bufferedBus
, explaining that it is only locked whilst closing.
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.
@AndrewSisley can you explain what you mean by broad? We use the same technique in the memory store and I think it's really fine for this as well.
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.
@AndrewSisley can you explain what you mean by broad? We use the same technique in the memory store and I think it's really fine for this as well.
The lock spans every public call to this struct, it can't really get any broader :) If it only locks whilst closing that's fine, but it would be very damaging if that were to change (hence the documentation and name change requested).
The current name (mutex
) is just begging for other places to lock it besides Close in the future.
event/buffered_bus.go
Outdated
|
||
case unsubscribeCommand: | ||
if _, ok := b.subs[t.id]; !ok { | ||
continue // not subscribed |
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.
question: Just pointing incase easy to test, that this path is not tested.
event/buffered_bus.go
Outdated
|
||
case publishCommand: | ||
for id := range b.events[WildCardName] { | ||
b.subs[id].value <- Message(t) |
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.
question: Just pointing incase easy to test, that this path is not tested.
event/buffered_bus.go
Outdated
} | ||
for id := range b.events[t.Name] { | ||
if _, ok := b.events[WildCardName][id]; ok { | ||
continue |
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.
question: Just pointing incase easy to test, that this path is not tested.
event/bus.go
Outdated
|
||
// Events returns the names of all subscribed events. | ||
func (s *Subscription) Events() []Name { | ||
return s.events |
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.
nitpick: This path is not tested.
event/buffered_bus_test.go
Outdated
defer bus.Close() | ||
|
||
sub, err := bus.Subscribe("test") | ||
assert.Nil(t, 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.
suggestion: it's better to use assert.NoError
because it produces better output
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.
It was copy pasted, but I've fixed them all.
event/buffered_bus_test.go
Outdated
assert.True(t, true) | ||
} | ||
|
||
func TestBufferedBusUnsubscribeTwice(t *testing.T) { |
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.
suggestion: some time ago we discussed a naming convention we want to use for our tests and agreed to name them using the following patter:
<test_group>_<condition>_<expectation>
.
For this particular test it might be something like TestBufferedBud_IfUnsubscribedTwice_[Succeed|NoError|NoPanic]
which makes it much more explicit.
For the test above TestBufferedBus_IfClosed_SubscribersAreNotBlocked
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've updated all the test names
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.
this is not quite what I meant.
The part is what makes this test different from others. It describes first 2 As (arrange and action).
The part describes the 3rd A: assert.
For example your new test name is TestBus_WildCardDeduplicates_Succeed
. In this case part might be IfSubscriptionsOverlap
or IfSubscribedToEventAndWildCard
or IfSubscribedToMultipleEvents
. The part could be ItShouldDeduplicate
or ShouldReceiveOnlyOneMessage
. So together it would be something like TestBus_IfSubscribedToEventAndWildCard_ItShouldDeduplicate
.
I'm not sure if I explained it clearly. So I will give some more examples how the current names can be transformed:
TestBus_EachSubscribersRecievesEachItem_Succeed
-> TestBus_WithMultipleSubscriptions_EachShouldReceiveMessage
or TestBus_UponPublishWhileMultipleSubscriptions_EachShouldReceiveMessage
TestBus_SubscribersDontRecieveItemsAfterUnsubscribing_Succeed
-> TestBus_AfterUnsubscribing_SubscribersDontRecieveItems
or TestBus_IfUnsubscribed_ShouldNotReceivePublishedMessages
Sorry for a long lecture. Just wanted to make sure it's clear.
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.
Updated
event/buffered_bus_test.go
Outdated
assert.NoError(t, err) | ||
|
||
bus.Unsubscribe(sub) | ||
bus.Unsubscribe(sub) |
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.
suggestion: if it is no panicking that you are asserting, why no to use assert.NotPanics
?
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.
This call itself won't panic because it is simply adding a message to the command channel which is processed asynchronously.
event/buffered_bus_test.go
Outdated
select { | ||
case <-sub.Message(): | ||
t.Errorf("should not receive duplicate message") | ||
case <-time.After(1 * time.Second): |
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.
todo: 1 second is too long. There is no reason for such a long wait.
Why not just use default
?:
select {
case <-sub.Message():
t.Errorf("should not receive duplicate message")
default:
}
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've decreased the delay. It's required because the publish is asynchronous.
event/buffered_bus_test.go
Outdated
assert.Equal(t, msg2, output2Ch2) | ||
} | ||
|
||
func TestBufferedBusSubscribersDontRecieveItemsAfterUnsubscribing(t *testing.T) { |
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.
praise: tests overall look good. Thanks for taking your time.
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.
LGTM! Thanks Keenan for the adjustments.
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 testing the requested paths.
LGTM
Relevant issue(s)
Resolves #2699
Resolves #2687
Description
This PR refactors the events package and updates the networking tests to use events.
I think there is a bit more clean up to be done, but the majority should be ready for review.
Tasks
How has this been tested?
make test
Specify the platform(s) on which this was tested: