Skip to content
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

Draft: Refresh room timeline when we see a MSC2716 marker event #2282

Conversation

MadLittleMods
Copy link
Contributor

@MadLittleMods MadLittleMods commented Apr 8, 2022

Refresh room timeline when we see a MSC2716 marker event.

The "marker" events are being used as state now because this way they can't be lost in the timeline. Regardless of when they were sent, we will still have the latest version of the state to compare against. Any time we see our latest state value change for marker events, refresh the timeline.

In a sync meeting with @ara4n, we came up with the idea to make the marker events as state events. When the client sees that the m.room.marker state changed to a different event ID, it can throw away all of the timeline and re-fetch as needed.

For homeservers where the same problem can happen, we probably don't want to throw away the whole timeline but it can go up the unsigned.replaces_state chain of the m.room.marker state events to get them all.

In terms of state performance, there could be thousands of marker events in a room but it's no different than room members joining and leaving over and over like an IRC room.

-- matrix-org/matrix-spec-proposals#2716 (comment)

Todo

  • Add tests
    • When already joined to room, new marker event will cause timelineNeedsRefresh
    • marker event already sent in room, user joins room where the /sync state includes marker should NOT trigger timelineNeedsRefresh
    • When already joined to room, marker event already sent in room, initial /sync state includes marker should NOT trigger timelineNeedsRefresh
    • When syncFromCache where the state includes marker should NOT trigger timelineNeedsRefresh
    • In normal room version, marker from creator will cause timelineNeedsRefresh
    • In normal room version, marker not from creator should NOT trigger timelineNeedsRefresh
  • Add method to refresh the timeline to use in Ask to refresh timeline when historical messages are imported (MSC2716) matrix-react-sdk#8303 (comment)

Dev notes

yarn jest spec/integ/matrix-client-syncing.spec.js --silent=false --verbose=false

        Error.stackTraceLimit = Infinity;
        console.log(`setStateEvents timelineWasEmpty=${markerFoundOptions && markerFoundOptions.timelineWasEmpty} stateEvents:\n`, stateEvents.map((ev) => {
            return `\t${ev.getType()} ${ev.getId()}`;
        }).join('\n'), new Error().stack);

room.resetLiveTimeline(null, null);
addEventsToTimeline
addEventToTimeline
addLiveEvent

resetLiveTimeline

resetAllTimelines

removeFilteredTimelineSet


syncFromCache
processSyncResponse
processRoomEvents
room.addLiveEvents


sessionStore


processSyncResponse
processRoomEvents
initialiseState
setStateEvents

load
getEventTimeline
initialiseState
setStateEvents

Here's what your changelog entry will look like:

✨ Features

See matrix-org/matrix-spec-proposals#2716 (comment)

> In a [sync meeting with @ara4n](https://docs.google.com/document/d/1KCEmpnGr4J-I8EeaVQ8QJZKBDu53ViI7V62y5BzfXr0/edit#bookmark=id.67nio1ka8znc), we came up with the idea to make the `marker` events as state events. When the client sees that the `m.room.marker` state changed to a different event ID, it can throw away all of the timeline and re-fetch as needed.
>
> For homeservers where the [same problem](matrix-org/matrix-spec-proposals#2716 (comment)) can happen, we probably don't want to throw away the whole timeline but it can go up the `unsigned.replaces_state` chain of the `m.room.marker` state events to get them all.
>
> In terms of state performance, there could be thousands of `marker` events in a room but it's no different than room members joining and leaving over and over like an IRC room.
@MadLittleMods MadLittleMods requested a review from a team as a code owner April 8, 2022 03:19
@MadLittleMods MadLittleMods marked this pull request as draft April 8, 2022 03:19
src/sync.ts Outdated
) {
// Saw new marker event, refreshing the timeline
console.log('Saw new marker event, refreshing the timeline', new Error().stack);
room.resetLiveTimeline(null, null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Test if this is the function we actually want to use.

Our goal is to throw away the current timeline and re-fetch events for the room from the server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be better if we re-fetched the new events first, then replaced the timeline

Copy link
Contributor Author

@MadLittleMods MadLittleMods Apr 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this more, do we even want to do this automatically? Someone could cause your Element client to constantly refresh the timeline by just sending marker events over and over. Granted, you probably want to leave a room like this 🤷. Perhaps also some sort of DOS vector since everyone will be refreshing and hitting the server at the exact same time.

I think we might want to approach this to be a similar flow to how Element receives app updates. When the client receives a marker event, we first notify the user that history was imported and is available to see after they refresh their timeline (which can be a button action). The UI can probably use something similar to the "Connectivity to the server has been lost." banner.

In most cases, the history won't be in people's current timelines cached in the Element client, so do we even need to worry about refreshing? This is what I kinda leaned to before this PR but it's slightly important from a demo perspective and a personal bridge flow. For example with Beeper, a room is created for your Whatsapp conversation, and as you sit in the room, you probably ideally want the history to appear as it is imported but at least be apparent in some way after the import finishes.

We can detect whether the history imported affects any timelines on the client and only ask to refresh then. To detect, when we receive a marker event, make a request to /context/{insertionEventId} with the insertionEventId the marker is pointing to and check if any its prev_events are in the timeline. /context doesn't work over federation but the homeserver backfills the insertion event when it receives the marker so it already has it available.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to prompt the user to refresh their timeline: matrix-org/matrix-react-sdk#8303

We can detect whether the history imported affects any timelines on the client and only ask to refresh then. To detect, when we receive a marker event, make a request to /context/{insertionEventId} with the insertionEventId the marker is pointing to and check if any its prev_events are in the timeline. /context doesn't work over federation but the homeserver backfills the insertion event when it receives the marker so it already has it available.

We can't do this because prev_events aren't available in the client API's 😢. I'm not too keen to add an extra field to the marker or insertion event indicating where the history is imported next to as there isn't a way to enforce that it's true (a homeserver could lie and make it up). I'd rather keep the source of truth in the prev_events.

Copy link
Member

@turt2live turt2live left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review to clear codeowners. When leaving draft it'll need to be re-reviewed.

* 2. It's the first state we're seeing after joining the room
* 3. Or whether it's coming from `syncFromCache` */
fromInitialState?: boolean;
}
Copy link
Contributor Author

@MadLittleMods MadLittleMods Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of dealing with unwieldy long function signatures with unlabeled and optional arguments in the usage, I've refactored some of these into option objects now that I am adding yet another boolean here (adding fromInitialState).

Before:

room.addLiveEvents(mainTimelineEvents, null, fromCache, timelineWasEmpty);

After:

room.addLiveEvents(mainTimelineEvents, {
    fromCache,
    fromInitialState: timelineWasEmpty
});

If necessary, this refactor can be split out to another PR with some effort.

src/sync.ts Outdated Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Apr 13, 2022

Codecov Report

Merging #2282 (0694b84) into develop (9aab917) will decrease coverage by 0.09%.
The diff coverage is 46.57%.

❗ Current head 0694b84 differs from pull request most recent head 26fb2b6. Consider uploading reports for the commit 26fb2b6 to get more accurate results

@@             Coverage Diff             @@
##           develop    #2282      +/-   ##
===========================================
- Coverage    59.71%   59.62%   -0.10%     
===========================================
  Files           91       91              
  Lines        16439    16484      +45     
  Branches      3794     3808      +14     
===========================================
+ Hits          9817     9828      +11     
- Misses        6622     6656      +34     
Impacted Files Coverage Δ
src/@types/event.ts 100.00% <ø> (ø)
src/client.ts 38.04% <ø> (ø)
src/models/thread.ts 74.10% <ø> (ø)
src/sync.ts 63.85% <17.39%> (-1.73%) ⬇️
src/models/room.ts 56.71% <29.16%> (-0.63%) ⬇️
src/models/room-state.ts 77.25% <66.66%> (-0.16%) ⬇️
src/models/event-timeline-set.ts 79.38% <81.81%> (+0.23%) ⬆️
src/models/event-timeline.ts 93.75% <100.00%> (+0.05%) ⬆️

src/sync.ts Outdated
room.currentState.on(RoomStateEvent.Marker, async function(
markerEvent,
{ fromInitialState }: ISetStateOptions = {},
) {
Copy link
Contributor Author

@MadLittleMods MadLittleMods Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling out one of the main points of interest in the PR (new logic)

@@ -401,6 +414,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>

// assume all our sentinels are now out-of-date
this.sentinels = {};
} else if (event.getType() === EventType.Marker) {
this.emit(RoomStateEvent.Marker, event, setStateOptions);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we see a new marker state event, emit an event so we can listen for it in sync.ts -> registerStateListeners and see whether we need to refresh the timeline

@MadLittleMods
Copy link
Contributor Author

Closing in favor of #2299

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants