def test_sync_online(self): user_id = "@foo:bar" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now - SYNC_ONLINE_TIMEOUT - 1, last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids={user_id}, now=now) self.assertIsNotNone(new_state) self.assertEquals(new_state.state, PresenceState.ONLINE)
def test_no_timeout(self): user_id = "@foo:bar" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now, last_user_sync_ts=now, last_federation_update_ts=now, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNone(new_state)
def test_idle_timer(self): user_id = "@foo:bar" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now - IDLE_TIMER - 1, last_user_sync_ts=now, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNotNone(new_state) self.assertEquals(new_state.state, PresenceState.UNAVAILABLE)
def test_federation_timeout(self): user_id = "@foo:bar" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now, last_user_sync_ts=now, last_federation_update_ts=now - FEDERATION_TIMEOUT - 1, ) new_state = handle_timeout( state, is_mine=False, syncing_user_ids=set(), now=now ) self.assertIsNotNone(new_state) self.assertEquals(new_state.state, PresenceState.OFFLINE)
def test_online_to_online_last_active_noop(self): wheel_timer = Mock() user_id = "@foo:bar" now = 5000000 prev_state = UserPresenceState.default(user_id) prev_state = prev_state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now - LAST_ACTIVE_GRANULARITY - 10, currently_active=True, ) new_state = prev_state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now ) state, persist_and_notify, federation_ping = handle_update( prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now ) self.assertFalse(persist_and_notify) self.assertTrue(federation_ping) self.assertTrue(state.currently_active) self.assertEquals(new_state.state, state.state) self.assertEquals(new_state.status_msg, state.status_msg) self.assertEquals(state.last_federation_update_ts, now) self.assertEquals(wheel_timer.insert.call_count, 3) wheel_timer.insert.assert_has_calls( [ call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER), call( now=now, obj=user_id, then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT, ), call( now=now, obj=user_id, then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY, ), ], any_order=True, )
def test_busy_no_idle(self): """ Tests that a user setting their presence to busy but idling doesn't turn their presence state into unavailable. """ user_id = "@foo:bar" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.BUSY, last_active_ts=now - IDLE_TIMER - 1, last_user_sync_ts=now, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNotNone(new_state) self.assertEquals(new_state.state, PresenceState.BUSY)
def test_last_active(self): user_id = "@foo:bar" status_msg = "I'm here!" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1, last_user_sync_ts=now, last_federation_update_ts=now, status_msg=status_msg, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNotNone(new_state) self.assertEquals(state, new_state)
def test_federation_ping(self): user_id = "@foo:bar" status_msg = "I'm here!" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now, last_user_sync_ts=now, last_federation_update_ts=now - FEDERATION_PING_INTERVAL - 1, status_msg=status_msg, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNotNone(new_state) self.assertEqual(state, new_state)
def test_online_to_offline(self): wheel_timer = Mock() user_id = "@foo:bar" now = 5000000 prev_state = UserPresenceState.default(user_id) prev_state = prev_state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=now, currently_active=True ) new_state = prev_state.copy_and_replace(state=PresenceState.OFFLINE) state, persist_and_notify, federation_ping = handle_update( prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now ) self.assertTrue(persist_and_notify) self.assertEquals(new_state.state, state.state) self.assertEquals(state.last_federation_update_ts, now) self.assertEquals(wheel_timer.insert.call_count, 0)
def test_sync_timeout(self): user_id = "@foo:bar" status_msg = "I'm here!" now = 5000000 state = UserPresenceState.default(user_id) state = state.copy_and_replace( state=PresenceState.ONLINE, last_active_ts=0, last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1, status_msg=status_msg, ) new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) self.assertIsNotNone(new_state) assert new_state is not None self.assertEqual(new_state.state, PresenceState.OFFLINE) self.assertEqual(new_state.status_msg, status_msg)
def test_online_to_idle(self): wheel_timer = Mock() user_id = "@foo:bar" now = 5000000 prev_state = UserPresenceState.default(user_id) prev_state = prev_state.copy_and_replace(state=PresenceState.ONLINE, last_active_ts=now, currently_active=True) new_state = prev_state.copy_and_replace( state=PresenceState.UNAVAILABLE) state, persist_and_notify, federation_ping = handle_update( prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now) self.assertTrue(persist_and_notify) self.assertEquals(new_state.state, state.state) self.assertEquals(state.last_federation_update_ts, now) self.assertEquals(new_state.state, state.state) self.assertEquals(new_state.status_msg, state.status_msg) self.assertEquals(wheel_timer.insert.call_count, 1) wheel_timer.insert.assert_has_calls( [ call( now=now, obj=user_id, then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT, ) ], any_order=True, )
def test_remote_ping_timer(self): wheel_timer = Mock() user_id = "@foo:bar" now = 5000000 prev_state = UserPresenceState.default(user_id) prev_state = prev_state.copy_and_replace(state=PresenceState.ONLINE, last_active_ts=now) new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE) state, persist_and_notify, federation_ping = handle_update( prev_state, new_state, is_mine=False, wheel_timer=wheel_timer, now=now) self.assertFalse(persist_and_notify) self.assertFalse(federation_ping) self.assertFalse(state.currently_active) self.assertEquals(new_state.state, state.state) self.assertEquals(new_state.status_msg, state.status_msg) self.assertEquals(wheel_timer.insert.call_count, 1) wheel_timer.insert.assert_has_calls( [ call( now=now, obj=user_id, then=new_state.last_federation_update_ts + FEDERATION_TIMEOUT, ) ], any_order=True, )
async def _update_states(self, new_states): """Updates presence of users. Sets the appropriate timeouts. Pokes the notifier and federation if and only if the changed presence state should be sent to clients/servers. """ now = self.clock.time_msec() with Measure(self.clock, "presence_update_states"): # NOTE: We purposefully don't await between now and when we've # calculated what we want to do with the new states, to avoid races. to_notify = {} # Changes we want to notify everyone about to_federation_ping = {} # These need sending keep-alives # Only bother handling the last presence change for each user new_states_dict = {} for new_state in new_states: new_states_dict[new_state.user_id] = new_state new_state = new_states_dict.values() for new_state in new_states: user_id = new_state.user_id # Its fine to not hit the database here, as the only thing not in # the current state cache are OFFLINE states, where the only field # of interest is last_active which is safe enough to assume is 0 # here. prev_state = self.user_to_current_state.get( user_id, UserPresenceState.default(user_id) ) new_state, should_notify, should_ping = handle_update( prev_state, new_state, is_mine=self.is_mine_id(user_id), wheel_timer=self.wheel_timer, now=now, ) self.user_to_current_state[user_id] = new_state if should_notify: to_notify[user_id] = new_state elif should_ping: to_federation_ping[user_id] = new_state # TODO: We should probably ensure there are no races hereafter presence_updates_counter.inc(len(new_states)) if to_notify: notified_presence_counter.inc(len(to_notify)) await self._persist_and_notify(list(to_notify.values())) self.unpersisted_users_changes |= {s.user_id for s in new_states} self.unpersisted_users_changes -= set(to_notify.keys()) to_federation_ping = { user_id: state for user_id, state in to_federation_ping.items() if user_id not in to_notify } if to_federation_ping: federation_presence_out_counter.inc(len(to_federation_ping)) self._push_to_remotes(to_federation_ping.values())