mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-07-27 02:35:19 -04:00
Fix MSC3030 /timestamp_to_event
returning outliers
that it has no idea whether are near a gap or not (#14215)
Fix MSC3030 `/timestamp_to_event` endpoint returning `outliers` that it has no idea whether are near a gap or not (and therefore unable to determine whether it's actually the closest event). The reason Synapse doesn't know whether an `outlier` is next to a gap is because our gap checks rely on entries in the `event_edges`, `event_forward_extremeties`, and `event_backward_extremities` tables which is [not the case for `outliers`](2c63cdcc3f/docs/development/room-dag-concepts.md (outliers)
). Also fixes MSC3030 Complement `can_paginate_after_getting_remote_event_from_timestamp_to_event_endpoint` test flake. Although this acted flakey in Complement, if `sync_partial_state` raced and beat us before `/timestamp_to_event`, then even if we retried the failing `/context` request it wouldn't work until we made this Synapse change. With this PR, Synapse will never return an `outlier` event so that test will always go and ask over federation. Fix https://github.com/matrix-org/synapse/issues/13944 ### Why did this fail before? Why was it flakey? Sleuthing the server logs on the [CI failure](5121449357 (step)
:5:5805), it looks like `hs2:/timestamp_to_event` found `$NP6-oU7mIFVyhtKfGvfrEQX949hQX-T-gvuauG6eurU` as an `outlier` event locally. Then when we went and asked for it via `/context`, since it's an `outlier`, it was filtered out of the results -> `You don't have permission to access that event.` This is reproducible when `sync_partial_state` races and persists `$NP6-oU7mIFVyhtKfGvfrEQX949hQX-T-gvuauG6eurU` as an `outlier` before we evaluate `get_event_for_timestamp(...)`. To consistently reproduce locally, just add a delay at the [start of `get_event_for_timestamp(...)`](cb20b885cb/synapse/handlers/room.py (L1470-L1496)
) so it always runs after `sync_partial_state` completes. ```py from twisted.internet import task as twisted_task d = twisted_task.deferLater(self.hs.get_reactor(), 3.5) await d ``` In a run where it passes, on `hs2`, `get_event_for_timestamp(...)` finds a different event locally which is next to a gap and we request from a closer one from `hs1` which gets backfilled. And since the backfilled event is not an `outlier`, it's returned as expected during `/context`. With this PR, Synapse will never return an `outlier` event so that test will always go and ask over federation.
This commit is contained in:
parent
2a76a7369f
commit
fa8616e65c
3 changed files with 104 additions and 21 deletions
|
@ -1971,12 +1971,17 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
|
||||
Args:
|
||||
room_id: room where the event lives
|
||||
event_id: event to check
|
||||
event: event to check (can't be an `outlier`)
|
||||
|
||||
Returns:
|
||||
Boolean indicating whether it's an extremity
|
||||
"""
|
||||
|
||||
assert not event.internal_metadata.is_outlier(), (
|
||||
"is_event_next_to_backward_gap(...) can't be used with `outlier` events. "
|
||||
"This function relies on `event_backward_extremities` which won't be filled in for `outliers`."
|
||||
)
|
||||
|
||||
def is_event_next_to_backward_gap_txn(txn: LoggingTransaction) -> bool:
|
||||
# If the event in question has any of its prev_events listed as a
|
||||
# backward extremity, it's next to a gap.
|
||||
|
@ -2026,12 +2031,17 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
|
||||
Args:
|
||||
room_id: room where the event lives
|
||||
event_id: event to check
|
||||
event: event to check (can't be an `outlier`)
|
||||
|
||||
Returns:
|
||||
Boolean indicating whether it's an extremity
|
||||
"""
|
||||
|
||||
assert not event.internal_metadata.is_outlier(), (
|
||||
"is_event_next_to_forward_gap(...) can't be used with `outlier` events. "
|
||||
"This function relies on `event_edges` and `event_forward_extremities` which won't be filled in for `outliers`."
|
||||
)
|
||||
|
||||
def is_event_next_to_gap_txn(txn: LoggingTransaction) -> bool:
|
||||
# If the event in question is a forward extremity, we will just
|
||||
# consider any potential forward gap as not a gap since it's one of
|
||||
|
@ -2112,13 +2122,33 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
The closest event_id otherwise None if we can't find any event in
|
||||
the given direction.
|
||||
"""
|
||||
if direction == "b":
|
||||
# Find closest event *before* a given timestamp. We use descending
|
||||
# (which gives values largest to smallest) because we want the
|
||||
# largest possible timestamp *before* the given timestamp.
|
||||
comparison_operator = "<="
|
||||
order = "DESC"
|
||||
else:
|
||||
# Find closest event *after* a given timestamp. We use ascending
|
||||
# (which gives values smallest to largest) because we want the
|
||||
# closest possible timestamp *after* the given timestamp.
|
||||
comparison_operator = ">="
|
||||
order = "ASC"
|
||||
|
||||
sql_template = """
|
||||
sql_template = f"""
|
||||
SELECT event_id FROM events
|
||||
LEFT JOIN rejections USING (event_id)
|
||||
WHERE
|
||||
origin_server_ts %s ?
|
||||
AND room_id = ?
|
||||
room_id = ?
|
||||
AND origin_server_ts {comparison_operator} ?
|
||||
/**
|
||||
* Make sure the event isn't an `outlier` because we have no way
|
||||
* to later check whether it's next to a gap. `outliers` do not
|
||||
* have entries in the `event_edges`, `event_forward_extremeties`,
|
||||
* and `event_backward_extremities` tables to check against
|
||||
* (used by `is_event_next_to_backward_gap` and `is_event_next_to_forward_gap`).
|
||||
*/
|
||||
AND NOT outlier
|
||||
/* Make sure event is not rejected */
|
||||
AND rejections.event_id IS NULL
|
||||
/**
|
||||
|
@ -2128,27 +2158,14 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
* Finally, we can tie-break based on when it was received on the server
|
||||
* (`stream_ordering`).
|
||||
*/
|
||||
ORDER BY origin_server_ts %s, depth %s, stream_ordering %s
|
||||
ORDER BY origin_server_ts {order}, depth {order}, stream_ordering {order}
|
||||
LIMIT 1;
|
||||
"""
|
||||
|
||||
def get_event_id_for_timestamp_txn(txn: LoggingTransaction) -> Optional[str]:
|
||||
if direction == "b":
|
||||
# Find closest event *before* a given timestamp. We use descending
|
||||
# (which gives values largest to smallest) because we want the
|
||||
# largest possible timestamp *before* the given timestamp.
|
||||
comparison_operator = "<="
|
||||
order = "DESC"
|
||||
else:
|
||||
# Find closest event *after* a given timestamp. We use ascending
|
||||
# (which gives values smallest to largest) because we want the
|
||||
# closest possible timestamp *after* the given timestamp.
|
||||
comparison_operator = ">="
|
||||
order = "ASC"
|
||||
|
||||
txn.execute(
|
||||
sql_template % (comparison_operator, order, order, order),
|
||||
(timestamp, room_id),
|
||||
sql_template,
|
||||
(room_id, timestamp),
|
||||
)
|
||||
row = txn.fetchone()
|
||||
if row:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue