This repository has been archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Factor out MultiWriter
token from RoomStreamToken
#16427
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Factor out `MultiWriter` token from `RoomStreamToken`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,8 @@ | |
from synapse.util.stringutils import parse_and_validate_server_name | ||
|
||
if TYPE_CHECKING: | ||
from typing_extensions import Self | ||
|
||
from synapse.appservice.api import ApplicationService | ||
from synapse.storage.databases.main import DataStore, PurgeEventsStore | ||
from synapse.storage.databases.main.appservice import ApplicationServiceWorkerStore | ||
|
@@ -436,7 +438,78 @@ def f2(m: Match[bytes]) -> bytes: | |
|
||
|
||
@attr.s(frozen=True, slots=True, order=False) | ||
class RoomStreamToken: | ||
class AbstractMultiWriterStreamToken(metaclass=abc.ABCMeta): | ||
"""An abstract stream token class for streams that supports multiple | ||
writers. | ||
|
||
This works by keeping track of the stream position of each writer, | ||
represented by a default `stream` attribute and a map of instance name to | ||
stream position of any writers that are ahead of the default stream | ||
position. | ||
""" | ||
|
||
stream: int = attr.ib(validator=attr.validators.instance_of(int), kw_only=True) | ||
|
||
instance_map: "immutabledict[str, int]" = attr.ib( | ||
factory=immutabledict, | ||
validator=attr.validators.deep_mapping( | ||
key_validator=attr.validators.instance_of(str), | ||
value_validator=attr.validators.instance_of(int), | ||
mapping_validator=attr.validators.instance_of(immutabledict), | ||
), | ||
kw_only=True, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OMG can we use |
||
) | ||
|
||
@classmethod | ||
@abc.abstractmethod | ||
async def parse(cls, store: "DataStore", string: str) -> "Self": | ||
"""Parse the string representation of the token.""" | ||
... | ||
|
||
@abc.abstractmethod | ||
async def to_string(self, store: "DataStore") -> str: | ||
"""Serialize the token into its string representation.""" | ||
... | ||
|
||
def copy_and_advance(self, other: "Self") -> "Self": | ||
"""Return a new token such that if an event is after both this token and | ||
the other token, then its after the returned token too. | ||
""" | ||
|
||
max_stream = max(self.stream, other.stream) | ||
|
||
instance_map = { | ||
instance: max( | ||
self.instance_map.get(instance, self.stream), | ||
other.instance_map.get(instance, other.stream), | ||
) | ||
for instance in set(self.instance_map).union(other.instance_map) | ||
} | ||
|
||
return attr.evolve( | ||
self, stream=max_stream, instance_map=immutabledict(instance_map) | ||
) | ||
|
||
def get_max_stream_pos(self) -> int: | ||
"""Get the maximum stream position referenced in this token. | ||
|
||
The corresponding "min" position is, by definition just `self.stream`. | ||
|
||
This is used to handle tokens that have non-empty `instance_map`, and so | ||
reference stream positions after the `self.stream` position. | ||
""" | ||
return max(self.instance_map.values(), default=self.stream) | ||
|
||
def get_stream_pos_for_instance(self, instance_name: str) -> int: | ||
"""Get the stream position that the given writer was at at this token.""" | ||
|
||
# If we don't have an entry for the instance we can assume that it was | ||
# at `self.stream`. | ||
return self.instance_map.get(instance_name, self.stream) | ||
|
||
|
||
@attr.s(frozen=True, slots=True, order=False) | ||
class RoomStreamToken(AbstractMultiWriterStreamToken): | ||
"""Tokens are positions between events. The token "s1" comes after event 1. | ||
|
||
s0 s1 | ||
|
@@ -513,16 +586,8 @@ class RoomStreamToken: | |
|
||
topological: Optional[int] = attr.ib( | ||
validator=attr.validators.optional(attr.validators.instance_of(int)), | ||
) | ||
stream: int = attr.ib(validator=attr.validators.instance_of(int)) | ||
|
||
instance_map: "immutabledict[str, int]" = attr.ib( | ||
factory=immutabledict, | ||
validator=attr.validators.deep_mapping( | ||
key_validator=attr.validators.instance_of(str), | ||
value_validator=attr.validators.instance_of(int), | ||
mapping_validator=attr.validators.instance_of(immutabledict), | ||
), | ||
kw_only=True, | ||
default=None, | ||
) | ||
|
||
def __attrs_post_init__(self) -> None: | ||
|
@@ -582,17 +647,7 @@ def copy_and_advance(self, other: "RoomStreamToken") -> "RoomStreamToken": | |
if self.topological or other.topological: | ||
raise Exception("Can't advance topological tokens") | ||
|
||
max_stream = max(self.stream, other.stream) | ||
|
||
instance_map = { | ||
instance: max( | ||
self.instance_map.get(instance, self.stream), | ||
other.instance_map.get(instance, other.stream), | ||
) | ||
for instance in set(self.instance_map).union(other.instance_map) | ||
} | ||
|
||
return RoomStreamToken(None, max_stream, immutabledict(instance_map)) | ||
return super().copy_and_advance(other) | ||
|
||
def as_historical_tuple(self) -> Tuple[int, int]: | ||
"""Returns a tuple of `(topological, stream)` for historical tokens. | ||
|
@@ -618,16 +673,6 @@ def get_stream_pos_for_instance(self, instance_name: str) -> int: | |
# at `self.stream`. | ||
return self.instance_map.get(instance_name, self.stream) | ||
|
||
def get_max_stream_pos(self) -> int: | ||
"""Get the maximum stream position referenced in this token. | ||
|
||
The corresponding "min" position is, by definition just `self.stream`. | ||
|
||
This is used to handle tokens that have non-empty `instance_map`, and so | ||
reference stream positions after the `self.stream` position. | ||
""" | ||
return max(self.instance_map.values(), default=self.stream) | ||
|
||
async def to_string(self, store: "DataStore") -> str: | ||
if self.topological is not None: | ||
return "t%d-%d" % (self.topological, self.stream) | ||
|
@@ -809,23 +854,28 @@ def copy_and_replace(self, key: str, new_value: Any) -> "StreamToken": | |
return attr.evolve(self, **{key: new_value}) | ||
|
||
|
||
StreamToken.START = StreamToken(RoomStreamToken(None, 0), 0, 0, 0, 0, 0, 0, 0, 0, 0) | ||
StreamToken.START = StreamToken(RoomStreamToken(stream=0), 0, 0, 0, 0, 0, 0, 0, 0, 0) | ||
|
||
|
||
@attr.s(slots=True, frozen=True, auto_attribs=True) | ||
class PersistedEventPosition: | ||
"""Position of a newly persisted event with instance that persisted it. | ||
|
||
This can be used to test whether the event is persisted before or after a | ||
RoomStreamToken. | ||
""" | ||
class PersistedPosition: | ||
"""Position of a newly persisted row with instance that persisted it.""" | ||
|
||
instance_name: str | ||
stream: int | ||
|
||
def persisted_after(self, token: RoomStreamToken) -> bool: | ||
def persisted_after(self, token: AbstractMultiWriterStreamToken) -> bool: | ||
return token.get_stream_pos_for_instance(self.instance_name) < self.stream | ||
|
||
|
||
@attr.s(slots=True, frozen=True, auto_attribs=True) | ||
class PersistedEventPosition(PersistedPosition): | ||
"""Position of a newly persisted event with instance that persisted it. | ||
|
||
This can be used to test whether the event is persisted before or after a | ||
RoomStreamToken. | ||
""" | ||
|
||
def to_room_stream_token(self) -> RoomStreamToken: | ||
"""Converts the position to a room stream token such that events | ||
persisted in the same room after this position will be after the | ||
|
@@ -836,7 +886,7 @@ def to_room_stream_token(self) -> RoomStreamToken: | |
""" | ||
# Doing the naive thing satisfies the desired properties described in | ||
# the docstring. | ||
return RoomStreamToken(None, self.stream) | ||
return RoomStreamToken(stream=self.stream) | ||
|
||
|
||
@attr.s(slots=True, frozen=True, auto_attribs=True) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 requires
typing_extensions >= 4.0
. Not sure how we feel about bumping that? Or just leaving it behindTYPE_CHECKING
?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'm happy as long as the packagers are. https://pkgs.org/search/?q=typing-extensions and https://repology.org/project/python:typing-extensions/versions. Debian buster and Ubuntu focal + jammy have 3.x; I can't remember if we've dropped support for those.
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.
Oh ISWYM. It'd be nice to be able to just import it, but I don't mind the conditional import and referencing it in "quotes" if that helps the packagers too. (ISTR we have to quote "defer.Deferred[blah]" anyway...
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 think putting it behind
TYPE_CHECKING
is fine.