-
Notifications
You must be signed in to change notification settings - Fork 178
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
[Access node] implement new splitter engine #947
Conversation
Co-authored-by: turbolent <turbolent@users.noreply.github.com> Co-authored-by: Janez Podhostnik <67895329+janezpodhostnik@users.noreply.github.com>
Co-authored-by: turbolent <turbolent@users.noreply.github.com> Co-authored-by: Janez Podhostnik <67895329+janezpodhostnik@users.noreply.github.com>
…/flow-go into smnzhu/multiplexer-engine
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.
Not sure whether this is suggestion is possible within the scope of this PR:
-
your
multiplexer.Engine
essentially only implements a multiplexing on theProcess
andSubmit
method, but not theSubmitLocal
orProcessLocal
:- From a high-level, engines are vertices in a data flow graph. The fact that the networking layer can feed data into engines is an auxiliary functionality, but not the primary purpose of engines.
- Your
multiplexer.Engine
is primarily an engine, but your implementation is focused on networking purposes only, neglecting other functions that are vital to an engine's interface (i.e.SubmitLocal
orProcessLocal
).
You are putting the channel as the engine's primary focus even though it should not be.
-
I think we have two options:
multiplexer.Engine
delegates all calls to the wrapped engines. I think this would be fine, as we can assemble any data flow pattern through multiplexers and engines that filter based on channel.- so instead of having one multiplexer that is "channel aware" (playmobil approach), create a multiplexer that only consumes messages for one channel. Thereby, the multiplexer can forward all calls to the engines it wraps. Then it would be truly implementing the
Engine
interface and multiplex all calls to the wrapped engines. - In the
multiplexer.network
you can create one dedicatedmultiplexer.Engine
per channel.
- so instead of having one multiplexer that is "channel aware" (playmobil approach), create a multiplexer that only consumes messages for one channel. Thereby, the multiplexer can forward all calls to the engines it wraps. Then it would be truly implementing the
- We implement a multiplexer for the
MessageProcessor
interface and remove theEngine
interface from the networking layer into themodule
package (there exists already an engine interface there, which is wrappingnetwork.Engine
)
engine/common/multiplexer/engine.go
Outdated
return e, nil | ||
} | ||
|
||
func (e *Engine) RegisterEngine(channel network.Channel, engine network.Engine) error { |
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 requires concurrency handling! With the current implementation, there is no guarantee whatsoever that a registered engine will ever receive any messages.
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.
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 in general looks great, I left a few comments.
engine/common/splitter/engine.go
Outdated
) | ||
|
||
type Engine struct { | ||
mu sync.RWMutex |
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.
Nit: something to indicate the coverage of the Mutex might help, e.g. enginesMu
engine/common/splitter/engine.go
Outdated
|
||
// process calls the given function in parallel for all the engines that have | ||
// registered with this splitter. | ||
func (e *Engine) process(f func(module.Engine) error) { |
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.
Nit: f
is the one argument I'd really like to track here, having it be a longer string would help.
// Register will subscribe the given engine with the spitter on the given channel, and all registered | ||
// engines will be notified with incoming messages on the channel. | ||
// The returned Conduit can be used to send messages to engines on other nodes subscribed to the same channel | ||
func (n *Network) Register(channel network.Channel, e network.Engine) (network.Conduit, error) { |
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.
OK, this may be a comment sitting across this PR and #984 but:
I see the appeal in a generic splitter, but I would also appreciate a strong godoc paragraph at the beginning of the engine that lays out where and when the splitter receives its channel processing obligations over time, and how it maintains them.
The topology of the splitter is clear, but to Vishal's point the channel responsibilities over the lifecycle of the component are a bit harder to tease out when channels are per-method parameters.
Another way to do this would be to write a mixed splitter / relay runnable example that would demonstrate this over time (esp. w/ unsubscribes), and this is stg we can defer to a further PR
Thanks for the suggestions @AlexHentschel, I knew that the distinction between I mostly agree with your point about splitter engine's
The As for the I think a possible alternative here is to still start a separate goroutine for each downstream engine, but capture all the errors and return them in a
Furthermore, using the I remember this book saying that goroutines are actually very lightweight, and we normally don't have to worry about creating them since they create minimal overhead. In the case of the splitter engine's However, I don't have a strong opinion on this given that we will probably be moving towards |
Codecov Report
@@ Coverage Diff @@
## master #947 +/- ##
========================================
Coverage 54.81% 54.81%
========================================
Files 277 279 +2
Lines 18548 18649 +101
========================================
+ Hits 10167 10223 +56
- Misses 7006 7047 +41
- Partials 1375 1379 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
@AlexHentschel please take a look at the latest changes and lmk what you think |
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.
👍
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 thought you could simplify the multierror logic with a multierror.Group
(from multierror 1.1), but I'm not happy with the logic in the WaitGroup
therein, and your approach is in the end fine.
Implements a new splitter engine described in https://github.com/dapperlabs/flow-go/issues/5669.
We also create a new splitter network implementation that can be passed into other engines to allow multiple engines to register for messages on the same network channel.
closes dapperlabs/flow-go#5669