-
Notifications
You must be signed in to change notification settings - Fork 315
fix(timeline): maintain aggregations when an event is deduplicated #4576
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
Conversation
6c2393e
to
140ab9a
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4576 +/- ##
==========================================
- Coverage 85.73% 85.69% -0.05%
==========================================
Files 292 292
Lines 33492 33558 +66
==========================================
+ Hits 28715 28756 +41
- Misses 4777 4802 +25 ☔ View full report in Codecov by Sentry. |
e660de4
to
78d32b5
Compare
095a5ed
to
3d7feaa
Compare
… the event has been redacted
forever = the lifetime of a timeline.
…regation_redaction`
…eItemContent::reactions()` I give up; returning a reference would be better to avoid plain cloning, but there's no way to make a Lazy reference work on wasm, because the map transitively uses a field that's not sync/send :( Which is sad, because the default value would be an empty map, and not contain any data that wouldn't be sync/send anyways.
3d7feaa
to
c54dbfe
Compare
c54dbfe
to
da828c3
Compare
A live review party sounds good to me, and I've asked some other members of the Rust team whether they'd like to join because I'm not sure I should look at this on my own. |
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 love to see some tests for the Aggregations manager, but looks good!
let content = self.inner.content().with_reactions(reactions); | ||
|
||
// Do not use `Self::with_content` which may override the latest_edit_json. | ||
// TODO: it's likely incorrect? |
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 don't understand this comment - can you expand on it? If it's incorrect, do we need to fix it before we can commit this?
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.
Ah yeah, something I noticed, and fixed in another PR; I can remove the comment now, thanks for spotting it!
/// | ||
/// In case of error, returns an error detailing why the aggregation | ||
/// couldn't be applied. | ||
pub fn apply(&self, content: &mut TimelineItemContent) -> Result<bool, AggregationError> { |
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.
As discussed irl, returning an enum instead of the bool here would make it clearer, I 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.
Turned out better than I expected! The code in apply/unapply
is a bit uglier IMO, but that makes it much clearer for the callers. Thank you and @Hywan both for suggesting and pushing for this!
/// In case of success, returns a boolean indicating whether unapplying the | ||
/// aggregation caused a change in the content. If the aggregation could be | ||
/// unapplied, but it didn't cause any change in the passed | ||
/// [`TimelineItemContent`], `Ok(false)` will be returned. |
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 here, an enum return value would help to understand.
/// Mark a target event as being sent (i.e. it transitions from an local | ||
/// transaction id to its remote event id counterpart), by updating the | ||
/// internal mappings. | ||
pub fn mark_target_as_sent(&mut self, txn_id: OwnedTransactionId, event_id: OwnedEventId) { |
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 would be good to have some unit tests for this.
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.
Yep, will write a bunch in a subsequent PR!
} | ||
} else { | ||
warn!("missing related-to item ({target:?}) for aggregation {aggregation_id:?}"); |
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 probably not a warning since it is fine to happen.
…atrix-org#4576) ## Some context An aggregation is an event that relates to another event: for instance, a reaction, a poll response, and so on and so forth. ## Some requirements Because of the sync mechanisms and federation, it can happen that a related event is received *before* receiving the event it relates to. Those events must be accounted for, stashed somewhere, and reapplied later, if/when the related-to event shows up. In addition to that, a room's event cache can also decide to move events around, in its own internal representation (likely because it ran into some duplicate events, or it managed to decrypt a previously UTD event). When that happens, a timeline opened on the given room will see a removal then re-insertion of the given event. If that event was the target of aggregations, then those aggregations must be re-applied when the given event is reinserted. ## Some solution To satisfy both requirements, the [`Aggregations`] "manager" object provided by this PR will take care of memoizing aggregations, **for the entire lifetime of the timeline** (or until it's clear'd by some caller). Aggregations are saved in memory, and have the same lifetime as that of a timeline. This makes it possible to apply pending aggregations to cater for the first use case, and to never lose any aggregations in the second use case. ## Some points for the reviewer - I think the most controversial point is that all aggregations are memoized for the entire lifetime of the timeline. Would that become an issue, we can get back to some incremental scheme, in the future: instead of memoizing aggregations for the entire lifetime of the timeline, we'd attach them to a single timeline item. When that item is removed, we'd put the aggregations back into a "pending" stash of aggregations. If the item is reinserted later, we could peek at the pending stash of aggregations, remove any that's in there, and reapply them to the reinserted event. This is what the [first version of this patch](matrix-org@ec64b9e) did, in a much more adhoc way, for reactions only; based on the current PR, we could do the same in a simpler manner - while the PR has small commits, they don't quite make sense to review individually, I'm afraid, as I was trying to find a way to make a general system that would work not only for reactions, poll responses and ends. As a matter of fact, the first commits may have introduced code that is changed in subsequent commits, making the review a bit hazardous. Happy to have a live reviewing party over Element Call, if that helps, considering the size of the patch. - future work may include using the aggregations manager for edits too, leading to more code removal.
Some context
An aggregation is an event that relates to another event: for instance, a
reaction, a poll response, and so on and so forth.
Some requirements
Because of the sync mechanisms and federation, it can happen that a related
event is received before receiving the event it relates to. Those events
must be accounted for, stashed somewhere, and reapplied later, if/when the
related-to event shows up.
In addition to that, a room's event cache can also decide to move events
around, in its own internal representation (likely because it ran into some
duplicate events, or it managed to decrypt a previously UTD event).
When that happens, a timeline opened on the given room
will see a removal then re-insertion of the given event. If that event was
the target of aggregations, then those aggregations must be re-applied when
the given event is reinserted.
Some solution
To satisfy both requirements, the [
Aggregations
] "manager" object providedby this PR will take care of memoizing aggregations, for the entire
lifetime of the timeline (or until it's clear'd by some
caller). Aggregations are saved in memory, and have the same lifetime as
that of a timeline. This makes it possible to apply pending aggregations
to cater for the first use case, and to never lose any aggregations in the
second use case.
Some points for the reviewer
Part of #3280.