# Buggy code
async def get_pdu(
    self,
    destinations: Iterable[str],
    event_id: str,
    room_version: RoomVersion,
    outlier: bool = False,
    timeout: Optional[int] = None,
) -> Optional[EventBase]:
    """Requests the PDU with given origin and ID from the remote home
    servers.

    Will attempt to get the PDU from each destination in the list until
    one succeeds.

    Args:
        destinations: Which homeservers to query
        event_id: event to fetch
        room_version: version of the room
        outlier: Indicates whether the PDU is an `outlier`, i.e. if
            it's from an arbitary point in the context as opposed to part
            of the current block of PDUs. Defaults to `False`
        timeout: How long to try (in ms) each destination for before
            moving to the next destination. None indicates no timeout.

    Returns:
        The requested PDU, or None if we were unable to find it.
    """

    # TODO: Rate limit the number of times we try and get the same event.

    ev = self._get_pdu_cache.get(event_id)
    if ev:
        return ev

    pdu_attempts = self.pdu_destination_tried.setdefault(event_id, {})

    signed_pdu = None
    for destination in destinations:
        now = self._clock.time_msec()
        last_attempt = pdu_attempts.get(destination, 0)
        if last_attempt + PDU_RETRY_TIME_MS > now:
            continue

        try:
            transaction_data = await self.transport_layer.get_event(
                destination, event_id, timeout=timeout
            )

            logger.debug(
                "retrieved event id %s from %s: %r",
                event_id,
                destination,
                transaction_data,
            )

            pdu_list = [
                event_from_pdu_json(p, room_version, outlier=outlier)
                for p in transaction_data["pdus"]
            ]

            if pdu_list and pdu_list[0]:
                pdu = pdu_list[0]

                # Check signatures are correct.
                signed_pdu = await self._check_sigs_and_hash(
                    room_version.identifier, pdu
                )

                break

            pdu_attempts[destination] = now

        except SynapseError as e:
            logger.info(
                "Failed to get PDU %s from %s because %s", event_id, destination, e
            )
            continue
        except NotRetryingDestination as e:
            logger.info(str(e))
            continue
        except FederationDeniedError as e:
            logger.info(str(e))
            continue
        except Exception as e:
            pdu_attempts[destination] = now

            logger.info(
                "Failed to get PDU %s from %s because %s", event_id, destination, e
            )
            continue

    if signed_pdu:
        self._get_pdu_cache[event_id] = signed_pdu

    return signed_pdu


# Traceback (AttributeError)
2020-02-24 14:00:10,088 - synapse.federation.federation_client - 421 - WARNING - POST-1957- Failed to send_join via matrix.org
...
Traceback (most recent call last):
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 402, in _try_destination_list
res = await callback(destination)
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 589, in send_request
valid_pdus_map = {p.event_id: p for p in valid_pdus}
File "/home/synapse/matrixtest/synapse/federation/federation_client.py", line 589, in <dictcomp>
valid_pdus_map = {p.event_id: p for p in valid_pdus}
AttributeError: 'coroutine' object has no attribute 'event_id'
2020-02-24 14:00:10,154 - synapse.http.server - 81 - INFO - POST-1957- <SynapseRequest at 0x7ff1bf3efd30 method='POST' uri='/_matrix/client/r0/join/%23synapse%3Amatrix.org' clientproto='HTTP/1.1' site=8447> SynapseError: 502 - Failed to send_join via any server

# Fixed code
async def get_pdu(
    self,
    destinations: Iterable[str],
    event_id: str,
    room_version: RoomVersion,
    outlier: bool = False,
    timeout: Optional[int] = None,
) -> Optional[EventBase]:
    """Requests the PDU with given origin and ID from the remote home
    servers.

    Will attempt to get the PDU from each destination in the list until
    one succeeds.

    Args:
        destinations: Which homeservers to query
        event_id: event to fetch
        room_version: version of the room
        outlier: Indicates whether the PDU is an `outlier`, i.e. if
            it's from an arbitary point in the context as opposed to part
            of the current block of PDUs. Defaults to `False`
        timeout: How long to try (in ms) each destination for before
            moving to the next destination. None indicates no timeout.

    Returns:
        The requested PDU, or None if we were unable to find it.
    """

    # TODO: Rate limit the number of times we try and get the same event.

    ev = self._get_pdu_cache.get(event_id)
    if ev:
        return ev

    pdu_attempts = self.pdu_destination_tried.setdefault(event_id, {})

    signed_pdu = None
    for destination in destinations:
        now = self._clock.time_msec()
        last_attempt = pdu_attempts.get(destination, 0)
        if last_attempt + PDU_RETRY_TIME_MS > now:
            continue

        try:
            transaction_data = await self.transport_layer.get_event(
                destination, event_id, timeout=timeout
            )

            logger.debug(
                "retrieved event id %s from %s: %r",
                event_id,
                destination,
                transaction_data,
            )

            pdu_list = [
                event_from_pdu_json(p, room_version, outlier=outlier)
                for p in transaction_data["pdus"]
            ]  # type: List[EventBase]

            if pdu_list and pdu_list[0]:
                pdu = pdu_list[0]

                # Check signatures are correct.
                signed_pdu = await self._check_sigs_and_hash(
                    room_version.identifier, pdu
                )

                break

            pdu_attempts[destination] = now

        except SynapseError as e:
            logger.info(
                "Failed to get PDU %s from %s because %s", event_id, destination, e
            )
            continue
        except NotRetryingDestination as e:
            logger.info(str(e))
            continue
        except FederationDeniedError as e:
            logger.info(str(e))
            continue
        except Exception as e:
            pdu_attempts[destination] = now

            logger.info(
                "Failed to get PDU %s from %s because %s", event_id, destination, e
            )
            continue

    if signed_pdu:
        self._get_pdu_cache[event_id] = signed_pdu

    return signed_pdu