Unfreeze event dict after check_event_allowed module callbacks#19547
Unfreeze event dict after check_event_allowed module callbacks#19547MatMaul wants to merge 6 commits intoelement-hq:developfrom
Conversation
| # Unfreeze the event dict to avoid potential issues with frozen dicts further | ||
| # down the code. Pydantic for example is not happy with frozen dicts. | ||
| # cf https://github.com/element-hq/synapse/issues/18117 | ||
| event._dict = unfreeze(event._dict) |
There was a problem hiding this comment.
Initial implementation was adding an unfreeze method to EventBase. I changed that since we really don't want this to be called inside the modules.
c9b87a9 to
4c86b67
Compare
| @@ -288,6 +289,9 @@ async def check_event_allowed( | |||
| # the hashes and signatures. | |||
| event.freeze() | |||
There was a problem hiding this comment.
We should also update the freeze() docstring to callout the specific use cases that it's meant for and that you should remember to unfreeze before going back to Synapse code, etc
| # Unfreeze the event dict to avoid potential issues with frozen dicts further | ||
| # down the code. Pydantic for example is not happy with frozen dicts. | ||
| # cf https://github.com/element-hq/synapse/issues/18117 | ||
| event._dict = unfreeze(event._dict) |
There was a problem hiding this comment.
Perhaps we should create new freeze_event(...) and unfreeze_event(...) functions in this file that handles this logic. That way the pair can better be associated together as event._dict is a pretty arbitrary detail.
The location in this file vs being event.freeze() will also prevent people from accidentally mis-using it elsewhere (leaking into other parts of the code that it shouldn't).
There was a problem hiding this comment.
Even one step further, we could use a context manager pattern for this with freeze_event(event) as frozen_event: ... so we never forget to unfreeze
| @@ -0,0 +1 @@ | |||
| Unfreeze event dict after check_event_allowed module callbacks. Frozen dicts can cause issues with Pydantic validation among other things. Contributed by @c-cal. | |||
There was a problem hiding this comment.
Currently, the PR description just says "Related to #18117" but I think this fixes the issue. Is that the case?
There was a problem hiding this comment.
Well it depends of how you look at it: we are fixing the problem for the module callback here, but we are not fixing the fact that a frozen event can be problematic in synapse code globally.
There was a problem hiding this comment.
hmmm, I see. I was jumping the gun since this is the only place we call event.freeze().
But we still have the USE_FROZEN_DICTS stuff which appears to do the same thing. At-least in this case, it's a self-inflicted configuration option and happens all the time, not just when a Synapse module is configured and some check_event_allowed callbacks are configured.
It's ironic that this config options says that it "prevents bugs" but introduces a whole new category of potential bugs.
| @@ -0,0 +1 @@ | |||
| Unfreeze event dict after check_event_allowed module callbacks. Frozen dicts can cause issues with Pydantic validation among other things. Contributed by @c-cal. | |||
There was a problem hiding this comment.
Feel free to add your own handle here as well
| @@ -0,0 +1 @@ | |||
| Unfreeze event dict after check_event_allowed module callbacks. Frozen dicts can cause issues with Pydantic validation among other things. Contributed by @c-cal. | |||
There was a problem hiding this comment.
Perhaps better 🤷 :
| Unfreeze event dict after check_event_allowed module callbacks. Frozen dicts can cause issues with Pydantic validation among other things. Contributed by @c-cal. | |
| Unfreeze event dict after module callbacks have run. Frozen dicts can cause issues with Pydantic validation and other type comparisons. Contributed by @c-cal. |
| event.freeze() | ||
|
|
||
| allow = True | ||
| event_dict = None |
There was a problem hiding this comment.
| event_dict = None | |
| replacement_event_dict = None |
|
|
||
| room_id = channel.json_body["room_id"] | ||
|
|
||
| event_id = self.create_and_send_event( |
There was a problem hiding this comment.
👍 Tested that this test fails when we comment out the unfreeze(...) fix
| event._dict = unfreeze(event._dict) | ||
|
|
||
| return True, None | ||
| return allow, event_dict |
There was a problem hiding this comment.
As an aside, I wonder if we have to be careful about someone passing back a frozen replacement_event_dict 🤔
Not something we necessarily have to concern ourselves with this PR but may be another problem. Perhaps we just need to tighten up the docstring details on what we expect passed back.
And hopefully, this doesn't naturally happen from someone taking the event, making a copy, mutating it, and passing it back.
| content={ | ||
| "body": "test", | ||
| "msgtype": "m.text", | ||
| "m.mentions": {}, |
There was a problem hiding this comment.
We should call out that this is the secret sauce that tests xxx and is important because xxx
| user: UserID, | ||
| soft_failed: bool = False, | ||
| prev_event_ids: list[str] | None = None, | ||
| content: dict[str, Any] | None = None, |
There was a problem hiding this comment.
From the new usage, it looks like this was done in order to be able to define m.mentions
Fix initially contributed in this PR: #18103
Fixes #18101
Pydantic validation was also failing if
m.mentionsis present (cf test).Related bug: #18117
Discussed in
#synapse-dev:matrix.orgPull Request Checklist