Ejemplo n.º 1
0
    async def _handle_presence(
            self, service: ApplicationService,
            users: Collection[Union[str, UserID]]) -> List[JsonDict]:
        events = []  # type: List[JsonDict]
        presence_source = self.event_sources.sources["presence"]
        from_key = await self.store.get_type_stream_id_for_appservice(
            service, "presence")
        for user in users:
            if isinstance(user, str):
                user = UserID.from_string(user)

            interested = await service.is_interested_in_presence(
                user, self.store)
            if not interested:
                continue
            presence_events, _ = await presence_source.get_new_events(
                user=user,
                service=service,
                from_key=from_key,
            )
            time_now = self.clock.time_msec()
            events.extend({
                "type":
                "m.presence",
                "sender":
                event.user_id,
                "content":
                format_user_presence_state(
                    event, time_now, include_user_id=False),
            } for event in presence_events)

        return events
Ejemplo n.º 2
0
    async def _handle_presence(
        self,
        service: ApplicationService,
        users: Collection[Union[str, UserID]],
        new_token: Optional[int],
    ) -> List[JsonDict]:
        """
        Return the latest presence updates that the given application service should receive.

        First, filter the given users list to those that the application service is
        interested in. Then retrieve the latest presence updates since the
        the last-known previously received presence stream token for the given
        application service. Return those presence updates.

        Args:
            service: The application service that ephemeral events are being sent to.
            users: The users that should receive the presence update.
            new_token: A presence update stream token. Purely used to double-check that the
                from_token we pull from the database isn't greater than or equal to this
                token. Prevents accidentally duplicating work.

        Returns:
            A list of json dictionaries containing data derived from the presence events
            that should be sent to the given application service.
        """
        events: List[JsonDict] = []
        presence_source = self.event_sources.sources.presence
        from_key = await self.store.get_type_stream_id_for_appservice(
            service, "presence")
        if new_token is not None and new_token <= from_key:
            logger.debug("Rejecting token lower than or equal to stored: %s" %
                         (new_token, ))
            return []

        for user in users:
            if isinstance(user, str):
                user = UserID.from_string(user)

            interested = await service.is_interested_in_presence(
                user, self.store)
            if not interested:
                continue

            presence_events, _ = await presence_source.get_new_events(
                user=user,
                from_key=from_key,
            )
            time_now = self.clock.time_msec()
            events.extend({
                "type":
                "m.presence",
                "sender":
                event.user_id,
                "content":
                format_user_presence_state(
                    event, time_now, include_user_id=False),
            } for event in presence_events)

        return events
Ejemplo n.º 3
0
        async def get_presence():
            # If presence is disabled, return an empty list
            if not self.hs.config.use_presence:
                return []

            states = await presence_handler.get_states(
                [m.user_id for m in room_members])

            return [{
                "type": EventTypes.Presence,
                "content": format_user_presence_state(s, time_now),
            } for s in states]
Ejemplo n.º 4
0
        async def get_presence() -> List[JsonDict]:
            # If presence is disabled, return an empty list
            if not self.hs.config.server.use_presence:
                return []

            states = await presence_handler.get_states(
                [m.user_id for m in room_members])

            return [{
                "type": EduTypes.PRESENCE,
                "content": format_user_presence_state(s, time_now),
            } for s in states]
Ejemplo n.º 5
0
 def encode_presence(events, time_now):
     return {
         "events": [
             {
                 "type": "m.presence",
                 "sender": event.user_id,
                 "content": format_user_presence_state(
                     event, time_now, include_user_id=False
                 ),
             }
             for event in events
         ]
     }
Ejemplo n.º 6
0
 def encode_presence(events, time_now):
     return {
         "events": [{
             "type":
             "m.presence",
             "sender":
             event.user_id,
             "content":
             format_user_presence_state(event,
                                        time_now,
                                        include_user_id=False),
         } for event in events]
     }
Ejemplo n.º 7
0
 def encode_presence(events: List[UserPresenceState], time_now: int) -> JsonDict:
     return {
         "events": [
             {
                 "type": "m.presence",
                 "sender": event.user_id,
                 "content": format_user_presence_state(
                     event, time_now, include_user_id=False
                 ),
             }
             for event in events
         ]
     }
Ejemplo n.º 8
0
        async def check_for_updates(
            before_token: StreamToken, after_token: StreamToken
        ) -> EventStreamResult:
            if after_token == before_token:
                return EventStreamResult([], from_token, from_token)

            # The events fetched from each source are a JsonDict, EventBase, or
            # UserPresenceState, but see below for UserPresenceState being
            # converted to JsonDict.
            events: List[Union[JsonDict, EventBase]] = []
            end_token = from_token

            for name, source in self.event_sources.sources.get_sources():
                keyname = "%s_key" % name
                before_id = getattr(before_token, keyname)
                after_id = getattr(after_token, keyname)
                if before_id == after_id:
                    continue

                new_events, new_key = await source.get_new_events(
                    user=user,
                    from_key=getattr(from_token, keyname),
                    limit=limit,
                    is_guest=is_peeking,
                    room_ids=room_ids,
                    explicit_room_id=explicit_room_id,
                )

                if name == "room":
                    new_events = await filter_events_for_client(
                        self.storage,
                        user.to_string(),
                        new_events,
                        is_peeking=is_peeking,
                    )
                elif name == "presence":
                    now = self.clock.time_msec()
                    new_events[:] = [
                        {
                            "type": "m.presence",
                            "content": format_user_presence_state(event, now),
                        }
                        for event in new_events
                    ]

                events.extend(new_events)
                end_token = end_token.copy_and_replace(keyname, new_key)

            return EventStreamResult(events, from_token, end_token)
Ejemplo n.º 9
0
    async def on_GET(self, request, user_id):
        requester = await self.auth.get_user_by_req(request)
        user = UserID.from_string(user_id)

        if requester.user != user:
            allowed = await self.presence_handler.is_visible(
                observed_user=user, observer_user=requester.user)

            if not allowed:
                raise AuthError(403,
                                "You are not allowed to see their presence.")

        state = await self.presence_handler.get_state(target_user=user)
        state = format_user_presence_state(state, self.clock.time_msec())

        return 200, state
Ejemplo n.º 10
0
    def on_GET(self, request, user_id):
        requester = yield self.auth.get_user_by_req(request)
        user = UserID.from_string(user_id)

        if requester.user != user:
            allowed = yield self.presence_handler.is_visible(
                observed_user=user, observer_user=requester.user,
            )

            if not allowed:
                raise AuthError(403, "You are not allowed to see their presence.")

        state = yield self.presence_handler.get_state(target_user=user)
        state = format_user_presence_state(state, self.clock.time_msec())

        defer.returnValue((200, state))
        def check_for_updates(before_token, after_token):
            if not after_token.is_after(before_token):
                defer.returnValue(
                    EventStreamResult([], (from_token, from_token)))

            events = []
            end_token = from_token

            for name, source in self.event_sources.sources.items():
                keyname = "%s_key" % name
                before_id = getattr(before_token, keyname)
                after_id = getattr(after_token, keyname)
                if before_id == after_id:
                    continue
                if only_keys and name not in only_keys:
                    continue

                new_events, new_key = yield source.get_new_events(
                    user=user,
                    from_key=getattr(from_token, keyname),
                    limit=limit,
                    is_guest=is_peeking,
                    room_ids=room_ids,
                    explicit_room_id=explicit_room_id,
                )

                if name == "room":
                    new_events = yield filter_events_for_client(
                        self.store,
                        user.to_string(),
                        new_events,
                        is_peeking=is_peeking,
                    )
                elif name == "presence":
                    now = self.clock.time_msec()
                    new_events[:] = [{
                        "type":
                        "m.presence",
                        "content":
                        format_user_presence_state(event, now),
                    } for event in new_events]

                events.extend(new_events)
                end_token = end_token.copy_and_replace(keyname, new_key)

            defer.returnValue(
                EventStreamResult(events, (from_token, end_token)))
Ejemplo n.º 12
0
        def check_for_updates(before_token, after_token):
            if not after_token.is_after(before_token):
                defer.returnValue(EventStreamResult([], (from_token, from_token)))

            events = []
            end_token = from_token

            for name, source in self.event_sources.sources.items():
                keyname = "%s_key" % name
                before_id = getattr(before_token, keyname)
                after_id = getattr(after_token, keyname)
                if before_id == after_id:
                    continue
                if only_keys and name not in only_keys:
                    continue

                new_events, new_key = yield source.get_new_events(
                    user=user,
                    from_key=getattr(from_token, keyname),
                    limit=limit,
                    is_guest=is_peeking,
                    room_ids=room_ids,
                    explicit_room_id=explicit_room_id,
                )

                if name == "room":
                    new_events = yield filter_events_for_client(
                        self.store,
                        user.to_string(),
                        new_events,
                        is_peeking=is_peeking,
                    )
                elif name == "presence":
                    now = self.clock.time_msec()
                    new_events[:] = [
                        {
                            "type": "m.presence",
                            "content": format_user_presence_state(event, now),
                        }
                        for event in new_events
                    ]

                events.extend(new_events)
                end_token = end_token.copy_and_replace(keyname, new_key)

            defer.returnValue(EventStreamResult(events, (from_token, end_token)))
Ejemplo n.º 13
0
    async def on_GET(self, request: SynapseRequest,
                     user_id: str) -> Tuple[int, JsonDict]:
        requester = await self.auth.get_user_by_req(request)
        user = UserID.from_string(user_id)

        if not self._use_presence:
            return 200, {"presence": "offline"}

        if requester.user != user:
            allowed = await self.presence_handler.is_visible(
                observed_user=user, observer_user=requester.user)

            if not allowed:
                raise AuthError(403,
                                "You are not allowed to see their presence.")

        state = await self.presence_handler.get_state(target_user=user)
        result = format_user_presence_state(state,
                                            self.clock.time_msec(),
                                            include_user_id=False)

        return 200, result
Ejemplo n.º 14
0
    async def get_stream(
        self,
        auth_user_id: str,
        pagin_config: PaginationConfig,
        timeout: int = 0,
        as_client_event: bool = True,
        affect_presence: bool = True,
        room_id: Optional[str] = None,
        is_guest: bool = False,
    ) -> JsonDict:
        """Fetches the events stream for a given user."""

        if room_id:
            blocked = await self.store.is_room_blocked(room_id)
            if blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        # send any outstanding server notices to the user.
        await self._server_notices_sender.on_user_syncing(auth_user_id)

        auth_user = UserID.from_string(auth_user_id)
        presence_handler = self.hs.get_presence_handler()

        context = await presence_handler.user_syncing(
            auth_user_id, affect_presence=affect_presence)
        with context:
            if timeout:
                # If they've set a timeout set a minimum limit.
                timeout = max(timeout, 500)

                # Add some randomness to this value to try and mitigate against
                # thundering herds on restart.
                timeout = random.randint(int(timeout * 0.9),
                                         int(timeout * 1.1))

            events, tokens = await self.notifier.get_events_for(
                auth_user,
                pagin_config,
                timeout,
                is_guest=is_guest,
                explicit_room_id=room_id,
            )

            time_now = self.clock.time_msec()

            # When the user joins a new room, or another user joins a currently
            # joined room, we need to send down presence for those users.
            to_add: List[JsonDict] = []
            for event in events:
                if not isinstance(event, EventBase):
                    continue
                if event.type == EventTypes.Member:
                    if event.membership != Membership.JOIN:
                        continue
                    # Send down presence.
                    if event.state_key == auth_user_id:
                        # Send down presence for everyone in the room.
                        users: Iterable[
                            str] = await self.store.get_users_in_room(
                                event.room_id)
                    else:
                        users = [event.state_key]

                    states = await presence_handler.get_states(users)
                    to_add.extend(
                        {
                            "type": EduTypes.Presence,
                            "content": format_user_presence_state(
                                state, time_now),
                        } for state in states)

            events.extend(to_add)

            chunks = await self._event_serializer.serialize_events(
                events,
                time_now,
                as_client_event=as_client_event,
                # We don't bundle "live" events, as otherwise clients
                # will end up double counting annotations.
                bundle_aggregations=False,
            )

            chunk = {
                "chunk": chunks,
                "start": await tokens[0].to_string(self.store),
                "end": await tokens[1].to_string(self.store),
            }

            return chunk
Ejemplo n.º 15
0
    def _transaction_transmission_loop(self):
        pending_pdus = []
        try:
            self.transmission_loop_running = True

            # This will throw if we wouldn't retry. We do this here so we fail
            # quickly, but we will later check this again in the http client,
            # hence why we throw the result away.
            yield get_retry_limiter(self._destination, self._clock, self._store)

            pending_pdus = []
            while True:
                device_message_edus, device_stream_id, dev_list_id = (
                    # We have to keep 2 free slots for presence and rr_edus
                    yield self._get_new_device_messages(MAX_EDUS_PER_TRANSACTION - 2)
                )

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # transmission_loop_running flag.

                pending_pdus = self._pending_pdus

                # We can only include at most 50 PDUs per transactions
                pending_pdus, self._pending_pdus = pending_pdus[:50], pending_pdus[50:]

                pending_edus = []

                # We can only include at most 100 EDUs per transactions
                # rr_edus and pending_presence take at most one slot each
                pending_edus.extend(self._get_rr_edus(force_flush=False))
                pending_presence = self._pending_presence
                self._pending_presence = {}
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self._server_name,
                            destination=self._destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self._clock.time_msec()
                                    )
                                    for presence in pending_presence.values()
                                ]
                            },
                        )
                    )

                pending_edus.extend(device_message_edus)
                pending_edus.extend(
                    self._pop_pending_edus(MAX_EDUS_PER_TRANSACTION - len(pending_edus))
                )
                while (
                    len(pending_edus) < MAX_EDUS_PER_TRANSACTION
                    and self._pending_edus_keyed
                ):
                    _, val = self._pending_edus_keyed.popitem()
                    pending_edus.append(val)

                if pending_pdus:
                    logger.debug(
                        "TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                        self._destination,
                        len(pending_pdus),
                    )

                if not pending_pdus and not pending_edus:
                    logger.debug("TX [%s] Nothing to send", self._destination)
                    self._last_device_stream_id = device_stream_id
                    return

                # if we've decided to send a transaction anyway, and we have room, we
                # may as well send any pending RRs
                if len(pending_edus) < MAX_EDUS_PER_TRANSACTION:
                    pending_edus.extend(self._get_rr_edus(force_flush=True))

                # END CRITICAL SECTION

                success = yield self._transaction_manager.send_new_transaction(
                    self._destination, pending_pdus, pending_edus
                )
                if success:
                    sent_transactions_counter.inc()
                    sent_edus_counter.inc(len(pending_edus))
                    for edu in pending_edus:
                        sent_edus_by_type.labels(edu.edu_type).inc()
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if device_message_edus:
                        yield self._store.delete_device_msgs_for_remote(
                            self._destination, device_stream_id
                        )
                        logger.info(
                            "Marking as sent %r %r", self._destination, dev_list_id
                        )
                        yield self._store.mark_as_sent_devices_by_remote(
                            self._destination, dev_list_id
                        )

                    self._last_device_stream_id = device_stream_id
                    self._last_device_list_stream_id = dev_list_id
                else:
                    break
        except NotRetryingDestination as e:
            logger.debug(
                "TX [%s] not ready for retry yet (next retry at %s) - "
                "dropping transaction for now",
                self._destination,
                datetime.datetime.fromtimestamp(
                    (e.retry_last_ts + e.retry_interval) / 1000.0
                ),
            )
        except FederationDeniedError as e:
            logger.info(e)
        except HttpResponseException as e:
            logger.warning(
                "TX [%s] Received %d response to transaction: %s",
                self._destination,
                e.code,
                e,
            )
        except RequestSendFailed as e:
            logger.warning(
                "TX [%s] Failed to send transaction: %s", self._destination, e
            )

            for p, _ in pending_pdus:
                logger.info(
                    "Failed to send event %s to %s", p.event_id, self._destination
                )
        except Exception:
            logger.exception("TX [%s] Failed to send transaction", self._destination)
            for p, _ in pending_pdus:
                logger.info(
                    "Failed to send event %s to %s", p.event_id, self._destination
                )
        finally:
            # We want to be *very* sure we clear this after we stop processing
            self.transmission_loop_running = False
Ejemplo n.º 16
0
    def _transaction_transmission_loop(self):
        pending_pdus = []
        try:
            self.transmission_loop_running = True

            # This will throw if we wouldn't retry. We do this here so we fail
            # quickly, but we will later check this again in the http client,
            # hence why we throw the result away.
            yield get_retry_limiter(self._destination, self._clock,
                                    self._store)

            pending_pdus = []
            while True:
                device_message_edus, device_stream_id, dev_list_id = (
                    yield self._get_new_device_messages())

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # transmission_loop_running flag.

                pending_pdus = self._pending_pdus

                # We can only include at most 50 PDUs per transactions
                pending_pdus, self._pending_pdus = pending_pdus[:
                                                                50], pending_pdus[
                                                                    50:]

                pending_edus = []

                pending_edus.extend(self._get_rr_edus(force_flush=False))

                # We can only include at most 100 EDUs per transactions
                pending_edus.extend(
                    self._pop_pending_edus(100 - len(pending_edus)))

                pending_edus.extend(self._pending_edus_keyed.values())

                self._pending_edus_keyed = {}

                pending_edus.extend(device_message_edus)

                pending_presence = self._pending_presence
                self._pending_presence = {}
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self._server_name,
                            destination=self._destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self._clock.time_msec())
                                    for presence in pending_presence.values()
                                ]
                            },
                        ))

                if pending_pdus:
                    logger.debug(
                        "TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                        self._destination, len(pending_pdus))

                if not pending_pdus and not pending_edus:
                    logger.debug("TX [%s] Nothing to send", self._destination)
                    self._last_device_stream_id = device_stream_id
                    return

                # if we've decided to send a transaction anyway, and we have room, we
                # may as well send any pending RRs
                if len(pending_edus) < 100:
                    pending_edus.extend(self._get_rr_edus(force_flush=True))

                # END CRITICAL SECTION

                success = yield self._transaction_manager.send_new_transaction(
                    self._destination, pending_pdus, pending_edus)
                if success:
                    sent_transactions_counter.inc()
                    sent_edus_counter.inc(len(pending_edus))
                    for edu in pending_edus:
                        sent_edus_by_type.labels(edu.edu_type).inc()
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if device_message_edus:
                        yield self._store.delete_device_msgs_for_remote(
                            self._destination, device_stream_id)
                        logger.info("Marking as sent %r %r", self._destination,
                                    dev_list_id)
                        yield self._store.mark_as_sent_devices_by_remote(
                            self._destination, dev_list_id)

                    self._last_device_stream_id = device_stream_id
                    self._last_device_list_stream_id = dev_list_id
                else:
                    break
        except NotRetryingDestination as e:
            logger.debug(
                "TX [%s] not ready for retry yet (next retry at %s) - "
                "dropping transaction for now",
                self._destination,
                datetime.datetime.fromtimestamp(
                    (e.retry_last_ts + e.retry_interval) / 1000.0),
            )
        except FederationDeniedError as e:
            logger.info(e)
        except HttpResponseException as e:
            logger.warning(
                "TX [%s] Received %d response to transaction: %s",
                self._destination,
                e.code,
                e,
            )
        except RequestSendFailed as e:
            logger.warning("TX [%s] Failed to send transaction: %s",
                           self._destination, e)

            for p, _ in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            self._destination)
        except Exception:
            logger.exception(
                "TX [%s] Failed to send transaction",
                self._destination,
            )
            for p, _ in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            self._destination)
        finally:
            # We want to be *very* sure we clear this after we stop processing
            self.transmission_loop_running = False
Ejemplo n.º 17
0
    async def _transaction_transmission_loop(self) -> None:
        pending_pdus = []  # type: List[EventBase]
        try:
            self.transmission_loop_running = True

            # This will throw if we wouldn't retry. We do this here so we fail
            # quickly, but we will later check this again in the http client,
            # hence why we throw the result away.
            await get_retry_limiter(self._destination, self._clock,
                                    self._store)

            pending_pdus = []
            while True:
                # We have to keep 2 free slots for presence and rr_edus
                limit = MAX_EDUS_PER_TRANSACTION - 2

                device_update_edus, dev_list_id = await self._get_device_update_edus(
                    limit)

                limit -= len(device_update_edus)

                (
                    to_device_edus,
                    device_stream_id,
                ) = await self._get_to_device_message_edus(limit)

                pending_edus = device_update_edus + to_device_edus

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # transmission_loop_running flag.

                pending_pdus = self._pending_pdus

                # We can only include at most 50 PDUs per transactions
                pending_pdus, self._pending_pdus = pending_pdus[:
                                                                50], pending_pdus[
                                                                    50:]

                pending_edus.extend(self._get_rr_edus(force_flush=False))
                pending_presence = self._pending_presence
                self._pending_presence = {}
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self._server_name,
                            destination=self._destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self._clock.time_msec())
                                    for presence in pending_presence.values()
                                ]
                            },
                        ))

                pending_edus.extend(
                    self._pop_pending_edus(MAX_EDUS_PER_TRANSACTION -
                                           len(pending_edus)))
                while (len(pending_edus) < MAX_EDUS_PER_TRANSACTION
                       and self._pending_edus_keyed):
                    _, val = self._pending_edus_keyed.popitem()
                    pending_edus.append(val)

                if pending_pdus:
                    logger.debug(
                        "TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                        self._destination,
                        len(pending_pdus),
                    )

                if not pending_pdus and not pending_edus:
                    logger.debug("TX [%s] Nothing to send", self._destination)
                    self._last_device_stream_id = device_stream_id
                    return

                # if we've decided to send a transaction anyway, and we have room, we
                # may as well send any pending RRs
                if len(pending_edus) < MAX_EDUS_PER_TRANSACTION:
                    pending_edus.extend(self._get_rr_edus(force_flush=True))

                # END CRITICAL SECTION

                success = await self._transaction_manager.send_new_transaction(
                    self._destination, pending_pdus, pending_edus)
                if success:
                    sent_transactions_counter.inc()
                    sent_edus_counter.inc(len(pending_edus))
                    for edu in pending_edus:
                        sent_edus_by_type.labels(edu.edu_type).inc()
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if to_device_edus:
                        await self._store.delete_device_msgs_for_remote(
                            self._destination, device_stream_id)

                    # also mark the device updates as sent
                    if device_update_edus:
                        logger.info("Marking as sent %r %r", self._destination,
                                    dev_list_id)
                        await self._store.mark_as_sent_devices_by_remote(
                            self._destination, dev_list_id)

                    self._last_device_stream_id = device_stream_id
                    self._last_device_list_stream_id = dev_list_id
                else:
                    break
        except NotRetryingDestination as e:
            logger.debug(
                "TX [%s] not ready for retry yet (next retry at %s) - "
                "dropping transaction for now",
                self._destination,
                datetime.datetime.fromtimestamp(
                    (e.retry_last_ts + e.retry_interval) / 1000.0),
            )

            if e.retry_interval > 60 * 60 * 1000:
                # we won't retry for another hour!
                # (this suggests a significant outage)
                # We drop pending PDUs and EDUs because otherwise they will
                # rack up indefinitely.
                # Note that:
                # - the EDUs that are being dropped here are those that we can
                #   afford to drop (specifically, only typing notifications,
                #   read receipts and presence updates are being dropped here)
                # - Other EDUs such as to_device messages are queued with a
                #   different mechanism
                # - this is all volatile state that would be lost if the
                #   federation sender restarted anyway

                # dropping read receipts is a bit sad but should be solved
                # through another mechanism, because this is all volatile!
                self._pending_pdus = []
                self._pending_edus = []
                self._pending_edus_keyed = {}
                self._pending_presence = {}
                self._pending_rrs = {}
        except FederationDeniedError as e:
            logger.info(e)
        except HttpResponseException as e:
            logger.warning(
                "TX [%s] Received %d response to transaction: %s",
                self._destination,
                e.code,
                e,
            )
        except RequestSendFailed as e:
            logger.warning("TX [%s] Failed to send transaction: %s",
                           self._destination, e)

            for p in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            self._destination)
        except Exception:
            logger.exception("TX [%s] Failed to send transaction",
                             self._destination)
            for p in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            self._destination)
        finally:
            # We want to be *very* sure we clear this after we stop processing
            self.transmission_loop_running = False
Ejemplo n.º 18
0
    def _snapshot_all_rooms(self, user_id=None, pagin_config=None,
                            as_client_event=True, include_archived=False):

        memberships = [Membership.INVITE, Membership.JOIN]
        if include_archived:
            memberships.append(Membership.LEAVE)

        room_list = yield self.store.get_rooms_for_user_where_membership_is(
            user_id=user_id, membership_list=memberships
        )

        user = UserID.from_string(user_id)

        rooms_ret = []

        now_token = yield self.hs.get_event_sources().get_current_token()

        presence_stream = self.hs.get_event_sources().sources["presence"]
        pagination_config = PaginationConfig(from_token=now_token)
        presence, _ = yield presence_stream.get_pagination_rows(
            user, pagination_config.get_source_config("presence"), None
        )

        receipt_stream = self.hs.get_event_sources().sources["receipt"]
        receipt, _ = yield receipt_stream.get_pagination_rows(
            user, pagination_config.get_source_config("receipt"), None
        )

        tags_by_room = yield self.store.get_tags_for_user(user_id)

        account_data, account_data_by_room = (
            yield self.store.get_account_data_for_user(user_id)
        )

        public_room_ids = yield self.store.get_public_room_ids()

        limit = pagin_config.limit
        if limit is None:
            limit = 10

        @defer.inlineCallbacks
        def handle_room(event):
            d = {
                "room_id": event.room_id,
                "membership": event.membership,
                "visibility": (
                    "public" if event.room_id in public_room_ids
                    else "private"
                ),
            }

            if event.membership == Membership.INVITE:
                time_now = self.clock.time_msec()
                d["inviter"] = event.sender

                invite_event = yield self.store.get_event(event.event_id)
                d["invite"] = serialize_event(invite_event, time_now, as_client_event)

            rooms_ret.append(d)

            if event.membership not in (Membership.JOIN, Membership.LEAVE):
                return

            try:
                if event.membership == Membership.JOIN:
                    room_end_token = now_token.room_key
                    deferred_room_state = run_in_background(
                        self.state_handler.get_current_state,
                        event.room_id,
                    )
                elif event.membership == Membership.LEAVE:
                    room_end_token = "s%d" % (event.stream_ordering,)
                    deferred_room_state = run_in_background(
                        self.store.get_state_for_events,
                        [event.event_id],
                    )
                    deferred_room_state.addCallback(
                        lambda states: states[event.event_id]
                    )

                (messages, token), current_state = yield make_deferred_yieldable(
                    defer.gatherResults(
                        [
                            run_in_background(
                                self.store.get_recent_events_for_room,
                                event.room_id,
                                limit=limit,
                                end_token=room_end_token,
                            ),
                            deferred_room_state,
                        ]
                    )
                ).addErrback(unwrapFirstError)

                messages = yield filter_events_for_client(
                    self.store, user_id, messages
                )

                start_token = now_token.copy_and_replace("room_key", token)
                end_token = now_token.copy_and_replace("room_key", room_end_token)
                time_now = self.clock.time_msec()

                d["messages"] = {
                    "chunk": [
                        serialize_event(m, time_now, as_client_event)
                        for m in messages
                    ],
                    "start": start_token.to_string(),
                    "end": end_token.to_string(),
                }

                d["state"] = [
                    serialize_event(c, time_now, as_client_event)
                    for c in current_state.values()
                ]

                account_data_events = []
                tags = tags_by_room.get(event.room_id)
                if tags:
                    account_data_events.append({
                        "type": "m.tag",
                        "content": {"tags": tags},
                    })

                account_data = account_data_by_room.get(event.room_id, {})
                for account_data_type, content in account_data.items():
                    account_data_events.append({
                        "type": account_data_type,
                        "content": content,
                    })

                d["account_data"] = account_data_events
            except Exception:
                logger.exception("Failed to get snapshot")

        yield concurrently_execute(handle_room, room_list, 10)

        account_data_events = []
        for account_data_type, content in account_data.items():
            account_data_events.append({
                "type": account_data_type,
                "content": content,
            })

        now = self.clock.time_msec()

        ret = {
            "rooms": rooms_ret,
            "presence": [
                {
                    "type": "m.presence",
                    "content": format_user_presence_state(event, now),
                }
                for event in presence
            ],
            "account_data": account_data_events,
            "receipts": receipt,
            "end": now_token.to_string(),
        }

        defer.returnValue(ret)
Ejemplo n.º 19
0
    def _attempt_new_transaction(self, destination):
        # list of (pending_pdu, deferred, order)
        if destination in self.pending_transactions:
            # XXX: pending_transactions can get stuck on by a never-ending
            # request at which point pending_pdus_by_dest just keeps growing.
            # we need application-layer timeouts of some flavour of these
            # requests
            logger.debug("TX [%s] Transaction already in progress",
                         destination)
            return

        try:
            self.pending_transactions[destination] = 1

            yield run_on_reactor()

            while True:
                pending_pdus = self.pending_pdus_by_dest.pop(destination, [])
                pending_edus = self.pending_edus_by_dest.pop(destination, [])
                pending_presence = self.pending_presence_by_dest.pop(
                    destination, {})
                pending_failures = self.pending_failures_by_dest.pop(
                    destination, [])

                pending_edus.extend(
                    self.pending_edus_keyed_by_dest.pop(destination,
                                                        {}).values())

                limiter = yield get_retry_limiter(
                    destination,
                    self.clock,
                    self.store,
                )

                device_message_edus, device_stream_id = (
                    yield self._get_new_device_messages(destination))

                pending_edus.extend(device_message_edus)
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self.server_name,
                            destination=destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self.clock.time_msec())
                                    for presence in pending_presence.values()
                                ]
                            },
                        ))

                if pending_pdus:
                    logger.debug(
                        "TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                        destination, len(pending_pdus))

                if not pending_pdus and not pending_edus and not pending_failures:
                    logger.debug("TX [%s] Nothing to send", destination)
                    self.last_device_stream_id_by_dest[destination] = (
                        device_stream_id)
                    return

                success = yield self._send_new_transaction(
                    destination,
                    pending_pdus,
                    pending_edus,
                    pending_failures,
                    device_stream_id,
                    should_delete_from_device_stream=bool(device_message_edus),
                    limiter=limiter,
                )
                if not success:
                    break
        except NotRetryingDestination:
            logger.info(
                "TX [%s] not ready for retry yet - "
                "dropping transaction for now",
                destination,
            )
        finally:
            # We want to be *very* sure we delete this after we stop processing
            self.pending_transactions.pop(destination, None)
Ejemplo n.º 20
0
    async def __aenter__(self) -> Tuple[List[EventBase], List[Edu]]:
        # First we calculate the EDUs we want to send, if any.

        # We start by fetching device related EDUs, i.e device updates and to
        # device messages. We have to keep 2 free slots for presence and rr_edus.
        limit = MAX_EDUS_PER_TRANSACTION - 2

        device_update_edus, dev_list_id = await self.queue._get_device_update_edus(
            limit)

        if device_update_edus:
            self._device_list_id = dev_list_id
        else:
            self.queue._last_device_list_stream_id = dev_list_id

        limit -= len(device_update_edus)

        (
            to_device_edus,
            device_stream_id,
        ) = await self.queue._get_to_device_message_edus(limit)

        if to_device_edus:
            self._device_stream_id = device_stream_id
        else:
            self.queue._last_device_stream_id = device_stream_id

        pending_edus = device_update_edus + to_device_edus

        # Now add the read receipt EDU.
        pending_edus.extend(self.queue._get_rr_edus(force_flush=False))

        # And presence EDU.
        if self.queue._pending_presence:
            pending_edus.append(
                Edu(
                    origin=self.queue._server_name,
                    destination=self.queue._destination,
                    edu_type=EduTypes.PRESENCE,
                    content={
                        "push": [
                            format_user_presence_state(
                                presence, self.queue._clock.time_msec()) for
                            presence in self.queue._pending_presence.values()
                        ]
                    },
                ))
            self.queue._pending_presence = {}

        # Finally add any other types of EDUs if there is room.
        pending_edus.extend(
            self.queue._pop_pending_edus(MAX_EDUS_PER_TRANSACTION -
                                         len(pending_edus)))
        while (len(pending_edus) < MAX_EDUS_PER_TRANSACTION
               and self.queue._pending_edus_keyed):
            _, val = self.queue._pending_edus_keyed.popitem()
            pending_edus.append(val)

        # Now we look for any PDUs to send, by getting up to 50 PDUs from the
        # queue
        self._pdus = self.queue._pending_pdus[:50]

        if not self._pdus and not pending_edus:
            return [], []

        # if we've decided to send a transaction anyway, and we have room, we
        # may as well send any pending RRs
        if len(pending_edus) < MAX_EDUS_PER_TRANSACTION:
            pending_edus.extend(self.queue._get_rr_edus(force_flush=True))

        if self._pdus:
            self._last_stream_ordering = self._pdus[
                -1].internal_metadata.stream_ordering
            assert self._last_stream_ordering

        return self._pdus, pending_edus
Ejemplo n.º 21
0
    def _snapshot_all_rooms(self,
                            user_id=None,
                            pagin_config=None,
                            as_client_event=True,
                            include_archived=False):

        memberships = [Membership.INVITE, Membership.JOIN]
        if include_archived:
            memberships.append(Membership.LEAVE)

        room_list = yield self.store.get_rooms_for_user_where_membership_is(
            user_id=user_id, membership_list=memberships)

        user = UserID.from_string(user_id)

        rooms_ret = []

        now_token = yield self.hs.get_event_sources().get_current_token()

        presence_stream = self.hs.get_event_sources().sources["presence"]
        pagination_config = PaginationConfig(from_token=now_token)
        presence, _ = yield presence_stream.get_pagination_rows(
            user, pagination_config.get_source_config("presence"), None)

        receipt_stream = self.hs.get_event_sources().sources["receipt"]
        receipt, _ = yield receipt_stream.get_pagination_rows(
            user, pagination_config.get_source_config("receipt"), None)

        tags_by_room = yield self.store.get_tags_for_user(user_id)

        account_data, account_data_by_room = (
            yield self.store.get_account_data_for_user(user_id))

        public_room_ids = yield self.store.get_public_room_ids()

        limit = pagin_config.limit
        if limit is None:
            limit = 10

        @defer.inlineCallbacks
        def handle_room(event):
            d = {
                "room_id":
                event.room_id,
                "membership":
                event.membership,
                "visibility":
                ("public" if event.room_id in public_room_ids else "private"),
            }

            if event.membership == Membership.INVITE:
                time_now = self.clock.time_msec()
                d["inviter"] = event.sender

                invite_event = yield self.store.get_event(event.event_id)
                d["invite"] = yield self._event_serializer.serialize_event(
                    invite_event,
                    time_now,
                    as_client_event,
                )

            rooms_ret.append(d)

            if event.membership not in (Membership.JOIN, Membership.LEAVE):
                return

            try:
                if event.membership == Membership.JOIN:
                    room_end_token = now_token.room_key
                    deferred_room_state = run_in_background(
                        self.state_handler.get_current_state,
                        event.room_id,
                    )
                elif event.membership == Membership.LEAVE:
                    room_end_token = "s%d" % (event.stream_ordering, )
                    deferred_room_state = run_in_background(
                        self.store.get_state_for_events,
                        [event.event_id],
                    )
                    deferred_room_state.addCallback(
                        lambda states: states[event.event_id])

                (messages,
                 token), current_state = yield make_deferred_yieldable(
                     defer.gatherResults([
                         run_in_background(
                             self.store.get_recent_events_for_room,
                             event.room_id,
                             limit=limit,
                             end_token=room_end_token,
                         ),
                         deferred_room_state,
                     ])).addErrback(unwrapFirstError)

                messages = yield filter_events_for_client(
                    self.store, user_id, messages)

                start_token = now_token.copy_and_replace("room_key", token)
                end_token = now_token.copy_and_replace("room_key",
                                                       room_end_token)
                time_now = self.clock.time_msec()

                d["messages"] = {
                    "chunk": (yield self._event_serializer.serialize_events(
                        messages,
                        time_now=time_now,
                        as_client_event=as_client_event,
                    )),
                    "start":
                    start_token.to_string(),
                    "end":
                    end_token.to_string(),
                }

                d["state"] = yield self._event_serializer.serialize_events(
                    current_state.values(),
                    time_now=time_now,
                    as_client_event=as_client_event)

                account_data_events = []
                tags = tags_by_room.get(event.room_id)
                if tags:
                    account_data_events.append({
                        "type": "m.tag",
                        "content": {
                            "tags": tags
                        },
                    })

                account_data = account_data_by_room.get(event.room_id, {})
                for account_data_type, content in account_data.items():
                    account_data_events.append({
                        "type": account_data_type,
                        "content": content,
                    })

                d["account_data"] = account_data_events
            except Exception:
                logger.exception("Failed to get snapshot")

        yield concurrently_execute(handle_room, room_list, 10)

        account_data_events = []
        for account_data_type, content in account_data.items():
            account_data_events.append({
                "type": account_data_type,
                "content": content,
            })

        now = self.clock.time_msec()

        ret = {
            "rooms":
            rooms_ret,
            "presence": [{
                "type": "m.presence",
                "content": format_user_presence_state(event, now),
            } for event in presence],
            "account_data":
            account_data_events,
            "receipts":
            receipt,
            "end":
            now_token.to_string(),
        }

        defer.returnValue(ret)
Ejemplo n.º 22
0
    def _transaction_transmission_loop(self, destination):
        pending_pdus = []
        try:
            self.pending_transactions[destination] = 1

            # This will throw if we wouldn't retry. We do this here so we fail
            # quickly, but we will later check this again in the http client,
            # hence why we throw the result away.
            yield get_retry_limiter(destination, self.clock, self.store)

            pending_pdus = []
            while True:
                device_message_edus, device_stream_id, dev_list_id = (
                    yield self._get_new_device_messages(destination)
                )

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # pending_transactions flag.

                pending_pdus = self.pending_pdus_by_dest.pop(destination, [])

                # We can only include at most 50 PDUs per transactions
                pending_pdus, leftover_pdus = pending_pdus[:50], pending_pdus[50:]
                if leftover_pdus:
                    self.pending_pdus_by_dest[destination] = leftover_pdus

                pending_edus = self.pending_edus_by_dest.pop(destination, [])

                # We can only include at most 100 EDUs per transactions
                pending_edus, leftover_edus = pending_edus[:100], pending_edus[100:]
                if leftover_edus:
                    self.pending_edus_by_dest[destination] = leftover_edus

                pending_presence = self.pending_presence_by_dest.pop(destination, {})

                pending_edus.extend(
                    self.pending_edus_keyed_by_dest.pop(destination, {}).values()
                )

                pending_edus.extend(device_message_edus)
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self.server_name,
                            destination=destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self.clock.time_msec()
                                    )
                                    for presence in pending_presence.values()
                                ]
                            },
                        )
                    )

                if pending_pdus:
                    logger.debug("TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                                 destination, len(pending_pdus))

                if not pending_pdus and not pending_edus:
                    logger.debug("TX [%s] Nothing to send", destination)
                    self.last_device_stream_id_by_dest[destination] = (
                        device_stream_id
                    )
                    return

                # END CRITICAL SECTION

                success = yield self._send_new_transaction(
                    destination, pending_pdus, pending_edus,
                )
                if success:
                    sent_transactions_counter.inc()
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if device_message_edus:
                        yield self.store.delete_device_msgs_for_remote(
                            destination, device_stream_id
                        )
                        logger.info("Marking as sent %r %r", destination, dev_list_id)
                        yield self.store.mark_as_sent_devices_by_remote(
                            destination, dev_list_id
                        )

                    self.last_device_stream_id_by_dest[destination] = device_stream_id
                    self.last_device_list_stream_id_by_dest[destination] = dev_list_id
                else:
                    break
        except NotRetryingDestination as e:
            logger.debug(
                "TX [%s] not ready for retry yet (next retry at %s) - "
                "dropping transaction for now",
                destination,
                datetime.datetime.fromtimestamp(
                    (e.retry_last_ts + e.retry_interval) / 1000.0
                ),
            )
        except FederationDeniedError as e:
            logger.info(e)
        except Exception as e:
            logger.warn(
                "TX [%s] Failed to send transaction: %s",
                destination,
                e,
            )
            for p, _ in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            destination)
        finally:
            # We want to be *very* sure we delete this after we stop processing
            self.pending_transactions.pop(destination, None)
Ejemplo n.º 23
0
    def _transaction_transmission_loop(self, destination):
        pending_pdus = []
        try:
            self.pending_transactions[destination] = 1

            # This will throw if we wouldn't retry. We do this here so we fail
            # quickly, but we will later check this again in the http client,
            # hence why we throw the result away.
            yield get_retry_limiter(destination, self.clock, self.store)

            pending_pdus = []
            while True:
                device_message_edus, device_stream_id, dev_list_id = (
                    yield self._get_new_device_messages(destination))

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # pending_transactions flag.

                pending_pdus = self.pending_pdus_by_dest.pop(destination, [])
                pending_edus = self.pending_edus_by_dest.pop(destination, [])
                pending_presence = self.pending_presence_by_dest.pop(
                    destination, {})
                pending_failures = self.pending_failures_by_dest.pop(
                    destination, [])

                pending_edus.extend(
                    self.pending_edus_keyed_by_dest.pop(destination,
                                                        {}).values())

                pending_edus.extend(device_message_edus)
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self.server_name,
                            destination=destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self.clock.time_msec())
                                    for presence in pending_presence.values()
                                ]
                            },
                        ))

                if pending_pdus:
                    logger.debug(
                        "TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                        destination, len(pending_pdus))

                if not pending_pdus and not pending_edus and not pending_failures:
                    logger.debug("TX [%s] Nothing to send", destination)
                    self.last_device_stream_id_by_dest[destination] = (
                        device_stream_id)
                    return

                # END CRITICAL SECTION

                success = yield self._send_new_transaction(
                    destination,
                    pending_pdus,
                    pending_edus,
                    pending_failures,
                )
                if success:
                    sent_transactions_counter.inc()
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if device_message_edus:
                        yield self.store.delete_device_msgs_for_remote(
                            destination, device_stream_id)
                        logger.info("Marking as sent %r %r", destination,
                                    dev_list_id)
                        yield self.store.mark_as_sent_devices_by_remote(
                            destination, dev_list_id)

                    self.last_device_stream_id_by_dest[
                        destination] = device_stream_id
                    self.last_device_list_stream_id_by_dest[
                        destination] = dev_list_id
                else:
                    break
        except NotRetryingDestination as e:
            logger.debug(
                "TX [%s] not ready for retry yet (next retry at %s) - "
                "dropping transaction for now",
                destination,
                datetime.datetime.fromtimestamp(
                    (e.retry_last_ts + e.retry_interval) / 1000.0),
            )
        except FederationDeniedError as e:
            logger.info(e)
        except Exception as e:
            logger.warn(
                "TX [%s] Failed to send transaction: %s",
                destination,
                e,
            )
            for p, _ in pending_pdus:
                logger.info("Failed to send event %s to %s", p.event_id,
                            destination)
        finally:
            # We want to be *very* sure we delete this after we stop processing
            self.pending_transactions.pop(destination, None)
Ejemplo n.º 24
0
    def _attempt_new_transaction(self, destination):
        # list of (pending_pdu, deferred, order)
        if destination in self.pending_transactions:
            # XXX: pending_transactions can get stuck on by a never-ending
            # request at which point pending_pdus_by_dest just keeps growing.
            # we need application-layer timeouts of some flavour of these
            # requests
            logger.debug(
                "TX [%s] Transaction already in progress",
                destination
            )
            return

        try:
            self.pending_transactions[destination] = 1

            # XXX: what's this for?
            yield run_on_reactor()

            while True:
                limiter = yield get_retry_limiter(
                    destination,
                    self.clock,
                    self.store,
                    backoff_on_404=True,  # If we get a 404 the other side has gone
                )

                device_message_edus, device_stream_id, dev_list_id = (
                    yield self._get_new_device_messages(destination)
                )

                # BEGIN CRITICAL SECTION
                #
                # In order to avoid a race condition, we need to make sure that
                # the following code (from popping the queues up to the point
                # where we decide if we actually have any pending messages) is
                # atomic - otherwise new PDUs or EDUs might arrive in the
                # meantime, but not get sent because we hold the
                # pending_transactions flag.

                pending_pdus = self.pending_pdus_by_dest.pop(destination, [])
                pending_edus = self.pending_edus_by_dest.pop(destination, [])
                pending_presence = self.pending_presence_by_dest.pop(destination, {})
                pending_failures = self.pending_failures_by_dest.pop(destination, [])

                pending_edus.extend(
                    self.pending_edus_keyed_by_dest.pop(destination, {}).values()
                )

                pending_edus.extend(device_message_edus)
                if pending_presence:
                    pending_edus.append(
                        Edu(
                            origin=self.server_name,
                            destination=destination,
                            edu_type="m.presence",
                            content={
                                "push": [
                                    format_user_presence_state(
                                        presence, self.clock.time_msec()
                                    )
                                    for presence in pending_presence.values()
                                ]
                            },
                        )
                    )

                if pending_pdus:
                    logger.debug("TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                                 destination, len(pending_pdus))

                if not pending_pdus and not pending_edus and not pending_failures:
                    logger.debug("TX [%s] Nothing to send", destination)
                    self.last_device_stream_id_by_dest[destination] = (
                        device_stream_id
                    )
                    return

                # END CRITICAL SECTION

                success = yield self._send_new_transaction(
                    destination, pending_pdus, pending_edus, pending_failures,
                    limiter=limiter,
                )
                if success:
                    # Remove the acknowledged device messages from the database
                    # Only bother if we actually sent some device messages
                    if device_message_edus:
                        yield self.store.delete_device_msgs_for_remote(
                            destination, device_stream_id
                        )
                        logger.info("Marking as sent %r %r", destination, dev_list_id)
                        yield self.store.mark_as_sent_devices_by_remote(
                            destination, dev_list_id
                        )

                    self.last_device_stream_id_by_dest[destination] = device_stream_id
                    self.last_device_list_stream_id_by_dest[destination] = dev_list_id
                else:
                    break
        except NotRetryingDestination:
            logger.debug(
                "TX [%s] not ready for retry yet - "
                "dropping transaction for now",
                destination,
            )
        finally:
            # We want to be *very* sure we delete this after we stop processing
            self.pending_transactions.pop(destination, None)
Ejemplo n.º 25
0
    async def _snapshot_all_rooms(
        self,
        user_id: str,
        pagin_config: PaginationConfig,
        as_client_event: bool = True,
        include_archived: bool = False,
    ) -> JsonDict:

        memberships = [Membership.INVITE, Membership.JOIN]
        if include_archived:
            memberships.append(Membership.LEAVE)

        room_list = await self.store.get_rooms_for_local_user_where_membership_is(
            user_id=user_id, membership_list=memberships)

        user = UserID.from_string(user_id)

        rooms_ret = []

        now_token = self.hs.get_event_sources().get_current_token()

        presence_stream = self.hs.get_event_sources().sources["presence"]
        presence, _ = await presence_stream.get_new_events(
            user, from_key=None, include_offline=False)

        joined_rooms = [
            r.room_id for r in room_list if r.membership == Membership.JOIN
        ]
        receipt = await self.store.get_linearized_receipts_for_rooms(
            joined_rooms,
            to_key=int(now_token.receipt_key),
        )

        tags_by_room = await self.store.get_tags_for_user(user_id)

        account_data, account_data_by_room = await self.store.get_account_data_for_user(
            user_id)

        public_room_ids = await self.store.get_public_room_ids()

        limit = pagin_config.limit
        if limit is None:
            limit = 10

        async def handle_room(event: RoomsForUser):
            d = {
                "room_id":
                event.room_id,
                "membership":
                event.membership,
                "visibility":
                ("public" if event.room_id in public_room_ids else "private"),
            }

            if event.membership == Membership.INVITE:
                time_now = self.clock.time_msec()
                d["inviter"] = event.sender

                invite_event = await self.store.get_event(event.event_id)
                d["invite"] = await self._event_serializer.serialize_event(
                    invite_event, time_now, as_client_event)

            rooms_ret.append(d)

            if event.membership not in (Membership.JOIN, Membership.LEAVE):
                return

            try:
                if event.membership == Membership.JOIN:
                    room_end_token = now_token.room_key
                    deferred_room_state = run_in_background(
                        self.state_handler.get_current_state, event.room_id)
                elif event.membership == Membership.LEAVE:
                    room_end_token = RoomStreamToken(
                        None,
                        event.stream_ordering,
                    )
                    deferred_room_state = run_in_background(
                        self.state_store.get_state_for_events,
                        [event.event_id])
                    deferred_room_state.addCallback(
                        lambda states: states[event.event_id])

                (messages,
                 token), current_state = await make_deferred_yieldable(
                     defer.gatherResults([
                         run_in_background(
                             self.store.get_recent_events_for_room,
                             event.room_id,
                             limit=limit,
                             end_token=room_end_token,
                         ),
                         deferred_room_state,
                     ])).addErrback(unwrapFirstError)

                messages = await filter_events_for_client(
                    self.storage, user_id, messages)

                start_token = now_token.copy_and_replace("room_key", token)
                end_token = now_token.copy_and_replace("room_key",
                                                       room_end_token)
                time_now = self.clock.time_msec()

                d["messages"] = {
                    "chunk": (await self._event_serializer.serialize_events(
                        messages,
                        time_now=time_now,
                        as_client_event=as_client_event)),
                    "start":
                    await start_token.to_string(self.store),
                    "end":
                    await end_token.to_string(self.store),
                }

                d["state"] = await self._event_serializer.serialize_events(
                    current_state.values(),
                    time_now=time_now,
                    as_client_event=as_client_event,
                )

                account_data_events = []
                tags = tags_by_room.get(event.room_id)
                if tags:
                    account_data_events.append({
                        "type": "m.tag",
                        "content": {
                            "tags": tags
                        }
                    })

                account_data = account_data_by_room.get(event.room_id, {})
                for account_data_type, content in account_data.items():
                    account_data_events.append({
                        "type": account_data_type,
                        "content": content
                    })

                d["account_data"] = account_data_events
            except Exception:
                logger.exception("Failed to get snapshot")

        await concurrently_execute(handle_room, room_list, 10)

        account_data_events = []
        for account_data_type, content in account_data.items():
            account_data_events.append({
                "type": account_data_type,
                "content": content
            })

        now = self.clock.time_msec()

        ret = {
            "rooms":
            rooms_ret,
            "presence": [{
                "type": "m.presence",
                "content": format_user_presence_state(event, now),
            } for event in presence],
            "account_data":
            account_data_events,
            "receipts":
            receipt,
            "end":
            await now_token.to_string(self.store),
        }

        return ret
Ejemplo n.º 26
0
    def _attempt_new_transaction(self, destination):
        # list of (pending_pdu, deferred, order)
        if destination in self.pending_transactions:
            # XXX: pending_transactions can get stuck on by a never-ending
            # request at which point pending_pdus_by_dest just keeps growing.
            # we need application-layer timeouts of some flavour of these
            # requests
            logger.debug(
                "TX [%s] Transaction already in progress",
                destination
            )
            return

        try:
            self.pending_transactions[destination] = 1

            yield run_on_reactor()

            while True:
                    pending_pdus = self.pending_pdus_by_dest.pop(destination, [])
                    pending_edus = self.pending_edus_by_dest.pop(destination, [])
                    pending_presence = self.pending_presence_by_dest.pop(destination, {})
                    pending_failures = self.pending_failures_by_dest.pop(destination, [])

                    pending_edus.extend(
                        self.pending_edus_keyed_by_dest.pop(destination, {}).values()
                    )

                    limiter = yield get_retry_limiter(
                        destination,
                        self.clock,
                        self.store,
                    )

                    device_message_edus, device_stream_id = (
                        yield self._get_new_device_messages(destination)
                    )

                    pending_edus.extend(device_message_edus)
                    if pending_presence:
                        pending_edus.append(
                            Edu(
                                origin=self.server_name,
                                destination=destination,
                                edu_type="m.presence",
                                content={
                                    "push": [
                                        format_user_presence_state(
                                            presence, self.clock.time_msec()
                                        )
                                        for presence in pending_presence.values()
                                    ]
                                },
                            )
                        )

                    if pending_pdus:
                        logger.debug("TX [%s] len(pending_pdus_by_dest[dest]) = %d",
                                     destination, len(pending_pdus))

                    if not pending_pdus and not pending_edus and not pending_failures:
                        logger.debug("TX [%s] Nothing to send", destination)
                        self.last_device_stream_id_by_dest[destination] = (
                            device_stream_id
                        )
                        return

                    success = yield self._send_new_transaction(
                        destination, pending_pdus, pending_edus, pending_failures,
                        device_stream_id,
                        should_delete_from_device_stream=bool(device_message_edus),
                        limiter=limiter,
                    )
                    if not success:
                        break
        except NotRetryingDestination:
            logger.info(
                "TX [%s] not ready for retry yet - "
                "dropping transaction for now",
                destination,
            )
        finally:
            # We want to be *very* sure we delete this after we stop processing
            self.pending_transactions.pop(destination, None)