Skip to content

Commit b744d2d

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 `room.CurrentState`, so the handler stays registered until its own callback has run. Every caller passes a joined user and an action that performs the leave, so the leave always arrives fresh via that callback. Make this contract explicit: snapshot membership before the action and fail if the user has already left (rather than silently treating it as "already seen"), and fail on timeout, since neither should happen in correct usage. The action still runs in the already-left case so any cleanup it performs is preserved. The `HandleTransactionRequests` ordering (callback after `room.AddEvent`) is left unchanged, as other tests rely on it (e.g. `federation_redaction_test.go` reads `serverRoom.Timeline` immediately after a callback-driven waiter).
1 parent db8c562 commit b744d2d

1 file changed

Lines changed: 24 additions & 13 deletions

File tree

tests/msc3902/federation_room_join_partial_state_test.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,34 @@ func (s *server) WithWaitForLeave(
141141
)
142142
defer removePDUHandler()
143143

144-
leaveAction()
145-
144+
// `WithWaitForLeave` expects the user to be in the room and to leave as a
145+
// result of the action, so that the leave arrives as a PDU our handler
146+
// matches. Snapshot the membership *before* the action: if the user has
147+
// already left, no such PDU is coming, which is a test bug rather than
148+
// something to wait for.
146149
memberEvent := room.CurrentState("m.room.member", userID)
147-
membership := ""
150+
membership := "leave"
148151
if memberEvent != nil {
149152
membership, _ = memberEvent.Membership()
150153
}
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)
160-
}
154+
alreadyLeft := membership == "leave"
155+
if alreadyLeft {
156+
t.Errorf("%s: %s had already left test room %s before WithWaitForLeave ran.", s.ServerName(), userID, room.RoomID)
157+
}
158+
159+
leaveAction()
160+
161+
// Wait on the channel fed by our PDU handler rather than polling
162+
// `room.CurrentState`: the room's current state is updated (by
163+
// `room.AddEvent`) *before* the PDU callback runs, so returning on a
164+
// `CurrentState` check could deregister our handler in the window before
165+
// the callback fires, making the (expected) leave look unexpected to
166+
// `HandleTransactionRequests`.
167+
select {
168+
case <-leaveChannel:
169+
t.Logf("%s saw %s leave test room %s.", s.ServerName(), userID, room.RoomID)
170+
case <-time.After(1 * time.Second):
171+
t.Errorf("%s timed out waiting for %s to leave test room %s.", s.ServerName(), userID, room.RoomID)
161172
}
162173
}
163174

0 commit comments

Comments
 (0)