Fix /initialSync error due to unhashable `RoomStreamToken` (#10827)

The deprecated /initialSync endpoint maintains a cache of responses,
using parameter values as part of the cache key. When a `from` or `to`
parameter is specified, it gets converted into a `StreamToken`, which
contains a `RoomStreamToken` and forms part of the cache key.
`RoomStreamToken`s need to be made hashable for this to work.
This commit is contained in:
Sean Quah 2021-09-22 14:43:26 +01:00 committed by GitHub
parent 52913d56a5
commit 9391de3f37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 19 additions and 6 deletions

1
changelog.d/10827.bugfix Normal file
View File

@ -0,0 +1 @@
Fix error in deprecated `/initialSync` endpoint when using the undocumented `from` and `to` parameters.

View File

@ -39,6 +39,8 @@ import logging
from collections import namedtuple from collections import namedtuple
from typing import TYPE_CHECKING, Collection, Dict, List, Optional, Set, Tuple from typing import TYPE_CHECKING, Collection, Dict, List, Optional, Set, Tuple
from frozendict import frozendict
from twisted.internet import defer from twisted.internet import defer
from synapse.api.filtering import Filter from synapse.api.filtering import Filter
@ -379,7 +381,7 @@ class StreamWorkerStore(EventsWorkerStore, SQLBaseStore, metaclass=abc.ABCMeta):
if p > min_pos if p > min_pos
} }
return RoomStreamToken(None, min_pos, positions) return RoomStreamToken(None, min_pos, frozendict(positions))
async def get_room_events_stream_for_rooms( async def get_room_events_stream_for_rooms(
self, self,

View File

@ -30,6 +30,7 @@ from typing import (
) )
import attr import attr
from frozendict import frozendict
from signedjson.key import decode_verify_key_bytes from signedjson.key import decode_verify_key_bytes
from unpaddedbase64 import decode_base64 from unpaddedbase64 import decode_base64
from zope.interface import Interface from zope.interface import Interface
@ -457,6 +458,9 @@ class RoomStreamToken:
Note: The `RoomStreamToken` cannot have both a topological part and an Note: The `RoomStreamToken` cannot have both a topological part and an
instance map. instance map.
For caching purposes, `RoomStreamToken`s and by extension, all their
attributes, must be hashable.
""" """
topological = attr.ib( topological = attr.ib(
@ -466,12 +470,12 @@ class RoomStreamToken:
stream = attr.ib(type=int, validator=attr.validators.instance_of(int)) stream = attr.ib(type=int, validator=attr.validators.instance_of(int))
instance_map = attr.ib( instance_map = attr.ib(
type=Dict[str, int], type="frozendict[str, int]",
factory=dict, factory=frozendict,
validator=attr.validators.deep_mapping( validator=attr.validators.deep_mapping(
key_validator=attr.validators.instance_of(str), key_validator=attr.validators.instance_of(str),
value_validator=attr.validators.instance_of(int), value_validator=attr.validators.instance_of(int),
mapping_validator=attr.validators.instance_of(dict), mapping_validator=attr.validators.instance_of(frozendict),
), ),
) )
@ -507,7 +511,7 @@ class RoomStreamToken:
return cls( return cls(
topological=None, topological=None,
stream=stream, stream=stream,
instance_map=instance_map, instance_map=frozendict(instance_map),
) )
except Exception: except Exception:
pass pass
@ -540,7 +544,7 @@ class RoomStreamToken:
for instance in set(self.instance_map).union(other.instance_map) for instance in set(self.instance_map).union(other.instance_map)
} }
return RoomStreamToken(None, max_stream, instance_map) return RoomStreamToken(None, max_stream, frozendict(instance_map))
def as_historical_tuple(self) -> Tuple[int, int]: def as_historical_tuple(self) -> Tuple[int, int]:
"""Returns a tuple of `(topological, stream)` for historical tokens. """Returns a tuple of `(topological, stream)` for historical tokens.
@ -593,6 +597,12 @@ class RoomStreamToken:
@attr.s(slots=True, frozen=True) @attr.s(slots=True, frozen=True)
class StreamToken: class StreamToken:
"""A collection of positions within multiple streams.
For caching purposes, `StreamToken`s and by extension, all their attributes,
must be hashable.
"""
room_key = attr.ib( room_key = attr.ib(
type=RoomStreamToken, validator=attr.validators.instance_of(RoomStreamToken) type=RoomStreamToken, validator=attr.validators.instance_of(RoomStreamToken)
) )