From 84dc8b9039621a2b55829bad8cc1dfec19253edd Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 16 Jan 2023 15:06:37 +0100 Subject: [PATCH 1/3] Change tests to accomodate non blocking regular sync --- ...federation_room_join_partial_state_test.go | 106 ++++++------------ 1 file changed, 36 insertions(+), 70 deletions(-) diff --git a/tests/federation_room_join_partial_state_test.go b/tests/federation_room_join_partial_state_test.go index 4d12b940..a8898de9 100644 --- a/tests/federation_room_join_partial_state_test.go +++ b/tests/federation_room_join_partial_state_test.go @@ -119,8 +119,8 @@ func TestPartialStateJoin(t *testing.T) { defer deployment.Destroy(t) // test that a regular /sync request made during a partial-state /send_join - // request blocks until the state is correctly synced. - t.Run("SyncBlocksDuringPartialStateJoin", func(t *testing.T) { + // request does not return the room until the state is correctly synced. + t.Run("RegularSyncDuringPartialStateJoin", func(t *testing.T) { alice := deployment.RegisterUser(t, "hs1", "t1alice", "secret", false) server := createTestServer(t, deployment) @@ -132,38 +132,26 @@ func TestPartialStateJoin(t *testing.T) { // Alice has now joined the room, and the server is syncing the state in the background. - // attempts to sync should now block. Fire off a goroutine to try it. - syncResponseChan := make(chan gjson.Result) - go func() { - response, _ := alice.MustSync(t, client.SyncReq{}) - syncResponseChan <- response - close(syncResponseChan) - }() + // Regular sync shouldn't include the room yet + response, nextBatch := alice.MustSync(t, client.SyncReq{}) + + syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(serverRoom.RoomID) + if response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Regular sync shouldn't include the joined room until resync is over") + } // wait for the state_ids request to arrive psjResult.AwaitStateIdsRequest(t) - // the client-side requests should still be waiting - select { - case <-syncResponseChan: - t.Fatalf("Sync completed before state resync complete") - default: - } - // release the federation /state response psjResult.FinishStateRequest() // the /sync request should now complete, with the new room - var syncRes gjson.Result - select { - case <-time.After(1 * time.Second): - t.Fatalf("/sync request request did not complete") - case syncRes = <-syncResponseChan: - } + response, _ = alice.MustSync(t, client.SyncReq{Since: nextBatch}) - roomRes := syncRes.Get("rooms.join." + client.GjsonEscape(serverRoom.RoomID)) + roomRes := response.Get(syncJoinedRoomPath) if !roomRes.Exists() { - t.Fatalf("/sync completed without join to new room\n") + t.Fatal("Regular sync should now include the joined room since resync is over") } // check that the state includes both charlie and derek. @@ -933,44 +921,35 @@ func TestPartialStateJoin(t *testing.T) { // wait for the state_ids request to arrive psjResult.AwaitStateIdsRequest(t) + // Regular sync shouldn't include the room yet + response, nextBatch := alice.MustSync(t, client.SyncReq{}) + + syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(serverRoom.RoomID) + if response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Regular sync shouldn't include the joined room until resync is over") + } + // restart the homeserver err := deployment.Restart(t) if err != nil { t.Errorf("Failed to restart homeserver: %s", err) } - // attempts to sync should block. Fire off a goroutine to try it. - syncResponseChan := make(chan gjson.Result) - go func() { - response, _ := alice.MustSync(t, client.SyncReq{}) - syncResponseChan <- response - close(syncResponseChan) - }() + // Regular sync still shouldn't include the room + response, nextBatch = alice.MustSync(t, client.SyncReq{Since: nextBatch}) - // we expect another state_ids request to arrive. - // we'd do another AwaitStateIdsRequest, except it's single-use. - - // the client-side requests should still be waiting - select { - case <-syncResponseChan: - t.Fatalf("Sync completed before state resync complete") - default: + if response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Regular sync shouldn't include the joined room until resync is over") } // release the federation /state response psjResult.FinishStateRequest() // the /sync request should now complete, with the new room - var syncRes gjson.Result - select { - case <-time.After(1 * time.Second): - t.Fatalf("/sync request request did not complete") - case syncRes = <-syncResponseChan: - } + response, _ = alice.MustSync(t, client.SyncReq{Since: nextBatch}) - roomRes := syncRes.Get("rooms.join." + client.GjsonEscape(serverRoom.RoomID)) - if !roomRes.Exists() { - t.Fatalf("/sync completed without join to new room\n") + if !response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Regular sync should now include the joined room since resync is over") } }) @@ -1029,34 +1008,21 @@ func TestPartialStateJoin(t *testing.T) { // wait until hs2 starts syncing state fedStateIdsRequestReceivedWaiter.Waitf(t, 5*time.Second, "Waiting for /state_ids request") - syncResponseChan := make(chan gjson.Result) - go func() { - response, _ := charlie.MustSync(t, client.SyncReq{}) - syncResponseChan <- response - close(syncResponseChan) - }() + response, nextBatch := charlie.MustSync(t, client.SyncReq{}) - // the client-side requests should still be waiting - select { - case <-syncResponseChan: - t.Fatalf("hs2 sync completed before state resync complete") - default: + // the client-side requests shouldn't report the join yet + syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(roomID) + if response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Regular sync shouldn't include the joined room yet") } // reply to hs2 with a bogus /state_ids response fedStateIdsSendResponseWaiter.Finish() - // charlie's /sync request should now complete, with the new room - var syncRes gjson.Result - select { - case <-time.After(1 * time.Second): - t.Fatalf("hs2 /sync request request did not complete") - case syncRes = <-syncResponseChan: - } + response, _ = charlie.MustSync(t, client.SyncReq{Since: nextBatch}) - roomRes := syncRes.Get("rooms.join." + client.GjsonEscape(roomID)) - if !roomRes.Exists() { - t.Fatalf("hs2 /sync completed without join to new room\n") + if !response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("hs2 /sync completed without join to new room") } }) @@ -1658,7 +1624,7 @@ func TestPartialStateJoin(t *testing.T) { // Alice has now joined the room, and the server is syncing the state in the background. - // attempts to sync should now block. Fire off a goroutine to try it. + // attempts to joined_members should now block. Fire off a goroutine to try it. jmResponseChan := make(chan *http.Response) go func() { response := alice.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", serverRoom.RoomID, "joined_members"}) From 0b9cce3a2607f6e088457f5f7a128469d4ab5db1 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Tue, 17 Jan 2023 16:07:00 +0100 Subject: [PATCH 2/3] Remove or rename mentions of regular sync --- ...federation_room_join_partial_state_test.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/federation_room_join_partial_state_test.go b/tests/federation_room_join_partial_state_test.go index a8898de9..6fe7ab48 100644 --- a/tests/federation_room_join_partial_state_test.go +++ b/tests/federation_room_join_partial_state_test.go @@ -118,9 +118,9 @@ func TestPartialStateJoin(t *testing.T) { deployment := Deploy(t, b.BlueprintAlice) defer deployment.Destroy(t) - // test that a regular /sync request made during a partial-state /send_join + // test that a non lazy /sync request made during a partial-state /send_join // request does not return the room until the state is correctly synced. - t.Run("RegularSyncDuringPartialStateJoin", func(t *testing.T) { + t.Run("NonLazySyncDuringPartialStateJoin", func(t *testing.T) { alice := deployment.RegisterUser(t, "hs1", "t1alice", "secret", false) server := createTestServer(t, deployment) @@ -132,12 +132,12 @@ func TestPartialStateJoin(t *testing.T) { // Alice has now joined the room, and the server is syncing the state in the background. - // Regular sync shouldn't include the room yet + // sync shouldn't include the room yet response, nextBatch := alice.MustSync(t, client.SyncReq{}) syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(serverRoom.RoomID) if response.Get(syncJoinedRoomPath).Exists() { - t.Fatal("Regular sync shouldn't include the joined room until resync is over") + t.Fatal("Sync shouldn't include the joined room until resync is over") } // wait for the state_ids request to arrive @@ -151,7 +151,7 @@ func TestPartialStateJoin(t *testing.T) { roomRes := response.Get(syncJoinedRoomPath) if !roomRes.Exists() { - t.Fatal("Regular sync should now include the joined room since resync is over") + t.Fatal("Sync should now include the joined room since resync is over") } // check that the state includes both charlie and derek. @@ -905,7 +905,7 @@ func TestPartialStateJoin(t *testing.T) { }) // test that a partial-state join continues syncing state after a restart - // the same as SyncBlocksDuringPartialStateJoin, with a restart in the middle + // the same as NonLazySyncDuringPartialStateJoin, with a restart in the middle t.Run("PartialStateJoinContinuesAfterRestart", func(t *testing.T) { alice := deployment.RegisterUser(t, "hs1", "t12alice", "secret", false) @@ -921,12 +921,12 @@ func TestPartialStateJoin(t *testing.T) { // wait for the state_ids request to arrive psjResult.AwaitStateIdsRequest(t) - // Regular sync shouldn't include the room yet + // Non lazy sync shouldn't include the room yet response, nextBatch := alice.MustSync(t, client.SyncReq{}) syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(serverRoom.RoomID) if response.Get(syncJoinedRoomPath).Exists() { - t.Fatal("Regular sync shouldn't include the joined room until resync is over") + t.Fatal("Sync shouldn't include the joined room until resync is over") } // restart the homeserver @@ -935,11 +935,11 @@ func TestPartialStateJoin(t *testing.T) { t.Errorf("Failed to restart homeserver: %s", err) } - // Regular sync still shouldn't include the room + // Sync still shouldn't include the room response, nextBatch = alice.MustSync(t, client.SyncReq{Since: nextBatch}) if response.Get(syncJoinedRoomPath).Exists() { - t.Fatal("Regular sync shouldn't include the joined room until resync is over") + t.Fatal("Sync shouldn't include the joined room until resync is over") } // release the federation /state response @@ -949,7 +949,7 @@ func TestPartialStateJoin(t *testing.T) { response, _ = alice.MustSync(t, client.SyncReq{Since: nextBatch}) if !response.Get(syncJoinedRoomPath).Exists() { - t.Fatal("Regular sync should now include the joined room since resync is over") + t.Fatal("Sync should now include the joined room since resync is over") } }) @@ -1013,7 +1013,7 @@ func TestPartialStateJoin(t *testing.T) { // the client-side requests shouldn't report the join yet syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(roomID) if response.Get(syncJoinedRoomPath).Exists() { - t.Fatal("Regular sync shouldn't include the joined room yet") + t.Fatal("Sync shouldn't include the joined room yet") } // reply to hs2 with a bogus /state_ids response @@ -1049,7 +1049,7 @@ func TestPartialStateJoin(t *testing.T) { t.Logf("Alice successfully synced") // wait for partial state to finish syncing, - // by waiting for the room to show up in a regular /sync. + // by waiting for the room to show up in /sync. psjResult.AwaitStateIdsRequest(t) psjResult.FinishStateRequest() alice.MustSyncUntil(t, @@ -1074,7 +1074,7 @@ func TestPartialStateJoin(t *testing.T) { server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{event.JSON()}, nil) } - // wait for the events to come down a regular /sync. + // wait for the events to come down a /sync. alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(serverRoom.RoomID, lastEventID), From a8dfc9df6fa56d9714a5f7ddf49ec67bc5c194f4 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Wed, 18 Jan 2023 17:52:50 +0100 Subject: [PATCH 3/3] Add long polling test --- ...federation_room_join_partial_state_test.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/federation_room_join_partial_state_test.go b/tests/federation_room_join_partial_state_test.go index 6fe7ab48..c2086cc6 100644 --- a/tests/federation_room_join_partial_state_test.go +++ b/tests/federation_room_join_partial_state_test.go @@ -169,6 +169,70 @@ func TestPartialStateJoin(t *testing.T) { } }) + t.Run("NonLazyLongPollingSyncDuringPartialStateJoin", func(t *testing.T) { + alice := deployment.RegisterUser(t, "hs1", "t99alice", "secret", false) + + server := createTestServer(t, deployment) + cancel := server.Listen() + defer cancel() + serverRoom := createTestRoom(t, server, alice.GetDefaultRoomVersion(t)) + psjResult := beginPartialStateJoin(t, server, serverRoom, alice) + defer psjResult.Destroy(t) + + // Alice has now joined the room, and the server is syncing the state in the background. + + // initial sync shouldn't include the room yet, but still return immediatly + response, nextBatch := alice.MustSync(t, client.SyncReq{ + TimeoutMillis: "10000", + }) + + syncJoinedRoomPath := "rooms.join." + client.GjsonEscape(serverRoom.RoomID) + if response.Get(syncJoinedRoomPath).Exists() { + t.Fatal("Sync shouldn't include the joined room until resync is over") + } + + // Begin a long polling sync that shouldn't return yet since no change happened + responseChan := make(chan gjson.Result, 1) + syncStarted := make(chan struct{}) + go func() { + defer close(responseChan) + defer close(syncStarted) + + syncStarted <- struct{}{} + response, _ := alice.MustSync(t, client.SyncReq{ + TimeoutMillis: "10000", + Since: nextBatch, + }) + responseChan <- response + }() + + // Try to wait for the sync to actually start, then un-partial-state the room + select { + case <-syncStarted: + // wait for the state_ids request to arrive + psjResult.AwaitStateIdsRequest(t) + // release the federation /state response + psjResult.FinishStateRequest() + case <-time.After(time.Second * 5): + // even though this should mostly be impossible, make sure we have a timeout + t.Fatalf("goroutine didn't start") + } + + // Try to wait for the sync to return or timeout after 15 seconds, + // as the above tests are using a timeout of 10 seconds + select { + case response = <-responseChan: + case <-time.After(time.Second * 5): + t.Errorf("sync should have returned before the timeout") + } + + // the /sync request should now complete, with the new room + roomRes := response.Get(syncJoinedRoomPath) + if !roomRes.Exists() { + t.Fatal("Sync should now include the joined room since resync is over") + } + }) + // when Alice does a lazy-loading sync, she should see the room immediately t.Run("CanLazyLoadingSyncDuringPartialStateJoin", func(t *testing.T) { alice := deployment.RegisterUser(t, "hs1", "t2alice", "secret", false)