-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix V2Typing send multiple times #214
Changes from 8 commits
2840d0d
ca263b2
e566158
8dc8d48
3a2001f
7c83d7e
86c4f18
d55880f
1b1d00d
a283715
d2cf57d
d357aa2
5846873
2ced198
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package handler2_test | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"os" | ||
"reflect" | ||
"sync" | ||
|
@@ -92,12 +94,17 @@ func (p *mockPub) WaitForPayloadType(t string) chan struct{} { | |
return ch | ||
} | ||
|
||
func (p *mockPub) DoWait(t *testing.T, errMsg string, ch chan struct{}) { | ||
func (p *mockPub) DoWait(t *testing.T, errMsg string, ch chan struct{}, wantTimeOut bool) { | ||
select { | ||
case <-ch: | ||
if wantTimeOut { | ||
t.Fatalf("expected to timeout, but received on channel") | ||
} | ||
return | ||
case <-time.After(time.Second): | ||
t.Fatalf("DoWait: timed out waiting: %s", errMsg) | ||
if !wantTimeOut { | ||
t.Fatalf("DoWait: timed out waiting: %s", errMsg) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -155,7 +162,7 @@ func TestHandlerFreshEnsurePolling(t *testing.T) { | |
DeviceID: deviceID, | ||
AccessTokenHash: tok.AccessTokenHash, | ||
}) | ||
pub.DoWait(t, "didn't see V2InitialSyncComplete", ch) | ||
pub.DoWait(t, "didn't see V2InitialSyncComplete", ch, false) | ||
|
||
// make sure we polled with the token i.e it did a db hit | ||
pMap.assertCallExists(t, pollInfo{ | ||
|
@@ -169,3 +176,44 @@ func TestHandlerFreshEnsurePolling(t *testing.T) { | |
}) | ||
|
||
} | ||
|
||
func TestSetTypingConcurrently(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't what I'm interested in testing. The case here should be passing without your change. The case I'm interested in is when you have 2 pollers receiving delayed typing notifs. For example. if alice starts typing then stops typing (so [A] then []) the problem is that 1 poller may be "behind" the other, such that it has yet to see [A] whilst the other "ahead" poller has already seen [A] and []. In this scenario, we flicker with 4 operations instead of 2, as we go [A], [], [A], [], which this test is not testing, nor does the code fix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It doesn't always pass. With luck it does, yea, if the machine is slow enough to execute both calls.
which also means that |
||
store := state.NewStorage(postgresURI) | ||
v2Store := sync2.NewStore(postgresURI, "secret") | ||
pMap := &mockPollerMap{} | ||
pub := newMockPub() | ||
sub := &mockSub{} | ||
h, err := handler2.NewHandler(pMap, v2Store, store, pub, sub, false, time.Minute) | ||
assertNoError(t, err) | ||
ctx := context.Background() | ||
|
||
roomID := "!typing:localhost" | ||
|
||
typingType := pubsub.V2Typing{} | ||
|
||
// startSignal is used to synchronize calling SetTyping | ||
startSignal := make(chan struct{}) | ||
// Call SetTyping twice, this may happen with pollers for the same user | ||
go func() { | ||
<-startSignal | ||
h.SetTyping(ctx, sync2.PollerID{UserID: "@alice", DeviceID: "aliceDevice"}, roomID, json.RawMessage(`{"content":{"user_ids":["@alice:localhost"]}}`)) | ||
}() | ||
go func() { | ||
<-startSignal | ||
h.SetTyping(ctx, sync2.PollerID{UserID: "@bob", DeviceID: "bobDevice"}, roomID, json.RawMessage(`{"content":{"user_ids":["@alice:localhost"]}}`)) | ||
}() | ||
|
||
close(startSignal) | ||
|
||
// Wait for the event to be published | ||
ch := pub.WaitForPayloadType(typingType.Type()) | ||
pub.DoWait(t, "didn't see V2Typing", ch, false) | ||
ch = pub.WaitForPayloadType(typingType.Type()) | ||
// Wait again, but this time we expect to timeout. | ||
pub.DoWait(t, "saw unexpected V2Typing", ch, true) | ||
|
||
// We expect only one call to Notify, as the hashes should match | ||
if gotCalls := len(pub.calls); gotCalls != 1 { | ||
t.Fatalf("expected only one call to notify, got %d", gotCalls) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -604,3 +604,94 @@ func TestExtensionLateEnable(t *testing.T) { | |
}, | ||
}) | ||
} | ||
|
||
func TestTypingMultiplePoller(t *testing.T) { | ||
pqString := testutils.PrepareDBConnectionString() | ||
// setup code | ||
v2 := runTestV2Server(t) | ||
v3 := runTestServer(t, v2, pqString) | ||
defer v2.close() | ||
defer v3.close() | ||
|
||
roomA := "!a:localhost" | ||
|
||
v2.addAccountWithDeviceID(alice, "first", aliceToken) | ||
v2.addAccountWithDeviceID(bob, "second", bobToken) | ||
|
||
// start the pollers | ||
aliceRes := v3.mustDoV3Request(t, aliceToken, sync3.Request{}) | ||
bobRes := v3.mustDoV3Request(t, bobToken, sync3.Request{}) | ||
|
||
// Create the room state and join with Bob | ||
roomState := createRoomState(t, alice, time.Now()) | ||
joinEv := testutils.NewStateEvent(t, "m.room.member", bob, alice, map[string]interface{}{ | ||
"membership": "join", | ||
}) | ||
roomState = append(roomState, joinEv) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not required, you're adding this in the timeline. |
||
|
||
// Queue the response with Alice typing | ||
v2.queueResponse(aliceToken, sync2.SyncResponse{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do this before you call |
||
Rooms: sync2.SyncRoomsResponse{ | ||
Join: map[string]sync2.SyncV2JoinResponse{ | ||
roomA: { | ||
State: sync2.EventsResponse{ | ||
Events: roomState, | ||
}, | ||
Ephemeral: sync2.EventsResponse{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need something in |
||
Events: []json.RawMessage{json.RawMessage(`{"type":"m.typing","content":{"user_ids":["@alice:localhost"]}}`)}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
// Wait for the server to have processed the syncv2 response. | ||
// This ensures the poller of Alice will be assigned the typing notification handler. | ||
v2.waitUntilEmpty(t, aliceToken) | ||
|
||
// Queue another response for bob, with bob typing. | ||
// Since Bobs poller isn't allowed to update typing notifications, we should only see | ||
// Alice typing below. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment makes no sense as that hasn't been decided yet. Move it down to when you start doing v3 reqs. |
||
v2.queueResponse(bobToken, sync2.SyncResponse{ | ||
Rooms: sync2.SyncRoomsResponse{ | ||
Join: map[string]sync2.SyncV2JoinResponse{ | ||
roomA: { | ||
State: sync2.EventsResponse{ | ||
Events: roomState, | ||
}, | ||
Ephemeral: sync2.EventsResponse{ | ||
Events: []json.RawMessage{json.RawMessage(`{"type":"m.typing","content":{"user_ids":["@bob:localhost"]}}`)}}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
// Wait for the server to have processed that event | ||
v2.waitUntilEmpty(t, bobToken) | ||
|
||
// Get the response from v3 | ||
for _, token := range []string{aliceToken, bobToken} { | ||
pos := aliceRes.Pos | ||
if token == bobToken { | ||
pos = bobRes.Pos | ||
} | ||
|
||
res := v3.mustDoV3RequestWithPos(t, token, pos, sync3.Request{ | ||
Extensions: extensions.Request{ | ||
Typing: &extensions.TypingRequest{ | ||
Core: extensions.Core{Enabled: &boolTrue}, | ||
}, | ||
}, | ||
Lists: map[string]sync3.RequestList{"a": { | ||
Ranges: sync3.SliceRanges{ | ||
[2]int64{0, 1}, | ||
}, | ||
Sort: []string{sync3.SortByRecency}, | ||
RoomSubscription: sync3.RoomSubscription{ | ||
TimelineLimit: 0, | ||
}, | ||
}}, | ||
}) | ||
// We expect only Alice typing, as only Alice Poller is "allowed" | ||
// to update typing notifications. | ||
m.MatchResponse(t, res, m.MatchTyping(roomA, []string{alice})) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And then test that if you update the typing event to be bob then it comes through please. |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though I couldn't get the test fail in CI, this was causing a race condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the bits which touch
typingMap
ortypingDeviceHandler
should be protected.