2222 Awaitable ,
2323 Callable ,
2424 Collection ,
25+ Container ,
2526 Dict ,
2627 Iterable ,
2728 List ,
@@ -513,6 +514,7 @@ async def _try_destination_list(
513514 description : str ,
514515 destinations : Iterable [str ],
515516 callback : Callable [[str ], Awaitable [T ]],
517+ failover_errcodes : Optional [Container [str ]] = None ,
516518 failover_on_unknown_endpoint : bool = False ,
517519 ) -> T :
518520 """Try an operation on a series of servers, until it succeeds
@@ -533,6 +535,9 @@ async def _try_destination_list(
533535 next server tried. Normally the stacktrace is logged but this is
534536 suppressed if the exception is an InvalidResponseError.
535537
538+ failover_errcodes: Error codes (specific to this endpoint) which should
539+ cause a failover when received as part of an HTTP 400 error.
540+
536541 failover_on_unknown_endpoint: if True, we will try other servers if it looks
537542 like a server doesn't support the endpoint. This is typically useful
538543 if the endpoint in question is new or experimental.
@@ -544,6 +549,9 @@ async def _try_destination_list(
544549 SynapseError if the chosen remote server returns a 300/400 code, or
545550 no servers were reachable.
546551 """
552+ if failover_errcodes is None :
553+ failover_errcodes = ()
554+
547555 for destination in destinations :
548556 if destination == self .server_name :
549557 continue
@@ -558,11 +566,17 @@ async def _try_destination_list(
558566 synapse_error = e .to_synapse_error ()
559567 failover = False
560568
561- # Failover on an internal server error, or if the destination
562- # doesn't implemented the endpoint for some reason.
569+ # Failover should occur:
570+ #
571+ # * On internal server errors.
572+ # * If the destination responds that it cannot complete the request.
573+ # * If the destination doesn't implemented the endpoint for some reason.
563574 if 500 <= e .code < 600 :
564575 failover = True
565576
577+ elif e .code == 400 and synapse_error .errcode in failover_errcodes :
578+ failover = True
579+
566580 elif failover_on_unknown_endpoint and self ._is_unknown_endpoint (
567581 e , synapse_error
568582 ):
@@ -678,8 +692,20 @@ async def send_request(destination: str) -> Tuple[str, EventBase, RoomVersion]:
678692
679693 return destination , ev , room_version
680694
695+ # MSC3083 defines additional error codes for room joins. Unfortunately
696+ # we do not yet know the room version, assume these will only be returned
697+ # by valid room versions.
698+ failover_errcodes = (
699+ (Codes .UNABLE_AUTHORISE_JOIN , Codes .UNABLE_TO_GRANT_JOIN )
700+ if membership == Membership .JOIN
701+ else None
702+ )
703+
681704 return await self ._try_destination_list (
682- "make_" + membership , destinations , send_request
705+ "make_" + membership ,
706+ destinations ,
707+ send_request ,
708+ failover_errcodes = failover_errcodes ,
683709 )
684710
685711 async def send_join (
@@ -818,7 +844,14 @@ async def _execute(pdu: EventBase) -> None:
818844 origin = destination ,
819845 )
820846
847+ # MSC3083 defines additional error codes for room joins.
848+ failover_errcodes = None
821849 if room_version .msc3083_join_rules :
850+ failover_errcodes = (
851+ Codes .UNABLE_AUTHORISE_JOIN ,
852+ Codes .UNABLE_TO_GRANT_JOIN ,
853+ )
854+
822855 # If the join is being authorised via allow rules, we need to send
823856 # the /send_join back to the same server that was originally used
824857 # with /make_join.
@@ -827,7 +860,9 @@ async def _execute(pdu: EventBase) -> None:
827860 get_domain_from_id (pdu .content ["join_authorised_via_users_server" ])
828861 ]
829862
830- return await self ._try_destination_list ("send_join" , destinations , send_request )
863+ return await self ._try_destination_list (
864+ "send_join" , destinations , send_request , failover_errcodes = failover_errcodes
865+ )
831866
832867 async def _do_send_join (
833868 self , room_version : RoomVersion , destination : str , pdu : EventBase
0 commit comments