Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 34f6011

Browse files
committed
Track sync ts per-device.
1 parent d564b9e commit 34f6011

File tree

4 files changed

+35
-7
lines changed

4 files changed

+35
-7
lines changed

synapse/api/presence.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class UserDevicePresenceState:
2626
device_id: Optional[str]
2727
state: str
2828
last_active_ts: int
29+
last_sync_ts: int
2930

3031

3132
@attr.s(slots=True, frozen=True, auto_attribs=True)

synapse/handlers/presence.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ async def set_state(
264264
state: JsonDict,
265265
ignore_status_msg: bool = False,
266266
force_notify: bool = False,
267+
is_sync: bool = False,
267268
) -> None:
268269
"""Set the presence state of the user.
269270
@@ -273,6 +274,7 @@ async def set_state(
273274
ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
274275
If False, the user's current status will be updated.
275276
force_notify: Whether to force notification of the update to clients.
277+
is_sync: True if this update was from a sync
276278
"""
277279

278280
@abc.abstractmethod
@@ -516,6 +518,7 @@ async def user_syncing(
516518
device_id,
517519
state={"presence": presence_state},
518520
ignore_status_msg=True,
521+
is_sync=True,
519522
)
520523

521524
curr_sync = self._user_to_num_current_syncs.get(user_id, 0)
@@ -615,6 +618,7 @@ async def set_state(
615618
state: JsonDict,
616619
ignore_status_msg: bool = False,
617620
force_notify: bool = False,
621+
is_sync: bool = False,
618622
) -> None:
619623
"""Set the presence state of the user.
620624
@@ -624,6 +628,7 @@ async def set_state(
624628
ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
625629
If False, the user's current status will be updated.
626630
force_notify: Whether to force notification of the update to clients.
631+
is_sync: True if this update was from a sync
627632
"""
628633
presence = state["presence"]
629634

@@ -644,6 +649,7 @@ async def set_state(
644649
state=state,
645650
ignore_status_msg=ignore_status_msg,
646651
force_notify=force_notify,
652+
is_sync=is_sync,
647653
)
648654

649655
async def bump_presence_active_time(self, user: UserID) -> None:
@@ -1035,6 +1041,7 @@ async def user_syncing(
10351041
device_id,
10361042
state={"presence": presence_state},
10371043
ignore_status_msg=True,
1044+
is_sync=True,
10381045
)
10391046
# Retrieve the new state for the logic below. This should come from the
10401047
# in-memory cache.
@@ -1236,6 +1243,7 @@ async def set_state(
12361243
state: JsonDict,
12371244
ignore_status_msg: bool = False,
12381245
force_notify: bool = False,
1246+
is_sync: bool = False,
12391247
) -> None:
12401248
"""Set the presence state of the user.
12411249
@@ -1246,6 +1254,7 @@ async def set_state(
12461254
ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
12471255
If False, the user's current status will be updated.
12481256
force_notify: Whether to force notification of the update to clients.
1257+
is_sync: True if this update was from a sync
12491258
"""
12501259
status_msg = state.get("status_msg", None)
12511260
presence = state["presence"]
@@ -1258,6 +1267,7 @@ async def set_state(
12581267
return
12591268

12601269
user_id = target_user.to_string()
1270+
now = self.clock.time_msec()
12611271

12621272
prev_state = await self.current_state_for_user(user_id)
12631273

@@ -1271,10 +1281,13 @@ async def set_state(
12711281
device_id,
12721282
presence,
12731283
last_active_ts=self.clock.time_msec(),
1284+
last_sync_ts=0,
12741285
),
12751286
)
12761287
device_state.state = presence
1277-
device_state.last_active_ts = self.clock.time_msec()
1288+
device_state.last_active_ts = now
1289+
if is_sync:
1290+
device_state.last_sync_ts = now
12781291

12791292
# Based on (all) the user's devices calculate the new presence state.
12801293
presence = _combine_device_states(
@@ -1290,7 +1303,7 @@ async def set_state(
12901303
if presence == PresenceState.ONLINE or (
12911304
presence == PresenceState.BUSY and self._busy_presence_enabled
12921305
):
1293-
new_fields["last_active_ts"] = self.clock.time_msec()
1306+
new_fields["last_active_ts"] = now
12941307

12951308
await self._update_states(
12961309
[prev_state.copy_and_replace(**new_fields)], force_notify=force_notify
@@ -1887,7 +1900,7 @@ def handle_timeouts(
18871900
Args:
18881901
user_states: List of UserPresenceState's to check.
18891902
is_mine_fn: Function that returns if a user_id is ours
1890-
syncing_user_devices: Set of user_ids with active syncs.
1903+
syncing_user_devices: A map of user ID to device_ids with active syncs.
18911904
user_to_devices: A map of user ID to device ID to UserDevicePresenceState.
18921905
now: Current time in ms.
18931906
@@ -1955,15 +1968,16 @@ def handle_timeout(
19551968
if device_id not in syncing_device_ids:
19561969
# If the user has done something recently but hasn't synced,
19571970
# don't set them as offline.
1958-
#
1959-
# XXX last_user_sync_ts needs to be per-device.
19601971
sync_or_active = max(
1961-
state.last_user_sync_ts, device_state.last_active_ts
1972+
device_state.last_sync_ts, device_state.last_active_ts
19621973
)
19631974
if now - sync_or_active > SYNC_ONLINE_TIMEOUT:
19641975
device_state.state = PresenceState.OFFLINE
19651976
device_changed = True
19661977

1978+
# XXX How will this work when restoring from the database if device information
1979+
# is not kept?
1980+
19671981
# If the presence state of any of the devices changed, then (maybe) update
19681982
# the user's overall presence state.
19691983
if device_changed:

synapse/replication/http/presence.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,23 +99,27 @@ async def _serialize_payload( # type: ignore[override]
9999
state: JsonDict,
100100
ignore_status_msg: bool = False,
101101
force_notify: bool = False,
102+
is_sync: bool = False,
102103
) -> JsonDict:
103104
return {
104105
"device_id": device_id,
105106
"state": state,
106107
"ignore_status_msg": ignore_status_msg,
107108
"force_notify": force_notify,
109+
"is_sync": is_sync,
108110
}
109111

110112
async def _handle_request( # type: ignore[override]
111113
self, request: Request, content: JsonDict, user_id: str
112114
) -> Tuple[int, JsonDict]:
115+
# Older versions of Synapse won't set device_id and is_sync.
113116
await self._presence_handler.set_state(
114117
UserID.from_string(user_id),
115-
content["device_id"],
118+
content.get("device_id"),
116119
content["state"],
117120
content["ignore_status_msg"],
118121
content["force_notify"],
122+
content.get("is_sync", False),
119123
)
120124

121125
return (200, {})

tests/handlers/test_presence.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ def test_idle_timer(self) -> None:
367367
device_id=device_id,
368368
state=state.state,
369369
last_active_ts=state.last_active_ts,
370+
last_sync_ts=state.last_user_sync_ts,
370371
)
371372

372373
new_state = handle_timeout(
@@ -404,6 +405,7 @@ def test_busy_no_idle(self) -> None:
404405
device_id=device_id,
405406
state=state.state,
406407
last_active_ts=state.last_active_ts,
408+
last_sync_ts=state.last_user_sync_ts,
407409
)
408410

409411
new_state = handle_timeout(
@@ -437,6 +439,7 @@ def test_sync_timeout(self) -> None:
437439
device_id=device_id,
438440
state=state.state,
439441
last_active_ts=state.last_active_ts,
442+
last_sync_ts=state.last_user_sync_ts,
440443
)
441444

442445
new_state = handle_timeout(
@@ -470,6 +473,7 @@ def test_sync_online(self) -> None:
470473
device_id=device_id,
471474
state=state.state,
472475
last_active_ts=state.last_active_ts,
476+
last_sync_ts=state.last_user_sync_ts,
473477
)
474478

475479
new_state = handle_timeout(
@@ -504,6 +508,7 @@ def test_federation_ping(self) -> None:
504508
device_id=device_id,
505509
state=state.state,
506510
last_active_ts=state.last_active_ts,
511+
last_sync_ts=state.last_user_sync_ts,
507512
)
508513

509514
new_state = handle_timeout(
@@ -534,6 +539,7 @@ def test_no_timeout(self) -> None:
534539
device_id=device_id,
535540
state=state.state,
536541
last_active_ts=state.last_active_ts,
542+
last_sync_ts=state.last_user_sync_ts,
537543
)
538544

539545
new_state = handle_timeout(
@@ -589,6 +595,7 @@ def test_last_active(self) -> None:
589595
device_id=device_id,
590596
state=state.state,
591597
last_active_ts=state.last_active_ts,
598+
last_sync_ts=state.last_user_sync_ts,
592599
)
593600

594601
new_state = handle_timeout(
@@ -801,6 +808,7 @@ def test_set_presence_from_syncing_is_set(self) -> None:
801808
# we should now be online
802809
self.assertEqual(state.state, PresenceState.ONLINE)
803810

811+
# TODO Add tests with BUSY.
804812
@parameterized.expand(
805813
# A list of tuples of 4 strings:
806814
#
@@ -952,6 +960,7 @@ def test_set_presence_from_syncing_multi_device(
952960
)
953961
self.assertEqual(state.state, expected_state_3)
954962

963+
# TODO Add tests with BUSY.
955964
@parameterized.expand(
956965
# A list of tuples of 4 strings:
957966
#

0 commit comments

Comments
 (0)