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
Add concept of StatelessContext #3550
Closed
+199
−64
Closed
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6ac54da
Add concept of StatelessContext
erikjohnston 57d3fac
Newsfile
erikjohnston a20c8a9
isort
erikjohnston 311950e
Shuffle things around and clarify doc comments
erikjohnston 7350875
Merge branch 'develop' of github.com:matrix-org/synapse into erikj/st…
erikjohnston 5b7da74
Rename _have_fetched_state
erikjohnston cdfd92d
Add missing run_in_background
erikjohnston c451ce9
Fixup and remove spurious attributes
erikjohnston fecd7c2
Merge branch 'develop' of github.com:matrix-org/synapse into erikj/st…
erikjohnston 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 @@ | ||
Lazily load state on master process when using workers to reduce DB consumption |
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 |
---|---|---|
|
@@ -13,31 +13,24 @@ | |
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import abc | ||
|
||
from frozendict import frozendict | ||
|
||
from twisted.internet import defer | ||
|
||
from synapse.util.logcontext import make_deferred_yieldable | ||
|
||
|
||
class EventContext(object): | ||
class StatelessContext(object): | ||
""" | ||
Attributes: | ||
current_state_ids (dict[(str, str), str]): | ||
The current state map including the current event. | ||
(type, state_key) -> event_id | ||
|
||
prev_state_ids (dict[(str, str), str]): | ||
The current state map excluding the current event. | ||
(type, state_key) -> event_id | ||
|
||
state_group (int|None): state group id, if the state has been stored | ||
as a state group. This is usually only None if e.g. the event is | ||
an outlier. | ||
rejected (bool|str): A rejection reason if the event was rejected, else | ||
False | ||
|
||
push_actions (list[(str, list[object])]): list of (user_id, actions) | ||
tuples | ||
|
||
prev_group (int): Previously persisted state group. ``None`` for an | ||
outlier. | ||
delta_ids (dict[(str, str), str]): Delta from ``prev_group``. | ||
|
@@ -47,9 +40,9 @@ class EventContext(object): | |
the empty list? | ||
""" | ||
|
||
__metaclass__ = abc.ABCMeta | ||
|
||
__slots__ = [ | ||
"current_state_ids", | ||
"prev_state_ids", | ||
"state_group", | ||
"rejected", | ||
"prev_group", | ||
|
@@ -59,10 +52,6 @@ class EventContext(object): | |
] | ||
|
||
def __init__(self): | ||
# The current state including the current event | ||
self.current_state_ids = None | ||
# The current state excluding the current event | ||
self.prev_state_ids = None | ||
self.state_group = None | ||
|
||
self.rejected = False | ||
|
@@ -76,9 +65,61 @@ def __init__(self): | |
|
||
self.app_service = None | ||
|
||
# The current state including the current event | ||
self.current_state_ids = None | ||
# The current state excluding the current event | ||
self.prev_state_ids = None | ||
|
||
@abc.abstractmethod | ||
def get_current_state_ids(self, store): | ||
"""Gets the current state IDs | ||
|
||
Returns: | ||
Deferred[dict[(str, str), str]|None]: Returns None if state_group | ||
is None, which happens when the associated event is an outlier. | ||
""" | ||
raise NotImplementedError() | ||
|
||
@abc.abstractmethod | ||
def get_prev_state_ids(self, store): | ||
"""Gets the prev state IDs | ||
|
||
Returns: | ||
Deferred[dict[(str, str), str]|None]: Returns None if state_group | ||
is None, which happens when the associated event is an outlier. | ||
""" | ||
raise NotImplementedError() | ||
|
||
|
||
class EventContext(StatelessContext): | ||
"""This is the same as StatelessContext, except that | ||
current_state_ids and prev_state_ids are already calculated. | ||
|
||
Attributes: | ||
current_state_ids (dict[(str, str), str]|None): | ||
The current state map including the current event. | ||
(type, state_key) -> event_id | ||
Is None if event is an outlier | ||
|
||
prev_state_ids (dict[(str, str), str]|None): | ||
The current state map excluding the current event. | ||
(type, state_key) -> event_id` | ||
Is None if event is an outlier | ||
""" | ||
__slots__ = [ | ||
"current_state_ids", | ||
"prev_state_ids", | ||
] | ||
|
||
def __init__(self): | ||
super(EventContext, self).__init__() | ||
|
||
self.current_state_ids = None | ||
self.prev_state_ids = None | ||
|
||
def serialize(self, event): | ||
"""Converts self to a type that can be serialized as JSON, and then | ||
deserialized by `deserialize` | ||
deserialized by `DeserializedContext.deserialize` | ||
|
||
Args: | ||
event (FrozenEvent): The event that this context relates to | ||
|
@@ -108,46 +149,120 @@ def serialize(self, event): | |
"app_service_id": self.app_service.id if self.app_service else None | ||
} | ||
|
||
def get_current_state_ids(self, store): | ||
"""Implements StatelessContext""" | ||
return defer.succeed(self.current_state_ids) | ||
|
||
def get_prev_state_ids(self, store): | ||
"""Implements StatelessContext""" | ||
return defer.succeed(self.prev_state_ids) | ||
|
||
|
||
class DeserializedContext(StatelessContext): | ||
"""A context that comes from a serialized version of a StatelessContext. | ||
|
||
It does not necessarily have current_state_ids and prev_state_ids precomputed | ||
(unlike EventContext), but does cache the results of | ||
`get_current_state_ids` and `get_prev_state_ids`. | ||
|
||
Attributes: | ||
_have_fetched_state (Deferred|None): Resolves when *_state_ids have | ||
been calculated. None if we haven't started calculating yet | ||
_prev_state_id (str|None): If set then the event associated with the | ||
context overrode the _prev_state_id | ||
_event_type (str): The type of the event the context is associated with | ||
_event_state_key (str|None): The state_key of the event the context is | ||
associated with | ||
_current_state_ids (dict[(str, str), str]|None): | ||
The current state map including the current event. | ||
(type, state_key) -> event_id | ||
_prev_state_ids (dict[(str, str), str]|None): | ||
The current state map excluding the current event. | ||
(type, state_key) -> event_id` | ||
""" | ||
|
||
__slots__ = [ | ||
"_current_state_ids", | ||
"_prev_state_ids", | ||
"_have_fetched_state", | ||
"_prev_state_id", | ||
"_event_type", | ||
"_event_state_key", | ||
] | ||
|
||
@staticmethod | ||
@defer.inlineCallbacks | ||
def deserialize(store, input): | ||
"""Converts a dict that was produced by `serialize` back into a | ||
EventContext. | ||
StatelessContext. | ||
|
||
Args: | ||
store (DataStore): Used to convert AS ID to AS object | ||
input (dict): A dict produced by `serialize` | ||
|
||
Returns: | ||
EventContext | ||
DeserializedContext | ||
""" | ||
context = EventContext() | ||
context = DeserializedContext() | ||
context.state_group = input["state_group"] | ||
context.rejected = input["rejected"] | ||
context.prev_group = input["prev_group"] | ||
context.delta_ids = _decode_state_dict(input["delta_ids"]) | ||
context.prev_state_events = input["prev_state_events"] | ||
|
||
# We use the state_group and prev_state_id stuff to pull the | ||
# current_state_ids out of the DB and construct prev_state_ids. | ||
prev_state_id = input["prev_state_id"] | ||
event_type = input["event_type"] | ||
event_state_key = input["event_state_key"] | ||
context._prev_state_id = input["prev_state_id"] | ||
context._event_type = input["event_type"] | ||
context._event_state_key = input["event_state_key"] | ||
|
||
context.current_state_ids = yield store.get_state_ids_for_group( | ||
context.state_group, | ||
) | ||
if prev_state_id and event_state_key: | ||
context.prev_state_ids = dict(context.current_state_ids) | ||
context.prev_state_ids[(event_type, event_state_key)] = prev_state_id | ||
else: | ||
context.prev_state_ids = context.current_state_ids | ||
context._have_fetched_state = None | ||
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. if this is going to be a Deferred, then it needs a different name. |
||
context._current_state_ids = None | ||
context._prev_state_ids = None | ||
|
||
app_service_id = input["app_service_id"] | ||
if app_service_id: | ||
context.app_service = store.get_app_service_by_id(app_service_id) | ||
|
||
defer.returnValue(context) | ||
return context | ||
|
||
@defer.inlineCallbacks | ||
def get_current_state_ids(self, store): | ||
"""Implements StatelessContext""" | ||
|
||
if not self._have_fetched_state: | ||
self._have_fetched_state = self._fill_out_state(store) | ||
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. you need a |
||
|
||
yield make_deferred_yieldable(self._have_fetched_state) | ||
|
||
defer.returnValue(self.current_state_ids) | ||
|
||
@defer.inlineCallbacks | ||
def get_prev_state_ids(self, store): | ||
"""Implements StatelessContext""" | ||
|
||
if not self._have_fetched_state: | ||
self._have_fetched_state = self._fill_out_state(store) | ||
|
||
yield make_deferred_yieldable(self._have_fetched_state) | ||
|
||
defer.returnValue(self.current_state_ids) | ||
|
||
@defer.inlineCallbacks | ||
def _fill_out_state(self, store): | ||
"""Called to populate the _current_state_ids and _prev_state_ids | ||
attributes by loading from the database. | ||
""" | ||
if self.state_group is None: | ||
return | ||
|
||
self.current_state_ids = yield store.get_state_ids_for_group( | ||
self.state_group, | ||
) | ||
if self._prev_state_id and self._event_state_key is not None: | ||
self.prev_state_ids = dict(self.current_state_ids) | ||
|
||
key = (self._event_type, self._event_state_key) | ||
self.prev_state_ids[key] = self._prev_state_id | ||
else: | ||
self.prev_state_ids = self.current_state_ids | ||
|
||
|
||
def _encode_state_dict(state_dict): | ||
|
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
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 feels like a funny name, given it may have a state.
Indeed I wonder if having it separate from
EventContext
is worthwhile? maybe combine them and haveDeserializedContext
just override the accessors?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.
Unfortunately we can't just override accessors since they would need to return a deferreds in the DeserializedContext
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.
yes, but that's also true for
EventContext
. I don't understand why that means we can't override the accessors.