Skip to content

Commit c6cfa0b

Browse files
committed
fix(spanner): test for client leaks
Adds a test that ensures that any started goroutine is also stopped.
1 parent c01b4af commit c6cfa0b

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

spanner/client_synctest_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//go:build go1.25
2+
3+
/*
4+
Copyright 2025 Google LLC
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package spanner
20+
21+
import (
22+
"context"
23+
"testing"
24+
"testing/synctest"
25+
26+
"golang.org/x/sync/errgroup"
27+
28+
. "cloud.google.com/go/spanner/internal/testutil"
29+
)
30+
31+
func TestClient_GoroutineLeak(t *testing.T) {
32+
t.Parallel()
33+
34+
var tests = []struct {
35+
name string
36+
test func(ctx context.Context, client *Client) error
37+
}{
38+
{
39+
name: "Connect",
40+
test: func(ctx context.Context, client *Client) error {
41+
return nil
42+
},
43+
},
44+
{
45+
name: "Single.ReadRow",
46+
test: func(ctx context.Context, client *Client) error {
47+
_, err := client.Single().ReadRow(ctx, "Albums", Key{"foo"}, []string{"SingerId", "AlbumId", "AlbumTitle"})
48+
return err
49+
},
50+
},
51+
{
52+
name: "ReadOnlyTransaction",
53+
test: func(ctx context.Context, client *Client) error {
54+
roTxn := client.ReadOnlyTransaction()
55+
defer roTxn.Close()
56+
57+
iter := roTxn.Query(ctx, NewStatement(SelectSingerIDAlbumIDAlbumTitleFromAlbums))
58+
if err := iter.Do(func(row *Row) error {
59+
return nil
60+
}); err != nil {
61+
return err
62+
}
63+
64+
iter = roTxn.Read(ctx, "Albums", KeySets(Key{"foo"}), []string{"SingerId", "AlbumId", "AlbumTitle"})
65+
return iter.Do(func(row *Row) error {
66+
return nil
67+
})
68+
},
69+
},
70+
{
71+
name: "ReadWriteTransaction",
72+
test: func(ctx context.Context, client *Client) error {
73+
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *ReadWriteTransaction) error {
74+
iter := txn.Read(ctx, "Albums", KeySets(Key{"foo"}), []string{"SingerId", "AlbumId", "AlbumTitle"})
75+
return iter.Do(func(r *Row) error {
76+
return nil
77+
})
78+
})
79+
return err
80+
},
81+
},
82+
{
83+
name: "parallel Single.ReadRow",
84+
test: func(ctx context.Context, client *Client) error {
85+
g := new(errgroup.Group)
86+
for range 25 {
87+
g.Go(func() error {
88+
_, err := client.Single().ReadRow(ctx, "Albums", Key{"foo"}, []string{"SingerId", "AlbumId", "AlbumTitle"})
89+
return err
90+
})
91+
}
92+
return g.Wait()
93+
},
94+
},
95+
}
96+
97+
for _, test := range tests {
98+
t.Run(test.name, func(t *testing.T) {
99+
server, opts, teardown := NewMockedSpannerInMemTestServer(t)
100+
defer teardown()
101+
102+
synctest.Test(t, func(t *testing.T) {
103+
ctx := t.Context()
104+
105+
// wait for any started goroutine to stop
106+
defer synctest.Wait()
107+
108+
config := ClientConfig{}
109+
client, err := makeClientWithConfig(ctx, "projects/p/instances/i/databases/d", config, server.ServerAddress, opts...)
110+
if err != nil {
111+
t.Fatalf("failed to get a client: %v", err)
112+
}
113+
defer client.Close()
114+
115+
test.test(ctx, client)
116+
})
117+
})
118+
}
119+
}

spanner/internal/testutil/mocked_inmem_server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func NewMockedSpannerInMemTestServerWithAddr(t *testing.T, addr string, sopt ...
8282
return mockedServer, opts, func() {
8383
mockedServer.TestSpanner.Stop()
8484
mockedServer.TestInstanceAdmin.Stop()
85-
mockedServer.server.Stop()
85+
mockedServer.server.GracefulStop()
8686
}
8787
}
8888

0 commit comments

Comments
 (0)