@@ -123,10 +123,15 @@ func (s *server) AddEDUHandler(eduHandler func(gomatrixserverlib.EDU) bool) func
123123 }
124124}
125125
126- // WithWaitForLeave runs the given action and waits for the user to leave the room.
126+ // WithWaitForLeave runs the given action and, when the resulting leave is
127+ // expected to reach this server, waits for it. `leaveAction` is always run; the
128+ // wait is skipped when `user` had already left the room (per their own
129+ // homeserver, so the action produces no new leave) or when this server isn't in
130+ // the room (so the leave won't be federated to us).
127131func (s * server ) WithWaitForLeave (
128- t * testing.T , room * federation.ServerRoom , userID string , leaveAction func (),
132+ t * testing.T , room * federation.ServerRoom , user * client. CSAPI , leaveAction func (),
129133) {
134+ userID := user .UserID
130135 leaveChannel := make (chan gomatrixserverlib.PDU , 10 )
131136 removePDUHandler := s .AddPDUHandler (
132137 func (e gomatrixserverlib.PDU ) bool {
@@ -141,24 +146,76 @@ func (s *server) WithWaitForLeave(
141146 )
142147 defer removePDUHandler ()
143148
149+ // We need to check if the user (on their homeserver) thinks they're in the
150+ // room, before performing the `leaveAction` (to avoid races).
151+ //
152+ // If they are not in the room, then the `leaveAction` will not produce a
153+ // new leave event and we should not wait for one.
154+ //
155+ // If they are in the room then the `leaveAction` will produce a new leave
156+ // event. We then need to check if we expect this server receive the leave
157+ // event by checking if this server is in the room. If they are, we wait, if
158+ // not we can return immediately after the `leaveAction`.
159+ userInRoom := userIsJoinedTo (t , user , room .RoomID )
160+
144161 leaveAction ()
145162
146- memberEvent := room .CurrentState ("m.room.member" , userID )
147- membership := ""
148- if memberEvent != nil {
149- membership , _ = memberEvent .Membership ()
163+ if ! userInRoom {
164+ // The user had already left, so the action produced no new leave and
165+ // none is coming: don't wait.
166+ t .Logf ("%s is not joined to test room %s; not waiting for them to leave." , userID , room .RoomID )
167+ return
150168 }
151- if membership == "leave" {
152- t .Logf ("%s has already seen %s leave test room %s." , s .ServerName (), userID , room .RoomID )
153- } else {
154- select {
155- case <- leaveChannel :
156- t .Logf ("%s saw %s leave test room %s." , s .ServerName (), userID , room .RoomID )
157- break
158- case <- time .After (1 * time .Second ):
159- t .Errorf ("%s timed out waiting for %s to leave test room %s." , s .ServerName (), userID , room .RoomID )
169+
170+ if ! s .isInRoom (room ) {
171+ // The homeserver only federates the leave to servers that are in the
172+ // room. If we aren't, no leave PDU is coming to us, so don't block until
173+ // the timeout.
174+ t .Logf ("%s is not in test room %s; not waiting for %s to leave." , s .ServerName (), room .RoomID , userID )
175+ return
176+ }
177+
178+ // Otherwise the action triggered the leave, which arrives as a PDU our
179+ // handler matches. Wait on its channel rather than polling
180+ // `room.CurrentState`: the room's current state is updated (by
181+ // `room.AddEvent`) *before* the PDU callback runs, so returning on a
182+ // `CurrentState` check could deregister our handler in the window before the
183+ // callback fires, making the (expected) leave look unexpected to
184+ // `HandleTransactionRequests`. This returns as soon as the leave arrives; the
185+ // timeout is only a ceiling for declaring failure.
186+ select {
187+ case <- leaveChannel :
188+ t .Logf ("%s saw %s leave test room %s." , s .ServerName (), userID , room .RoomID )
189+ case <- time .After (1 * time .Second ):
190+ t .Errorf ("%s timed out waiting for %s to leave test room %s." , s .ServerName (), userID , room .RoomID )
191+ }
192+ }
193+
194+ // isInRoom reports whether this Complement server has a joined user in the room,
195+ // according to its own `ServerRoom` view. The server reliably tracks its own
196+ // users' membership (it created their join/leave events), so this answers "will
197+ // the homeserver federate events in this room to us?".
198+ func (s * server ) isInRoom (room * federation.ServerRoom ) bool {
199+ for _ , serverInRoom := range room .ServersInRoom () {
200+ if serverInRoom == s .ServerName () {
201+ return true
202+ }
203+ }
204+ return false
205+ }
206+
207+ // userIsJoinedTo reports whether the user is currently joined to the room,
208+ // according to the user's own homeserver.
209+ func userIsJoinedTo (t * testing.T , user * client.CSAPI , roomID string ) bool {
210+ t .Helper ()
211+ res := user .MustDo (t , "GET" , []string {"_matrix" , "client" , "v3" , "joined_rooms" })
212+ joinedRooms := gjson .ParseBytes (client .ParseJSON (t , res )).Get ("joined_rooms" )
213+ for _ , joinedRoom := range joinedRooms .Array () {
214+ if joinedRoom .Str == roomID {
215+ return true
160216 }
161217 }
218+ return false
162219}
163220
164221// Wait for the server to receive the event with given event ID.
@@ -2249,7 +2306,7 @@ func TestPartialStateJoin(t *testing.T) {
22492306
22502307 // @t23alice:hs1 joins the room.
22512308 psjResult := beginPartialStateJoin (t , server1 , room , alice )
2252- defer server2 .WithWaitForLeave (t , server2Room , alice . UserID , func () { psjResult .Destroy (t ) })
2309+ defer server2 .WithWaitForLeave (t , server2Room , alice , func () { psjResult .Destroy (t ) })
22532310
22542311 // Both homeservers should receive device list updates.
22552312 renameDevice (t , alice , "A new device name 1" )
@@ -2296,7 +2353,7 @@ func TestPartialStateJoin(t *testing.T) {
22962353 )
22972354 // NB: We register the `psjResult.Destroy()` cleanup twice. This is alright because it
22982355 // is idempotent. Here we wait for server 2 to observe the leave too.
2299- defer server2 .WithWaitForLeave (t , server2Room , alice . UserID , func () { psjResult .Destroy (t ) })
2356+ defer server2 .WithWaitForLeave (t , server2Room , alice , func () { psjResult .Destroy (t ) })
23002357 joinEvent := room .CurrentState ("m.room.member" , server2 .UserID ("elsie" ))
23012358 server1 .MustSendTransaction (t , deployment , deployment .GetFullyQualifiedHomeserverName (t , "hs1" ), []json.RawMessage {joinEvent .JSON ()}, nil )
23022359 awaitEventViaSync (t , alice , room .RoomID , joinEvent .EventID (), "" )
@@ -2473,7 +2530,7 @@ func TestPartialStateJoin(t *testing.T) {
24732530 syncToken = awaitEventViaSync (t , alice , partialStateRoom .RoomID , leaveEvent .EventID (), syncToken )
24742531
24752532 leaveSharedRoom = func () {
2476- server2 .WithWaitForLeave (t , server2Room , alice . UserID , func () {
2533+ server2 .WithWaitForLeave (t , server2Room , alice , func () {
24772534 alice .MustLeaveRoom (t , roomID )
24782535 })
24792536 }
@@ -2533,7 +2590,7 @@ func TestPartialStateJoin(t *testing.T) {
25332590 // @t26alice:hs1 joins the room, followed by @elsie:server2.
25342591 // @elsie:server2 is kicked with an invalid event.
25352592 syncToken , server2Room , psjResult := setupIncorrectlyAcceptedKick (t , deployment , alice , server1 , server2 , deviceListUpdateChannel1 , deviceListUpdateChannel2 , room )
2536- defer server2 .WithWaitForLeave (t , server2Room , alice . UserID , func () { psjResult .Destroy (t ) })
2593+ defer server2 .WithWaitForLeave (t , server2Room , alice , func () { psjResult .Destroy (t ) })
25372594
25382595 // @t26alice:hs1 sends out a device list update which is missed by @elsie:server2.
25392596 // @elsie:server2 must receive missed device list updates once the partial state join finishes.
@@ -2593,7 +2650,7 @@ func TestPartialStateJoin(t *testing.T) {
25932650 federation .WithPartialState (),
25942651 )
25952652 psjResult := beginPartialStateJoin (t , server1 , room , alice )
2596- defer server2 .WithWaitForLeave (t , server2Room , alice . UserID , func () { psjResult .Destroy (t ) })
2653+ defer server2 .WithWaitForLeave (t , server2Room , alice , func () { psjResult .Destroy (t ) })
25972654
25982655 // @t28alice:hs1 sends out a device list update which is missed by @elsie:server2.
25992656 // @elsie:server2 must receive missed device list updates once the partial state join finishes.
@@ -2996,7 +3053,7 @@ func TestPartialStateJoin(t *testing.T) {
29963053 },
29973054 client .SyncJoinedTo (server .UserID ("charlie" ), otherRoomID ),
29983055 )
2999- defer server .WithWaitForLeave (t , otherRoom , alice . UserID , func () { alice .MustLeaveRoom (t , otherRoomID ) })
3056+ defer server .WithWaitForLeave (t , otherRoom , alice , func () { alice .MustLeaveRoom (t , otherRoomID ) })
30003057
30013058 // Depending on the homeserver implementation, @t31alice:hs1 must have been told that either:
30023059 // * charlie updated their device list, or
@@ -4422,7 +4479,7 @@ func (psj *partialStateJoinResult) Destroy(t *testing.T) {
44224479 psj .Server .WithWaitForLeave (
44234480 t ,
44244481 psj .ServerRoom ,
4425- psj .User . UserID ,
4482+ psj .User ,
44264483 func () { psj .User .MustLeaveRoom (t , psj .ServerRoom .RoomID ) },
44274484 )
44284485}
0 commit comments