Remove uninitialized session state #1014
Conversation
855f669 to
7fefba4
Compare
Pull Request Test Coverage Report for Build 17583852116Details
💛 - Coveralls |
|
Seeking a concept ack/nack and an approach ack/nack. |
7fefba4 to
d83d478
Compare
nothingmuch
left a comment
There was a problem hiding this comment.
perhaps it'd be simpler to leave process_event as a completely internal detail, and replace the new functions with two variants:
- one that takes an iterator of the SessionEvent enum
- one that takes just a statically typed initialization event (e.g. CreatedReplyKey for sender)
and the first would call the second after scrutinizing the enum variant
but most importantly neither should be pub, at most pub(crate), receiver's new is private but not sender's, so long as they aren't pub then we can worry about refactoring later
d83d478 to
fedd47a
Compare
@nothingmuch Fixed the visibility issues. Thanks for pointing that out. I ended up scrutinizing the enum variant in |
There was a problem hiding this comment.
I think the suggestion from @nothingmuch was to also make process_event a purely private function and have a new pub(super) process_events(mut logs: Iterator<SessionEvent>) that encapsulates the for loop (copied below for clarity)
Details
let mut history = SessionHistory::default();
let first_event = logs.next().ok_or(InternalReplayError::NoEvents)?.into();
history.events.push(first_event.clone());
let mut receiver = match first_event {
SessionEvent::Created(context) => ReceiveSession::new(context),
_ => return Err(InternalReplayError::InvalidEvent(Box::new(first_event), None).into()),
};
for event in logs {
history.events.push(event.clone().into());
receiver = receiver.process_event(event.into()).map_err(|e| {
if let Err(storage_err) = persister.close() {
return InternalReplayError::PersistenceFailure(ImplementationError::new(
storage_err,
))
.into();
}
e
})?;
}Other than that this LGTM. That encapsulation could be a follow up.
|
@nothingmuch please drop another review |
ff86ed2 to
1329e1d
Compare
Unintialized should be an unreachable state and we are misusing it today. If one does not have any events in their log and replays then they will receive an uninitilaized receiver. Which is not useful. Not having any events and deciding to replay an session is most likely a bug in the application and should get an error (NoEvents). Additionally, bc sessionState is pub applications have to match on uninitialized when processing their receiver states -- instead you should receive an error when you have no events in your log. The other reason Unintialized existed is to having a starting state when replaying the SEL. Which we can resolve by having a special carve out for the first sessionState (SessionState::new).
Replay errors for both send and receive are almost identical. This commit creates a shared error abstraction with two generic params (SessionEvent, SessionState).
1329e1d to
e229cdd
Compare
spacebear21
left a comment
There was a problem hiding this comment.
utACK e229cdd
@arminsabouri I'm thinking we should merge this and address any additional feedback in follow-ups, since a few other PRs build on top of this one.
| let mut history = SessionHistory::default(); | ||
| let first_event = logs.next().ok_or(InternalReplayError::NoEvents)?.into(); | ||
| history.events.push(first_event.clone()); | ||
| let mut receiver = match first_event { | ||
| SessionEvent::Created(context) => ReceiveSession::new(context), | ||
| _ => return Err(InternalReplayError::InvalidEvent(Box::new(first_event), None).into()), | ||
| }; |
There was a problem hiding this comment.
I agree with @nothingmuch that this should be encapsulated in a process_events function that takes the [Into?]Iterator of events (aka logs) but that can be a follow up
There was a problem hiding this comment.
It could probably even be shared to de-duplicate code between the sender/receiver with some generic fn. Worth it if it's more beautiful and easier to maintain symmetry but not strictly neccessary
There was a problem hiding this comment.
agree on both counts (there is potential for refactoring but also since it's all private it doesn't need to be in this PR)
nothingmuch
left a comment
There was a problem hiding this comment.
ACK.
In regards to refactoring, I agree there seems to be some potential, but I can only envision doing that comprehensively with macros or heavy use of generics (for example shifting reply_key from the context to the typestate structs themselves is boilerplate heavy, but if we declare the state machine structs all at once then the macro could know that if a field is introduced in some state then all of the subsequent states also have the field). I'm not convinced it's worth the effort to try and remove all of these warts, some duplication or slightly hacky things are fine so long as they don't affect the public API, which this PR now simplifies.
|
|
||
| impl fmt::Display for ImplementationError { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { std::fmt::Display::fmt(&self.0, f) } |
There was a problem hiding this comment.
mainly out of curiosity what compelled this change?
There was a problem hiding this comment.
IIRC there was a ambigious definition for fmt once I imported Debug.
Unintialized should be an unreachable state and we are misusing it today. If one does not have any events in their log and replays then they will receive an uninitilaized receiver. Which is not useful. Not having any events and deciding to replay an session is most likely a bug in the application and should get an error (
NoEvents). Additionally, bc sessionState is pub applications have to match on uninitialized when processing their receiver states -- instead you should receive an error when you have no events in your log. The other reason Unintialized existed is to having a starting state when replaying the SEL. Which we can resolve by having a special carve out for the first sessionState (SessionState::new).Pull Request Checklist
Please confirm the following before requesting review:
AI
in the body of this PR.