示例#1
0
    async def on_incoming_transaction(
        self, origin: str, transaction_data: JsonDict
    ) -> Tuple[int, Dict[str, Any]]:
        # keep this as early as possible to make the calculated origin ts as
        # accurate as possible.
        request_time = self._clock.time_msec()

        transaction = Transaction(**transaction_data)

        if not transaction.transaction_id:  # type: ignore
            raise Exception("Transaction missing transaction_id")

        logger.debug("[%s] Got transaction", transaction.transaction_id)  # type: ignore

        # use a linearizer to ensure that we don't process the same transaction
        # multiple times in parallel.
        with (
            await self._transaction_linearizer.queue(
                (origin, transaction.transaction_id)  # type: ignore
            )
        ):
            result = await self._handle_incoming_transaction(
                origin, transaction, request_time
            )

        return result
示例#2
0
 def _transaction_from_pdus(self, pdu_list: List[EventBase]) -> Transaction:
     """Returns a new Transaction containing the given PDUs suitable for
     transmission.
     """
     time_now = self._clock.time_msec()
     pdus = [p.get_pdu_json(time_now) for p in pdu_list]
     return Transaction(
         origin=self.server_name,
         pdus=pdus,
         origin_server_ts=int(time_now),
         destination=None,
     )
示例#3
0
    async def on_incoming_transaction(
            self, origin: str,
            transaction_data: JsonDict) -> Tuple[int, Dict[str, Any]]:
        # keep this as early as possible to make the calculated origin ts as
        # accurate as possible.
        request_time = self._clock.time_msec()

        transaction = Transaction(**transaction_data)
        transaction_id = transaction.transaction_id  # type: ignore

        if not transaction_id:
            raise Exception("Transaction missing transaction_id")

        logger.debug("[%s] Got transaction", transaction_id)

        # Reject malformed transactions early: reject if too many PDUs/EDUs
        if len(transaction.pdus) > 50 or (  # type: ignore
                hasattr(transaction, "edus")
                and len(transaction.edus) > 100  # type: ignore
        ):
            logger.info(
                "Transaction PDU or EDU count too large. Returning 400")
            return 400, {}

        # we only process one transaction from each origin at a time. We need to do
        # this check here, rather than in _on_incoming_transaction_inner so that we
        # don't cache the rejection in _transaction_resp_cache (so that if the txn
        # arrives again later, we can process it).
        current_transaction = self._active_transactions.get(origin)
        if current_transaction and current_transaction != transaction_id:
            logger.warning(
                "Received another txn %s from %s while still processing %s",
                transaction_id,
                origin,
                current_transaction,
            )
            return 429, {
                "errcode": Codes.UNKNOWN,
                "error": "Too many concurrent transactions",
            }

        # CRITICAL SECTION: we must now not await until we populate _active_transactions
        # in _on_incoming_transaction_inner.

        # We wrap in a ResponseCache so that we de-duplicate retried
        # transactions.
        return await self._transaction_resp_cache.wrap(
            (origin, transaction_id),
            self._on_incoming_transaction_inner,
            origin,
            transaction,
            request_time,
        )
示例#4
0
 def _transaction_dict_from_pdus(self, pdu_list: List[EventBase]) -> JsonDict:
     """Returns a new Transaction containing the given PDUs suitable for
     transmission.
     """
     time_now = self._clock.time_msec()
     pdus = [p.get_pdu_json(time_now) for p in pdu_list]
     return Transaction(
         # Just need a dummy transaction ID and destination since it won't be used.
         transaction_id="",
         origin=self.server_name,
         pdus=pdus,
         origin_server_ts=int(time_now),
         destination="",
     ).get_dict()
示例#5
0
    def on_incoming_transaction(self, origin, transaction_data):
        # keep this as early as possible to make the calculated origin ts as
        # accurate as possible.
        request_time = self._clock.time_msec()

        transaction = Transaction(**transaction_data)

        if not transaction.transaction_id:
            raise Exception("Transaction missing transaction_id")

        logger.debug("[%s] Got transaction", transaction.transaction_id)

        # use a linearizer to ensure that we don't process the same transaction
        # multiple times in parallel.
        with (yield self._transaction_linearizer.queue(
            (origin, transaction.transaction_id))):
            result = yield self._handle_incoming_transaction(
                origin, transaction, request_time)

        defer.returnValue(result)
示例#6
0
    async def on_incoming_transaction(
            self, origin: str,
            transaction_data: JsonDict) -> Tuple[int, Dict[str, Any]]:
        # keep this as early as possible to make the calculated origin ts as
        # accurate as possible.
        request_time = self._clock.time_msec()

        transaction = Transaction(**transaction_data)
        transaction_id = transaction.transaction_id  # type: ignore

        if not transaction_id:
            raise Exception("Transaction missing transaction_id")

        logger.debug("[%s] Got transaction", transaction_id)

        # We wrap in a ResponseCache so that we de-duplicate retried
        # transactions.
        return await self._transaction_resp_cache.wrap(
            (origin, transaction_id),
            self._on_incoming_transaction_inner,
            origin,
            transaction,
            request_time,
        )
示例#7
0
    async def send_new_transaction(
        self,
        destination: str,
        pdus: List[EventBase],
        edus: List[Edu],
    ) -> None:
        """
        Args:
            destination: The destination to send to (e.g. 'example.org')
            pdus: In-order list of PDUs to send
            edus: List of EDUs to send
        """

        # Make a transaction-sending opentracing span. This span follows on from
        # all the edus in that transaction. This needs to be done since there is
        # no active span here, so if the edus were not received by the remote the
        # span would have no causality and it would be forgotten.

        span_contexts = []
        keep_destination = whitelisted_homeserver(destination)

        for edu in edus:
            context = edu.get_context()
            if context:
                span_contexts.append(extract_text_map(json_decoder.decode(context)))
            if keep_destination:
                edu.strip_context()

        with start_active_span_follows_from("send_transaction", span_contexts):
            logger.debug("TX [%s] _attempt_new_transaction", destination)

            txn_id = str(self._next_txn_id)

            logger.debug(
                "TX [%s] {%s} Attempting new transaction (pdus: %d, edus: %d)",
                destination,
                txn_id,
                len(pdus),
                len(edus),
            )

            transaction = Transaction(
                origin_server_ts=int(self.clock.time_msec()),
                transaction_id=txn_id,
                origin=self._server_name,
                destination=destination,
                pdus=[p.get_pdu_json() for p in pdus],
                edus=[edu.get_dict() for edu in edus],
            )

            self._next_txn_id += 1

            logger.info(
                "TX [%s] {%s} Sending transaction [%s], (PDUs: %d, EDUs: %d)",
                destination,
                txn_id,
                transaction.transaction_id,
                len(pdus),
                len(edus),
            )

            # Actually send the transaction

            # FIXME (erikj): This is a bit of a hack to make the Pdu age
            # keys work
            # FIXME (richardv): I also believe it no longer works. We (now?) store
            #  "age_ts" in "unsigned" rather than at the top level. See
            #  https://github.com/matrix-org/synapse/issues/8429.
            def json_data_cb() -> JsonDict:
                data = transaction.get_dict()
                now = int(self.clock.time_msec())
                if "pdus" in data:
                    for p in data["pdus"]:
                        if "age_ts" in p:
                            unsigned = p.setdefault("unsigned", {})
                            unsigned["age"] = now - int(p["age_ts"])
                            del p["age_ts"]
                return data

            try:
                response = await self._transport_layer.send_transaction(
                    transaction, json_data_cb
                )
            except HttpResponseException as e:
                code = e.code

                set_tag(tags.ERROR, True)

                logger.info("TX [%s] {%s} got %d response", destination, txn_id, code)
                raise

            logger.info("TX [%s] {%s} got 200 response", destination, txn_id)

            for e_id, r in response.get("pdus", {}).items():
                if "error" in r:
                    logger.warning(
                        "TX [%s] {%s} Remote returned error for %s: %s",
                        destination,
                        txn_id,
                        e_id,
                        r,
                    )

            if pdus and destination in self._federation_metrics_domains:
                last_pdu = pdus[-1]
                last_pdu_ts_metric.labels(server_name=destination).set(
                    last_pdu.origin_server_ts / 1000
                )
示例#8
0
    async def on_incoming_transaction(
        self,
        origin: str,
        transaction_id: str,
        destination: str,
        transaction_data: JsonDict,
    ) -> Tuple[int, JsonDict]:
        # If we receive a transaction we should make sure that kick off handling
        # any old events in the staging area.
        if not self._started_handling_of_staged_events:
            self._started_handling_of_staged_events = True
            self._handle_old_staged_events()

        # keep this as early as possible to make the calculated origin ts as
        # accurate as possible.
        request_time = self._clock.time_msec()

        transaction = Transaction(
            transaction_id=transaction_id,
            destination=destination,
            origin=origin,
            origin_server_ts=transaction_data.get("origin_server_ts"),  # type: ignore
            pdus=transaction_data.get("pdus"),  # type: ignore
            edus=transaction_data.get("edus"),
        )

        if not transaction_id:
            raise Exception("Transaction missing transaction_id")

        logger.debug("[%s] Got transaction", transaction_id)

        # Reject malformed transactions early: reject if too many PDUs/EDUs
        if len(transaction.pdus) > 50 or len(transaction.edus) > 100:
            logger.info("Transaction PDU or EDU count too large. Returning 400")
            return 400, {}

        # we only process one transaction from each origin at a time. We need to do
        # this check here, rather than in _on_incoming_transaction_inner so that we
        # don't cache the rejection in _transaction_resp_cache (so that if the txn
        # arrives again later, we can process it).
        current_transaction = self._active_transactions.get(origin)
        if current_transaction and current_transaction != transaction_id:
            logger.warning(
                "Received another txn %s from %s while still processing %s",
                transaction_id,
                origin,
                current_transaction,
            )
            return 429, {
                "errcode": Codes.UNKNOWN,
                "error": "Too many concurrent transactions",
            }

        # CRITICAL SECTION: we must now not await until we populate _active_transactions
        # in _on_incoming_transaction_inner.

        # We wrap in a ResponseCache so that we de-duplicate retried
        # transactions.
        return await self._transaction_resp_cache.wrap(
            (origin, transaction_id),
            self._on_incoming_transaction_inner,
            origin,
            transaction,
            request_time,
        )