-
Notifications
You must be signed in to change notification settings - Fork 14
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
Unified Event interface #292
Comments
After some discussion with @matejpavlovic over Zoom, I think we are somewhat in agreement with something like this: type Event interface {
// Src returns the module that emitted the event.
// While this information is not always necessary for the system operation,
// it is useful for analyzing event traces and debugging.
Src() t.ModuleID
// Dest returns the destination module of the event.
Dest() t.ModuleID
// Marshal returns a serialized representation of the event
// as a slice of bytes from which the event can be reconstructed by Mir.
// Note that this means that the format of the output must be readable by Mir's event deserializer.
// Also note that Marshal does not necessarily guarantee the output to be deterministic.
// Even multiple subsequent calls to Marshal on the same Event object might return different byte slices.
Marshal() []byte
// DebugRepr returns a human-readable representation of the event.
// While not used by the runtime itself, it can be used by debug tools.
// Ideally, this representation should be JSON, to support advanced features of debug tools (like pretty printing).
DebugRepr() string
} Summary of changes:
That said, I'm not sure if it would be worth having the return type be
|
Nice, thanks for the summary! type Event interface {
// Src returns the module that emitted the event.
// While this information is not always necessary for the system operation,
// it is useful for analyzing event traces and debugging.
Src() t.ModuleID
// Dest returns the destination module of the event.
Dest() t.ModuleID
// Bytes returns a serialized representation of the event
// as a slice of bytes from which the event can be reconstructed by the module using the event.
// Note that Bytes does not necessarily guarantee the output to be deterministic.
// Even multiple subsequent calls to Bytes on the same event object might return different byte slices.
Bytes() []byte
// String returns a human-readable representation of the event.
// While not used by the runtime itself, it can be used by asociated tools.
// Ideally, this representation should be JSON, to support advanced features of debug tools (like pretty printing).
String() string
} Regarding the list of module IDs, I'm torn between having the possibility to (probably in rather rare cases) conveniently send the same event to multiple modules and simplifying code that deals with single destinations. E.g., answering the question "Is this event destined to this module?" turns into a simple comparison, as opposed to iterating over a slice. I have a slight tendency towards single destinations, but I'll think about it more. |
In my opinion, deciding on single vs multiple destinations should come down to how better/worse the runtime can perform. I say that, because we can move the problem of ergonomics to DSL functions (and have the destination for loop under them). If we keep the codegen approach to create these functions, this is a fairly easy change. Right now, I'm also inclined towards single destination. I'm skeptical on the possible savings in allocations of output events and event lists compensating the cost of the extra slice in each event. |
Yes I agree. Good point with the allocation overhead. I do expect the vast majority of events to go to only one module, and it's probably better to create a few copies of the same event from time to time then creating a one-element slice for every single event. |
Problem with the current approach
Mir currently uses Protocol Buffers to directly represent events. While this brings some advantage in terms of potential interoperability and, to a substantial extent, removes the need for explicit marshalling and unmarshalling, it requires the Mir framework itself to be aware of all the possible event types used by the modules (in order to be able to unmarshal all events). This is particularly problematic, since the goal of Mir is to be a stand-alone library independent of any protocol that is built on top of it and, in particular, all the different types of events that the protocol might define.
What we need
Events are all required to be serializable to allow for interoperability (e.g. using multiple programming languages), tracing, and debugging. At the same time, a very simple way of representing events (e.g. only in their serialized form) leads to inefficiencies when exchanging events within the same process (through shared memory). We should thus make sure that an event is only marshalled when necessary. (I.e., one module marshalling it and another module in the same process unmarshalling it, just because the Mir runtime cannot interpret it, is extremely wasteful).
We need a flexible yet efficient way of representing events in Mir. In particular, the approach should be general enough to allow a wide range of custom implementations of protocol-specific events while avoiding unnecessary marshalling and unmarshalling. In other words, Mir needs to be able to unmarshal (and perform basic operations on) an event without knowing its precise type. Thus, all events must be representable both by an object of the module-specific type and as the basic Mir event type, ideally without having to explicitly register module-specific event-processing handlers with the Mir runtime.
Proposed
Event
interfaceWe propose the following interface for a Mir event:
The set of methods allows for implementing events according to the requirements stated above.
The
IsRaw
andData
methods enable the encapsulation of any module-specific event payload in an opaque data slice that can be saved within a basic Mir-native implementation of theEvent
interface. Mir can then manipulate such a raw event, using only the basic functionality required to be implemented by every event (i.e., the methods defined by theEvent
interface). Once the raw event reaches a module that needs to process the event's data, the module can access the event data by callingData
, unmarshalling it, and so convert the raw module to a protocol-specific one. At the same time, theEvent
being aninterface
rather than a rigidstruct
, it can be handled by the Mir runtime without the need for marshalling the module-specific event data.Note on
t.EventDest
: At the time of writing this text, the destination of an event can only be a single module. Thus, in the current implementation,t.EventDest
could be replaced byt.ModuleID
. However, we envision a generalization of the event dispatching mechanism, where an event's destination could be one or more queues / buffers / channels, to which modules could be registered / subscribed.Note on the
IsRaw
method: One might argue that theIsRaw
method is not necessary and the information it provides could be encoded directly in the return value ofData
. E.g., in a programming language like Go, simply the actual type of the value could indicate whether it has to be unmarshalled or not - a byte slice is always unmarshalled, another type is used directly. There are, however, some issues with this approach.Data
would probably have to return avoid*
pointer that is later cast to the module-specific event type. Without further information, however, the caller would not know whether the pointer points to a byte array to be unmarshalled or to an already deserialized data structure ready for use.The text was updated successfully, but these errors were encountered: