示例#1
0
    def get_states(self, target_user_ids, as_event=False):
        """Get the presence state for users.

        Args:
            target_user_ids (list)
            as_event (bool): Whether to format it as a client event or not.

        Returns:
            list
        """

        updates = yield self.current_state_for_users(target_user_ids)
        updates = updates.values()

        for user_id in set(target_user_ids) - set(u.user_id for u in updates):
            updates.append(UserPresenceState.default(user_id))

        now = self.clock.time_msec()
        if as_event:
            defer.returnValue([
                {
                    "type": "m.presence",
                    "content": _format_user_presence_state(state, now),
                }
                for state in updates
            ])
        else:
            defer.returnValue([
                _format_user_presence_state(state, now) for state in updates
            ])
示例#2
0
    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)
示例#3
0
    def _handle_timeouts(self):
        """Checks the presence of users that have timed out and updates as
        appropriate.
        """
        now = self.clock.time_msec()

        with Measure(self.clock, "presence_handle_timeouts"):
            # Fetch the list of users that *may* have timed out. Things may have
            # changed since the timeout was set, so we won't necessarily have to
            # take any action.
            users_to_check = self.wheel_timer.fetch(now)

            states = [
                self.user_to_current_state.get(
                    user_id, UserPresenceState.default(user_id)
                )
                for user_id in set(users_to_check)
            ]

            timers_fired_counter.inc_by(len(states))

            changes = handle_timeouts(
                states,
                is_mine_fn=self.hs.is_mine_id,
                user_to_num_current_syncs=self.user_to_num_current_syncs,
                now=now,
            )

        preserve_fn(self._update_states)(changes)
示例#4
0
    def current_state_for_users(self, user_ids):
        """Get the current presence state for multiple users.

        Returns:
            dict: `user_id` -> `UserPresenceState`
        """
        states = {
            user_id: self.user_to_current_state.get(user_id, None)
            for user_id in user_ids
        }

        missing = [user_id for user_id, state in states.items() if not state]
        if missing:
            # There are things not in our in memory cache. Lets pull them out of
            # the database.
            res = yield self.store.get_presence_for_users(missing)
            states.update({state.user_id: state for state in res})

            missing = [user_id for user_id, state in states.items() if not state]
            if missing:
                new = {
                    user_id: UserPresenceState.default(user_id)
                    for user_id in missing
                }
                states.update(new)
                self.user_to_current_state.update(new)

        defer.returnValue(states)
示例#5
0
    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)
示例#6
0
    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)
示例#7
0
    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)
示例#8
0
    def test_sync_timeout(self):
        user_id = "@foo:bar"
        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,
        )

        new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)

        self.assertIsNotNone(new_state)
        self.assertEquals(new_state.state, PresenceState.OFFLINE)
示例#9
0
文件: presence.py 项目: 0-T-0/synapse
    def _handle_timeouts(self):
        """Checks the presence of users that have timed out and updates as
        appropriate.
        """
        logger.info("Handling presence timeouts")
        now = self.clock.time_msec()

        try:
            with Measure(self.clock, "presence_handle_timeouts"):
                # Fetch the list of users that *may* have timed out. Things may have
                # changed since the timeout was set, so we won't necessarily have to
                # take any action.
                users_to_check = set(self.wheel_timer.fetch(now))

                # Check whether the lists of syncing processes from an external
                # process have expired.
                expired_process_ids = [
                    process_id for process_id, last_update
                    in self.external_process_last_updated_ms.items()
                    if now - last_update > EXTERNAL_PROCESS_EXPIRY
                ]
                for process_id in expired_process_ids:
                    users_to_check.update(
                        self.external_process_last_updated_ms.pop(process_id, ())
                    )
                    self.external_process_last_update.pop(process_id)

                states = [
                    self.user_to_current_state.get(
                        user_id, UserPresenceState.default(user_id)
                    )
                    for user_id in users_to_check
                ]

                timers_fired_counter.inc_by(len(states))

                changes = handle_timeouts(
                    states,
                    is_mine_fn=self.is_mine_id,
                    syncing_user_ids=self.get_currently_syncing_users(),
                    now=now,
                )

            preserve_fn(self._update_states)(changes)
        except:
            logger.exception("Exception in _handle_timeouts loop")
示例#10
0
    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, user_to_num_current_syncs={}, now=now
        )

        self.assertIsNotNone(new_state)
        self.assertEquals(new_state.state, PresenceState.UNAVAILABLE)
示例#11
0
    def test_last_active(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_ACTIVE_GRANULARITY - 1,
            last_user_sync_ts=now,
            last_federation_update_ts=now,
        )

        new_state = handle_timeout(
            state, is_mine=True, user_to_num_current_syncs={}, now=now
        )

        self.assertIsNotNone(new_state)
        self.assertEquals(state, new_state)
示例#12
0
    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, user_to_num_current_syncs={}, now=now
        )

        self.assertIsNotNone(new_state)
        self.assertEquals(new_state.state, PresenceState.OFFLINE)
示例#13
0
    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,
        )
示例#14
0
    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, user_to_num_current_syncs={
                user_id: 1,
            }, now=now
        )

        self.assertIsNotNone(new_state)
        self.assertEquals(new_state.state, PresenceState.ONLINE)
示例#15
0
    def process_replication_rows(self, token, rows):
        states = [
            UserPresenceState(
                row.user_id,
                row.state,
                row.last_active_ts,
                row.last_federation_update_ts,
                row.last_user_sync_ts,
                row.status_msg,
                row.currently_active,
            )
            for row in rows
        ]

        for state in states:
            self.user_to_current_state[state.user_id] = state

        stream_id = token
        yield self.notify_from_replication(states, stream_id)
示例#16
0
    async def _handle_timeouts(self):
        """Checks the presence of users that have timed out and updates as
        appropriate.
        """
        logger.debug("Handling presence timeouts")
        now = self.clock.time_msec()

        # Fetch the list of users that *may* have timed out. Things may have
        # changed since the timeout was set, so we won't necessarily have to
        # take any action.
        users_to_check = set(self.wheel_timer.fetch(now))

        # Check whether the lists of syncing processes from an external
        # process have expired.
        expired_process_ids = [
            process_id for process_id, last_update in
            self.external_process_last_updated_ms.items()
            if now - last_update > EXTERNAL_PROCESS_EXPIRY
        ]
        for process_id in expired_process_ids:
            # For each expired process drop tracking info and check the users
            # that were syncing on that process to see if they need to be timed
            # out.
            users_to_check.update(
                self.external_process_to_current_syncs.pop(process_id, ()))
            self.external_process_last_updated_ms.pop(process_id)

        states = [
            self.user_to_current_state.get(user_id,
                                           UserPresenceState.default(user_id))
            for user_id in users_to_check
        ]

        timers_fired_counter.inc(len(states))

        changes = handle_timeouts(
            states,
            is_mine_fn=self.is_mine_id,
            syncing_user_ids=self.get_currently_syncing_users(),
            now=now,
        )

        return await self._update_states(changes)
示例#17
0
    def test_last_active(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_ACTIVE_GRANULARITY - 1,
            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.assertIsNotNone(new_state)
        self.assertEquals(state, new_state)
示例#18
0
    def test_federation_ping(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_PING_INTERVAL - 1,
        )

        new_state = handle_timeout(state,
                                   is_mine=True,
                                   syncing_user_ids=set(),
                                   now=now)

        self.assertIsNotNone(new_state)
        self.assertEquals(new_state, new_state)
示例#19
0
    def test_offline_to_online(self):
        wheel_timer = Mock()
        user_id = "@foo:bar"
        now = 5000000

        prev_state = UserPresenceState.default(user_id)
        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.assertTrue(persist_and_notify)
        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,
        )
示例#20
0
    def get_presence_for_users(self, user_ids):
        rows = yield self.db_pool.simple_select_many_batch(
            table="presence_stream",
            column="user_id",
            iterable=user_ids,
            keyvalues={},
            retcols=(
                "user_id",
                "state",
                "last_active_ts",
                "last_federation_update_ts",
                "last_user_sync_ts",
                "status_msg",
                "currently_active",
            ),
            desc="get_presence_for_users",
        )

        for row in rows:
            row["currently_active"] = bool(row["currently_active"])

        return {row["user_id"]: UserPresenceState(**row) for row in rows}
示例#21
0
    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)
示例#22
0
    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,
        )
示例#23
0
    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())
示例#24
0
 def from_data(data):
     return PresenceRow(state=UserPresenceState.from_dict(data))
示例#25
0
 def from_data(data):
     return PresenceDestinationsRow(
         state=UserPresenceState.from_dict(data["state"]), destinations=data["dests"]
     )
示例#26
0
 def from_data(data):
     return PresenceDestinationsRow(
         state=UserPresenceState.from_dict(data["state"]),
         destinations=data["dests"],
     )
示例#27
0
    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 yield 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

            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.hs.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_by(len(new_states))

            if to_notify:
                notified_presence_counter.inc_by(len(to_notify))
                yield self._persist_and_notify(to_notify.values())

            self.unpersisted_users_changes |= set(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_by(len(to_federation_ping))

                _, _, hosts_to_states = yield self._get_interested_parties(
                    to_federation_ping.values()
                )

                self._push_to_remotes(hosts_to_states)
示例#28
0
 def from_data(data):
     return PresenceRow(
         state=UserPresenceState.from_dict(data)
     )
示例#29
0
    def process_replication(self, result):
        # The federation stream contains things that we want to send out, e.g.
        # presence, typing, etc.
        fed_stream = result.get("federation")
        if fed_stream:
            latest_id = int(fed_stream["position"])

            # The federation stream containis a bunch of different types of
            # rows that need to be handled differently. We parse the rows, put
            # them into the appropriate collection and then send them off.
            presence_to_send = {}
            keyed_edus = {}
            edus = {}
            failures = {}
            device_destinations = set()

            # Parse the rows in the stream
            for row in fed_stream["rows"]:
                position, typ, content_js = row
                content = json.loads(content_js)

                if typ == send_queue.PRESENCE_TYPE:
                    destination = content["destination"]
                    state = UserPresenceState.from_dict(content["state"])

                    presence_to_send.setdefault(destination, []).append(state)
                elif typ == send_queue.KEYED_EDU_TYPE:
                    key = content["key"]
                    edu = Edu(**content["edu"])

                    keyed_edus.setdefault(edu.destination,
                                          {})[(edu.destination,
                                               tuple(key))] = edu
                elif typ == send_queue.EDU_TYPE:
                    edu = Edu(**content)

                    edus.setdefault(edu.destination, []).append(edu)
                elif typ == send_queue.FAILURE_TYPE:
                    destination = content["destination"]
                    failure = content["failure"]

                    failures.setdefault(destination, []).append(failure)
                elif typ == send_queue.DEVICE_MESSAGE_TYPE:
                    device_destinations.add(content["destination"])
                else:
                    raise Exception("Unrecognised federation type: %r", typ)

            # We've finished collecting, send everything off
            for destination, states in presence_to_send.items():
                self.federation_sender.send_presence(destination, states)

            for destination, edu_map in keyed_edus.items():
                for key, edu in edu_map.items():
                    self.federation_sender.send_edu(
                        edu.destination,
                        edu.edu_type,
                        edu.content,
                        key=key,
                    )

            for destination, edu_list in edus.items():
                for edu in edu_list:
                    self.federation_sender.send_edu(
                        edu.destination,
                        edu.edu_type,
                        edu.content,
                        key=None,
                    )

            for destination, failure_list in failures.items():
                for failure in failure_list:
                    self.federation_sender.send_failure(destination, failure)

            for destination in device_destinations:
                self.federation_sender.send_device_messages(destination)

            # Record where we are in the stream.
            yield self.store.update_federation_out_pos("federation", latest_id)

        # We also need to poke the federation sender when new events happen
        event_stream = result.get("events")
        if event_stream:
            latest_pos = event_stream["position"]
            self.federation_sender.notify_new_events(latest_pos)