Skip to content

Commit 27a2bb1

Browse files
committed
Fix flaky TestPartialStateJoin/Outgoing_device_list_updates from a leave-handler race
`WithWaitForLeave` registered a PDU handler (whose callback feeds leaveChannel) but then decided whether to wait by peeking at `room.CurrentState`. Because an incoming PDU is added to the room (`HandleTransactionRequests` calls `room.AddEvent`, updating `CurrentState`) *before* the PDU callback runs, the test goroutine could observe the updated state, take the "already seen" shortcut and return, firing the deferred removePDUHandler() in the window before the callback ran. The now-handler-less callback then flagged the (expected) leave as an unexpected PDU, failing the test. Wait on the channel fed by the handler instead of polling `CurrentState`, so the handler stays registered until its own callback has run. Snapshot membership before the action to distinguish a pre-existing leave (nothing to wait for) from a fresh one, and keep a `CurrentState` check only as a timeout fallback for leaves applied via non-callback paths.
1 parent db8c562 commit 27a2bb1

1 file changed

Lines changed: 33 additions & 12 deletions

File tree

tests/msc3902/federation_room_join_partial_state_test.go

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,42 @@ func (s *server) WithWaitForLeave(
141141
)
142142
defer removePDUHandler()
143143

144+
currentMembership := func() string {
145+
memberEvent := room.CurrentState("m.room.member", userID)
146+
if memberEvent == nil {
147+
return ""
148+
}
149+
membership, _ := memberEvent.Membership()
150+
return membership
151+
}
152+
153+
// Snapshot the membership *before* running the action. If the user has
154+
// already left, the leave PDU predates our handler, so no PDU callback is
155+
// pending for it and there is nothing to wait for.
156+
alreadyLeft := currentMembership() == "leave"
157+
144158
leaveAction()
145159

146-
memberEvent := room.CurrentState("m.room.member", userID)
147-
membership := ""
148-
if memberEvent != nil {
149-
membership, _ = memberEvent.Membership()
150-
}
151-
if membership == "leave" {
160+
if alreadyLeft {
152161
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):
162+
return
163+
}
164+
165+
// Otherwise the leave is expected to arrive as a result of the action. Wait
166+
// on the channel fed by our PDU handler rather than polling
167+
// room.CurrentState: the room's current state is updated (by room.AddEvent)
168+
// *before* the PDU callback runs, so returning on a CurrentState check could
169+
// deregister our handler in the window before the callback fires, making the
170+
// (expected) leave look unexpected to HandleTransactionRequests.
171+
select {
172+
case <-leaveChannel:
173+
t.Logf("%s saw %s leave test room %s.", s.ServerName(), userID, room.RoomID)
174+
case <-time.After(1 * time.Second):
175+
// Fall back to the room state in case the leave was applied via a path
176+
// that doesn't fire the PDU callback (e.g. added to the room directly).
177+
if currentMembership() == "leave" {
178+
t.Logf("%s has already seen %s leave test room %s.", s.ServerName(), userID, room.RoomID)
179+
} else {
159180
t.Errorf("%s timed out waiting for %s to leave test room %s.", s.ServerName(), userID, room.RoomID)
160181
}
161182
}

0 commit comments

Comments
 (0)