-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: prevent goroutine leak and CPU spinning at websocket transport (#…
…2209) * Added goroutine leak test for chat example * Improved chat example with proper concurrency * Revert "fix: prevents goroutine leak at websocket transport (#2168)" This reverts commit eef7bfa. * Improved subscription channel usage * Regenerated examples and codegen * Add support for subscription keepalives in websocket client * Update chat example test * if else chain to switch Signed-off-by: Steve Coffman <[email protected]> * Revert "Add support for subscription keepalives in websocket client" This reverts commits 64b882c and 670cf22. * Fixed chat example race condition * Fixed chatroom#Messages type Co-authored-by: Steve Coffman <[email protected]>
- Loading branch information
1 parent
5f5bfcb
commit 6855b72
Showing
7 changed files
with
336 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,75 @@ | ||
package chat | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"fmt" | ||
"github.com/99designs/gqlgen/client" | ||
"github.com/99designs/gqlgen/graphql/handler" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"runtime" | ||
"sync" | ||
"testing" | ||
) | ||
|
||
func TestChatSubscriptions(t *testing.T) { | ||
c := client.New(handler.NewDefaultServer(NewExecutableSchema(New()))) | ||
|
||
sub := c.Websocket(`subscription @user(username:"vektah") { messageAdded(roomName:"#gophers") { text createdBy } }`) | ||
defer sub.Close() | ||
|
||
go func() { | ||
var resp interface{} | ||
time.Sleep(10 * time.Millisecond) | ||
err := c.Post(`mutation { | ||
a:post(text:"Hello!", roomName:"#gophers", username:"vektah") { id } | ||
b:post(text:"Hello Vektah!", roomName:"#gophers", username:"andrey") { id } | ||
c:post(text:"Whats up?", roomName:"#gophers", username:"vektah") { id } | ||
}`, &resp) | ||
assert.NoError(t, err) | ||
}() | ||
|
||
var msg struct { | ||
resp struct { | ||
MessageAdded struct { | ||
Text string | ||
CreatedBy string | ||
const batchSize = 128 | ||
var wg sync.WaitGroup | ||
for i := 0; i < batchSize*8; i++ { | ||
wg.Add(1) | ||
go func(i int) { | ||
defer wg.Done() | ||
sub := c.Websocket(fmt.Sprintf( | ||
`subscription @user(username:"vektah") { messageAdded(roomName:"#gophers%d") { text createdBy } }`, | ||
i, | ||
)) | ||
defer sub.Close() | ||
|
||
var msg struct { | ||
resp struct { | ||
MessageAdded struct { | ||
Text string | ||
CreatedBy string | ||
} | ||
} | ||
err error | ||
} | ||
|
||
msg.err = sub.Next(&msg.resp) | ||
require.NoError(t, msg.err, "sub.Next") | ||
require.Equal(t, "You've joined the room", msg.resp.MessageAdded.Text) | ||
require.Equal(t, "system", msg.resp.MessageAdded.CreatedBy) | ||
|
||
go func() { | ||
var resp interface{} | ||
err := c.Post(fmt.Sprintf(`mutation { | ||
a:post(text:"Hello!", roomName:"#gophers%d", username:"vektah") { id } | ||
b:post(text:"Hello Vektah!", roomName:"#gophers%d", username:"andrey") { id } | ||
c:post(text:"Whats up?", roomName:"#gophers%d", username:"vektah") { id } | ||
}`, i, i, i), &resp) | ||
assert.NoError(t, err) | ||
}() | ||
|
||
msg.err = sub.Next(&msg.resp) | ||
require.NoError(t, msg.err, "sub.Next") | ||
require.Equal(t, "Hello!", msg.resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", msg.resp.MessageAdded.CreatedBy) | ||
|
||
msg.err = sub.Next(&msg.resp) | ||
require.NoError(t, msg.err, "sub.Next") | ||
require.Equal(t, "Whats up?", msg.resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", msg.resp.MessageAdded.CreatedBy) | ||
}(i) | ||
// wait for goroutines to finish every N tests to not starve on CPU | ||
if (i+1)%batchSize == 0 { | ||
wg.Wait() | ||
} | ||
err error | ||
} | ||
wg.Wait() | ||
|
||
msg.err = sub.Next(&msg.resp) | ||
require.NoError(t, msg.err, "sub.Next") | ||
require.Equal(t, "Hello!", msg.resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", msg.resp.MessageAdded.CreatedBy) | ||
|
||
msg.err = sub.Next(&msg.resp) | ||
require.NoError(t, msg.err, "sub.Next") | ||
require.Equal(t, "Whats up?", msg.resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", msg.resp.MessageAdded.CreatedBy) | ||
// 1 for the main thread, 1 for the testing package and remainder is reserved for the HTTP server threads | ||
// TODO: use something like runtime.Stack to filter out HTTP server threads, | ||
// TODO: which is required for proper concurrency and leaks testing | ||
require.Less(t, runtime.NumGoroutine(), 1+1+batchSize*2, "goroutine leak") | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.