Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

lazyload aware /messages #3589

Merged
merged 73 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
7b7fd27
untested attempt at deduplicating lazy-loaded members
ara4n Jun 3, 2018
c341d81
Merge branch 'develop' into matthew/remove_redundant_lazy_members
ara4n Jun 10, 2018
f7bd5da
add include_redundant_members filter option & make it work
ara4n Jun 10, 2018
589e5aa
merge and apply isort
ara4n Jul 19, 2018
8e66dd1
merge in #2970
ara4n Jul 19, 2018
a08b37b
fix bad merge
ara4n Jul 19, 2018
7362e6c
make /context lazyload & filter aware
ara4n Jul 20, 2018
cd28d2f
speed up /members and add at= and membership params
ara4n Jul 20, 2018
a17f0b6
make it work
ara4n Jul 20, 2018
9ba6ef2
Merge branch 'matthew/lazy_load_apis' into matthew/members_at
ara4n Jul 20, 2018
c6117fa
make it work
ara4n Jul 20, 2018
8f1585d
make filtering work
ara4n Jul 21, 2018
42308c0
initial cut at a room summary API
ara4n Jul 23, 2018
0beeecf
remove debug log
ara4n Jul 23, 2018
63ce31b
namespace the summary fields correctly
ara4n Jul 23, 2018
c8cbede
fix key typo
bwindels Jul 23, 2018
f9c3c26
Merge branch 'matthew/filter_members' into matthew/remove_redundant_l…
ara4n Jul 23, 2018
c2870ab
Merge branch 'matthew/remove_redundant_lazy_members' into matthew/laz…
ara4n Jul 23, 2018
ffb7a4c
Merge branch 'matthew/lazy_load_apis' into matthew/members_at
ara4n Jul 23, 2018
c1c6ff1
Merge branch 'matthew/members_at' into matthew/room_summary
ara4n Jul 23, 2018
7d99b0e
changelog
ara4n Jul 23, 2018
cd27a77
changelog
ara4n Jul 23, 2018
4018a6d
changelog
ara4n Jul 23, 2018
c238a88
changelog
ara4n Jul 23, 2018
e1daddd
first cut at lazyload aware /messages
ara4n Jul 23, 2018
dd2c0e1
make it work
ara4n Jul 23, 2018
40e61d5
changelog
ara4n Jul 23, 2018
1ba3683
return the correct counts & self-exclude from heros
ara4n Jul 24, 2018
f129390
spell heroes correctly
ara4n Jul 24, 2018
bfbebec
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Jul 24, 2018
4c2edd1
remember to filter state events
ara4n Jul 24, 2018
e61071a
fix heroes definition to match MSC
ara4n Jul 24, 2018
d32e5f8
Merge branch 'matthew/filter_members' into matthew/remove_redundant_l…
ara4n Jul 24, 2018
238f750
deduplicating redundant members via event_id rather than mxid
ara4n Jul 24, 2018
2a79e1a
Merge branch 'matthew/remove_redundant_lazy_members' into matthew/laz…
ara4n Jul 24, 2018
48f3e43
Merge branch 'matthew/lazy_load_apis' into matthew/members_at
ara4n Jul 24, 2018
7cc81ed
Merge branch 'matthew/members_at' into matthew/room_summary
ara4n Jul 24, 2018
96d1f3c
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Jul 24, 2018
08af91d
fix merge fail
ara4n Jul 24, 2018
e76254b
Merge branch 'matthew/members_at' into matthew/room_summary
ara4n Jul 24, 2018
4e33d06
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Jul 24, 2018
58529df
Merge branch 'matthew/room_summary' of github.com:matrix-org/synapse …
erikjohnston Jul 27, 2018
9637ae4
Merge branch 'matthew/lazy_load_messages_api' of git+ssh://github.com…
ara4n Jul 27, 2018
d5b1842
fix missing import
ara4n Jul 27, 2018
e952368
incorporate review
ara4n Jul 30, 2018
423c512
add null check for state
ara4n Aug 7, 2018
2d9c062
Merge branch 'develop' into matthew/members_at
ara4n Aug 12, 2018
eebee08
convert /members?at= to take a stream token
ara4n Aug 12, 2018
5df2c36
Merge branch 'develop' into matthew/room_summary
ara4n Aug 12, 2018
d0c0d72
incorporate all the review feedback
ara4n Aug 12, 2018
b327e07
fix use of get_recent_event_ids_for_room
ara4n Aug 12, 2018
c6fd400
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Aug 12, 2018
9e4acb7
Merge branch 'matthew/members_at' into matthew/room_summary
ara4n Aug 12, 2018
e235005
add cap neg to /versions for LL
ara4n Aug 12, 2018
db333cc
fix english
ara4n Aug 12, 2018
32bf4fa
return early rather than big if blocks
ara4n Aug 14, 2018
859ad35
incorporate PR review
ara4n Aug 14, 2018
ab31a44
Merge branch 'matthew/members_at' into matthew/lazy_load_messages_api
ara4n Aug 14, 2018
dd4498f
Merge branch 'develop' into matthew/members_at
ara4n Aug 14, 2018
53cce23
Merge branch 'matthew/members_at' into matthew/lazy_load_messages_api
ara4n Aug 14, 2018
65d4521
don't remove old versions from /versions for now
ara4n Aug 15, 2018
fe9ed88
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Aug 15, 2018
217d5dd
Merge branch 'develop' into matthew/members_at
ara4n Aug 15, 2018
8442478
Merge branch 'matthew/members_at' into matthew/room_summary
ara4n Aug 15, 2018
0cbe5a2
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Aug 15, 2018
f5189b9
remove incorrectly reintroduced method
richvdh Aug 15, 2018
0d5770d
cleanups
richvdh Aug 15, 2018
c2d9c96
Merge branch 'matthew/members_at' into matthew/room_summary
richvdh Aug 15, 2018
dc085c0
Merge remote-tracking branch 'origin/develop' into matthew/room_summary
richvdh Aug 15, 2018
7306074
remove spurious changelogs
richvdh Aug 15, 2018
c3cdc21
factor out get_lazy_loaded_members_cache
ara4n Aug 15, 2018
33fdfa7
Merge branch 'matthew/room_summary' into matthew/lazy_load_messages_api
ara4n Aug 15, 2018
62d8ce7
Merge branch 'develop' into matthew/lazy_load_messages_api
richvdh Aug 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3331.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add support for the include_redundant_members filter param as per MSC1227
1 change: 1 addition & 0 deletions changelog.d/3567.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
make the /context API filter & lazy-load aware as per MSC1227
1 change: 1 addition & 0 deletions changelog.d/3568.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
speed up /members API and add `at` and `membership` params as per MSC1227
1 change: 1 addition & 0 deletions changelog.d/3574.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implement `summary` block in /sync response as per MSC688
1 change: 1 addition & 0 deletions changelog.d/3589.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add lazy-loading support to /messages as per MSC1227
52 changes: 48 additions & 4 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,49 @@ def get_room_data(self, user_id=None, room_id=None,
defer.returnValue(data)

@defer.inlineCallbacks
def get_state_events(self, user_id, room_id, is_guest=False):
def _check_in_room_or_world_readable(self, room_id, user_id):
try:
# check_user_was_in_room will return the most recent membership
# event for the user if:
# * The user is a non-guest user, and was ever in the room
# * The user is a guest user, and has joined the room
# else it will throw.
member_event = yield self.auth.check_user_was_in_room(room_id, user_id)
defer.returnValue((member_event.membership, member_event.event_id))
return
except AuthError:
visibility = yield self.state_handler.get_current_state(
room_id, EventTypes.RoomHistoryVisibility, ""
)
if (
visibility and
visibility.content["history_visibility"] == "world_readable"
):
defer.returnValue((Membership.JOIN, None))
return
raise AuthError(
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
)

@defer.inlineCallbacks
def get_state_events(
self, user_id, room_id, types=None, filtered_types=None,
at_event=None, is_guest=False
):
"""Retrieve all state events for a given room. If the user is
joined to the room then return the current state. If the user has
left the room return the state events from when they left.
left the room return the state events from when they left. If an explicit
'at' parameter is passed, return the state events as of that event.

Args:
user_id(str): The user requesting state events.
room_id(str): The room ID to get all state events from.
types(list[(Str, (Str|None))]): the (type, state_key)s to return
results for.
filtered_types(list[Str]): the list of types to apply the types filter
to.
at_event(str): the event_id we are requesting the state as of
is_guest(Boolean): whether this user is a guest
Returns:
A list of dicts representing state events. [{}, {}, {}]
"""
Expand All @@ -98,10 +133,19 @@ def get_state_events(self, user_id, room_id, is_guest=False):
)

if membership == Membership.JOIN:
room_state = yield self.state.get_current_state(room_id)
if at_event:
room_state = yield self.store.get_state_for_events(
[at_event], types, filtered_types=filtered_types
)
room_state = room_state[at_event]
else:
state_ids = yield self.store.get_filtered_current_state_ids(
room_id, types, filtered_types=filtered_types
)
room_state = yield self.store.get_events(state_ids.values())
elif membership == Membership.LEAVE:
room_state = yield self.store.get_state_for_events(
[membership_event_id], None
[membership_event_id], types, filtered_types=filtered_types
)
room_state = room_state[membership_event_id]

Expand Down
35 changes: 34 additions & 1 deletion synapse/handlers/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from twisted.internet import defer
from twisted.python.failure import Failure

from synapse.api.constants import Membership
from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import SynapseError
from synapse.events.utils import serialize_event
from synapse.types import RoomStreamToken
Expand Down Expand Up @@ -251,6 +251,33 @@ def get_messages(self, requester, room_id=None, pagin_config=None,
is_peeking=(member_event_id is None),
)

state = None
if event_filter and event_filter.lazy_load_members():
# TODO: remove redundant members

types = [
(EventTypes.Member, state_key)
for state_key in set(
event.sender # FIXME: we also care about invite targets etc.
for event in events
)
]

state_ids = yield self.store.get_state_ids_for_event(
events[0].event_id, types=types,
)

if state_ids:
state = yield self.store.get_events(list(state_ids.values()))

if state:
state = yield filter_events_for_client(
self.store,
user_id,
state.values(),
is_peeking=(member_event_id is None),
)

time_now = self.clock.time_msec()

chunk = {
Expand All @@ -262,4 +289,10 @@ def get_messages(self, requester, room_id=None, pagin_config=None,
"end": next_token.to_string(),
}

if state:
chunk["state"] = [
serialize_event(e, time_now, as_client_event)
for e in state
]

defer.returnValue(chunk)
137 changes: 134 additions & 3 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
"ephemeral",
"account_data",
"unread_notifications",
"summary",
])):
__slots__ = []

Expand Down Expand Up @@ -494,10 +495,128 @@ def get_state_at(self, room_id, stream_position, types=None, filtered_types=None
state = {}
defer.returnValue(state)

@defer.inlineCallbacks
def compute_summary(self, room_id, sync_config, batch, state, now_token):
""" Works out a room summary block for this room, summarising the number
of joined members in the room, and providing the 'hero' members if the
room has no name so clients can consistently name rooms. Also adds
state events to 'state' if needed to describe the heroes.

Args:
room_id(str):
sync_config(synapse.handlers.sync.SyncConfig):
batch(synapse.handlers.sync.TimelineBatch): The timeline batch for
the room that will be sent to the user.
state(dict): dict of (type, state_key) -> Event as returned by
compute_state_delta
now_token(str): Token of the end of the current batch.

Returns:
A deferred dict describing the room summary
"""

# FIXME: this promulgates https://github.com/matrix-org/synapse/issues/3305
last_events, _ = yield self.store.get_recent_event_ids_for_room(
room_id, end_token=now_token.room_key, limit=1,
)

if not last_events:
defer.returnValue(None)
return

last_event = last_events[-1]
state_ids = yield self.store.get_state_ids_for_event(
last_event.event_id, [
(EventTypes.Member, None),
(EventTypes.Name, ''),
(EventTypes.CanonicalAlias, ''),
]
)

member_ids = {
state_key: event_id
for (t, state_key), event_id in state_ids.iteritems()
if t == EventTypes.Member
}
name_id = state_ids.get((EventTypes.Name, ''))
canonical_alias_id = state_ids.get((EventTypes.CanonicalAlias, ''))

summary = {}

# FIXME: it feels very heavy to load up every single membership event
# just to calculate the counts.
member_events = yield self.store.get_events(member_ids.values())

joined_user_ids = []
invited_user_ids = []

for ev in member_events.values():
if ev.content.get("membership") == Membership.JOIN:
joined_user_ids.append(ev.state_key)
elif ev.content.get("membership") == Membership.INVITE:
invited_user_ids.append(ev.state_key)

# TODO: only send these when they change.
summary["m.joined_member_count"] = len(joined_user_ids)
summary["m.invited_member_count"] = len(invited_user_ids)

if not name_id and not canonical_alias_id:
# FIXME: order by stream ordering, not alphabetic

me = sync_config.user.to_string()
if (joined_user_ids or invited_user_ids):
summary['m.heroes'] = sorted(
[
user_id
for user_id in (joined_user_ids + invited_user_ids)
if user_id != me
]
)[0:5]
else:
summary['m.heroes'] = sorted(
[user_id for user_id in member_ids.keys() if user_id != me]
)[0:5]

if sync_config.filter_collection.lazy_load_members():
# ensure we send membership events for heroes if needed
cache_key = (sync_config.user.to_string(), sync_config.device_id)
cache = self.lazy_loaded_members_cache.get(cache_key)

# track which members the client should already know about via LL:
# Ones which are already in state...
existing_members = set(
user_id for (typ, user_id) in state.keys()
if typ == EventTypes.Member
)

# ...or ones which are in the timeline...
for ev in batch.events:
if ev.type == EventTypes.Member:
existing_members.add(ev.state_key)

# ...and then ensure any missing ones get included in state.
missing_hero_event_ids = [
member_ids[hero_id]
for hero_id in summary['m.heroes']
if (
cache.get(hero_id) != member_ids[hero_id] and
hero_id not in existing_members
)
]

missing_hero_state = yield self.store.get_events(missing_hero_event_ids)
missing_hero_state = missing_hero_state.values()

for s in missing_hero_state:
cache.set(s.state_key, s.event_id)
state[(EventTypes.Member, s.state_key)] = s

defer.returnValue(summary)

@defer.inlineCallbacks
def compute_state_delta(self, room_id, batch, sync_config, since_token, now_token,
full_state):
""" Works out the differnce in state between the start of the timeline
""" Works out the difference in state between the start of the timeline
and the previous sync.

Args:
Expand All @@ -511,7 +630,7 @@ def compute_state_delta(self, room_id, batch, sync_config, since_token, now_toke
full_state(bool): Whether to force returning the full state.

Returns:
A deferred new event dictionary
A deferred dict of (type, state_key) -> Event
"""
# TODO(mjark) Check if the state events were received by the server
# after the previous sync, since we need to include those state
Expand Down Expand Up @@ -1416,7 +1535,6 @@ def _generate_room_entry(self, sync_result_builder, ignored_users,
if events == [] and tags is None:
return

since_token = sync_result_builder.since_token
now_token = sync_result_builder.now_token
sync_config = sync_result_builder.sync_config

Expand Down Expand Up @@ -1459,6 +1577,18 @@ def _generate_room_entry(self, sync_result_builder, ignored_users,
full_state=full_state
)

summary = {}
if (
sync_config.filter_collection.lazy_load_members() and
(
any(ev.type == EventTypes.Member for ev in batch.events) or
since_token is None
)
):
summary = yield self.compute_summary(
room_id, sync_config, batch, state, now_token
)

if room_builder.rtype == "joined":
unread_notifications = {}
room_sync = JoinedSyncResult(
Expand All @@ -1468,6 +1598,7 @@ def _generate_room_entry(self, sync_result_builder, ignored_users,
ephemeral=ephemeral,
account_data=account_data_events,
unread_notifications=unread_notifications,
summary=summary,
)

if room_sync or always_include:
Expand Down
21 changes: 19 additions & 2 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,30 @@ def __init__(self, hs):
def on_GET(self, request, room_id):
# TODO support Pagination stream API (limit/tokens)
requester = yield self.auth.get_user_by_req(request)
events = yield self.message_handler.get_state_events(
handler = self.message_handler

# request the state as of a given event
# useful for synchronising with /messages
at_event = parse_string(request, "at")

# let you filter down on particular memberships
membership = parse_string(request, "membership")
not_membership = parse_string(request, "not_membership")

events = yield handler.get_state_events(
room_id=room_id,
user_id=requester.user.to_string(),
at_event=at_event,
types=[(EventTypes.Member, None)],
)

chunk = []

for event in events:
if event["type"] != EventTypes.Member:
if (
(membership and event['content'].get("membership") != membership) or
(not_membership and event['content'].get("membership") == not_membership)
):
continue
chunk.append(event)

Expand All @@ -401,6 +416,8 @@ def on_GET(self, request, room_id):
}))


# deprecated in favour of /members?membership=join?
# except it does custom AS logic and has a simpler return format
class JoinedRoomMemberListRestServlet(ClientV1RestServlet):
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$")

Expand Down
1 change: 1 addition & 0 deletions synapse/rest/client/v2_alpha/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ def serialize(event):
ephemeral_events = room.ephemeral
result["ephemeral"] = {"events": ephemeral_events}
result["unread_notifications"] = room.unread_notifications
result["summary"] = room.summary

return result

Expand Down
14 changes: 10 additions & 4 deletions synapse/rest/client/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ class VersionsRestServlet(RestServlet):
def on_GET(self, request):
return (200, {
"versions": [
"r0.0.1",
"r0.1.0",
"r0.2.0",
# we don't need to include the minor values any more, as we've defined
# X.Y.Z to be backwards-compatible with X.(Y-1).* where Y>1
Copy link
Member

Choose a reason for hiding this comment

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

Was this the conclusion of that conversation? I thought for r0 we were saying Y was effectively the major version.

Copy link
Member Author

Choose a reason for hiding this comment

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

i was saying this, but on closer inspection it turned out we’d never defined it as such.

Copy link
Member

Choose a reason for hiding this comment

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

I think this needs a bit more discussion, which should probably happen outside of the comment box on github.

r0.3.0 isn't compatible with r0.2.0 (push rules changed, some APIs removed, event formats changed) and if I recall correctly r0.3.0 was released intentionally as r0 given the constraints of the API.

Copy link
Member Author

Choose a reason for hiding this comment

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

agreed. @richvdh, wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

yes I agree this needs more discussion :/.

For example, presumably the change to push rules strictly means that we don't support < r0.2.0 any more.

My inclination would be to avoid poking at this hornet's nest for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

have unpoked.

# "r0.0.1",
# "r0.1.0",
# "r0.2.0",
"r0.3.0",
]
],
# as per MSC1497:
"unstable_features": {
"m.lazy_load_members": True,
}
Copy link
Member

Choose a reason for hiding this comment

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

trailing comma would be nice...

})


Expand Down
Loading