Validate local events once, against precompiled schemas

`jsonschema.validate` checks the schema against the metaschema on every
call. For power levels that cost more than validating the event: about
9 ms per `/createRoom`. Check the power levels and mentions schemas once
at import and validate with bound validator instances, reporting the same
best-match error.

`create_event` also ran `validate_new` a second time on the event that
`create_new_client_event` had just validated. Drop the repeat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
This commit is contained in:
Erik JohnstonandClaude Opus 5.5 committed 2026-10-18 16:09:03 +00:00
1 parent 4dd87ff58a
commit 2a407a8953
4 files changed
+39 -19

No files matched your search

+36 -16
View File
@@ -106,25 +106,14 @@ class EventValidator:
)
elif event.type == EventTypes.PowerLevels:
try:
jsonschema.validate(
instance=event.content,
schema=POWER_LEVELS_SCHEMA,
cls=POWER_LEVELS_VALIDATOR,
)
except jsonschema.ValidationError as e:
raise _validation_error_to_api_error(e)
_validate_against_schema(_POWER_LEVELS_SCHEMA_VALIDATOR, event.content)
# If the event contains a mentions key, validate it.
if EventContentFields.MENTIONS in event.content:
try:
jsonschema.validate(
instance=event.content[EventContentFields.MENTIONS],
schema=MENTIONS_SCHEMA,
cls=MENTIONS_VALIDATOR,
)
except jsonschema.ValidationError as e:
raise _validation_error_to_api_error(e)
_validate_against_schema(
_MENTIONS_SCHEMA_VALIDATOR,
event.content[EventContentFields.MENTIONS],
)
def _validate_retention(self, event: EventBase) -> None:
"""Checks that an event that defines the retention policy for a room respects the
@@ -329,3 +318,34 @@ def _validation_error_to_api_error(err: jsonschema.ValidationError) -> SynapseEr
POWER_LEVELS_VALIDATOR = _create_validator(POWER_LEVELS_SCHEMA)
MENTIONS_VALIDATOR = _create_validator(MENTIONS_SCHEMA)
def _compile_validator(
cls: type[jsonschema.Draft7Validator], schema: JsonDict
) -> jsonschema.Draft7Validator:
"""Check `schema` once and return a validator instance bound to it.
`jsonschema.validate` checks the schema against the metaschema on every
call, which costs more than validating a power levels event.
"""
cls.check_schema(schema)
return cls(schema)
_POWER_LEVELS_SCHEMA_VALIDATOR = _compile_validator(
POWER_LEVELS_VALIDATOR, POWER_LEVELS_SCHEMA
)
_MENTIONS_SCHEMA_VALIDATOR = _compile_validator(MENTIONS_VALIDATOR, MENTIONS_SCHEMA)
def _validate_against_schema(
validator: jsonschema.Draft7Validator, instance: object
) -> None:
"""Validate `instance`, raising a 400 `SynapseError` if it does not match.
Reports the same error as `jsonschema.validate` would.
"""
error = jsonschema.exceptions.best_match(validator.iter_errors(instance))
if error is not None:
raise _validation_error_to_api_error(error)
+1 -1
View File
@@ -800,7 +800,7 @@ class EventCreationHandler:
403, "You must be in the room to create an alias for it"
)
self.validator.validate_new(event, self.config)
# `create_new_client_event` has already run `validate_new` on the event.
return event, unpersisted_context
async def _is_exempt_from_privacy_policy(
+1 -1
View File
@@ -126,7 +126,7 @@ class TestBulkPushRuleEvaluator(HomeserverTestCase):
# some of this validation was missing in the past.
with (
patch("synapse.events.validator.validate_canonicaljson"),
patch("synapse.events.validator.jsonschema.validate"),
patch("synapse.events.validator._validate_against_schema"),
patch("synapse.handlers.event_auth.check_state_dependent_auth_rules"),
):
pl_event_id = self.helper.send_state(
+1 -1
View File
@@ -229,7 +229,7 @@ class UpgradeRoomTest(unittest.HomeserverTestCase):
# Synapse refuses to accept new stringy power level events. Bypass this by
# neutering the validation.
with patch("synapse.events.validator.jsonschema.validate"):
with patch("synapse.events.validator._validate_against_schema"):
# Note: https://github.com/matrix-org/matrix-spec/issues/853 plans to forbid
# string power levels in new rooms. For this test to have a clean
# conscience, we ought to ensure it's upgrading from a sufficiently old