Skip to content

Commit 81b9df2

Browse files
authored
idle: move idleness manager to separate package and ~13s of tests into it (#6566)
1 parent 7d35b8e commit 81b9df2

28 files changed

+416
-500
lines changed

balancer/rls/balancer_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,11 +1030,7 @@ func (s) TestUpdateStatePauses(t *testing.T) {
10301030
// the test would fail. Waiting for the channel to become READY here
10311031
// ensures that the test does not flake because of this rare sequence of
10321032
// events.
1033-
for s := cc.GetState(); s != connectivity.Ready; s = cc.GetState() {
1034-
if !cc.WaitForStateChange(ctx, s) {
1035-
t.Fatal("Timeout when waiting for connectivity state to reach READY")
1036-
}
1037-
}
1033+
testutils.AwaitState(ctx, t, cc, connectivity.Ready)
10381034

10391035
// Cache the state changes seen up to this point.
10401036
states0 := ccWrapper.getStates()

call.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727
//
2828
// All errors returned by Invoke are compatible with the status package.
2929
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply any, opts ...CallOption) error {
30-
if err := cc.idlenessMgr.onCallBegin(); err != nil {
30+
if err := cc.idlenessMgr.OnCallBegin(); err != nil {
3131
return err
3232
}
33-
defer cc.idlenessMgr.onCallEnd()
33+
defer cc.idlenessMgr.OnCallEnd()
3434

3535
// allow interceptor to see all applicable call options, which means those
3636
// configured as defaults from dial option as well as per-call options

channelz/service/service_sktopt_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,6 @@ func protoToSocketOption(skopts []*channelzpb.SocketOption) *channelz.SocketOpti
128128
}
129129

130130
func (s) TestGetSocketOptions(t *testing.T) {
131-
czCleanup := channelz.NewChannelzStorageForTesting()
132-
defer cleanupWrapper(czCleanup, t)
133131
ss := []*dummySocket{
134132
{
135133
socketOptions: &channelz.SocketOptionData{

channelz/service/service_test.go

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ func Test(t *testing.T) {
5151
grpctest.RunSubTests(t, s{})
5252
}
5353

54-
func cleanupWrapper(cleanup func() error, t *testing.T) {
55-
if err := cleanup(); err != nil {
56-
t.Error(err)
57-
}
58-
}
59-
6054
type protoToSocketOptFunc func([]*channelzpb.SocketOption) *channelz.SocketOptionData
6155

6256
// protoToSocketOpt is used in function socketProtoToStruct to extract socket option
@@ -311,8 +305,7 @@ func (s) TestGetTopChannels(t *testing.T) {
311305
},
312306
{},
313307
}
314-
czCleanup := channelz.NewChannelzStorageForTesting()
315-
defer cleanupWrapper(czCleanup, t)
308+
316309
for _, c := range tcs {
317310
id := channelz.RegisterChannel(c, nil, "")
318311
defer channelz.RemoveEntry(id)
@@ -364,8 +357,7 @@ func (s) TestGetServers(t *testing.T) {
364357
lastCallStartedTimestamp: time.Now().UTC(),
365358
},
366359
}
367-
czCleanup := channelz.NewChannelzStorageForTesting()
368-
defer cleanupWrapper(czCleanup, t)
360+
369361
for _, s := range ss {
370362
id := channelz.RegisterServer(s, "")
371363
defer channelz.RemoveEntry(id)
@@ -397,8 +389,6 @@ func (s) TestGetServers(t *testing.T) {
397389
}
398390

399391
func (s) TestGetServerSockets(t *testing.T) {
400-
czCleanup := channelz.NewChannelzStorageForTesting()
401-
defer cleanupWrapper(czCleanup, t)
402392
svrID := channelz.RegisterServer(&dummyServer{}, "")
403393
defer channelz.RemoveEntry(svrID)
404394
refNames := []string{"listen socket 1", "normal socket 1", "normal socket 2"}
@@ -438,8 +428,6 @@ func (s) TestGetServerSockets(t *testing.T) {
438428
// This test makes a GetServerSockets with a non-zero start ID, and expect only
439429
// sockets with ID >= the given start ID.
440430
func (s) TestGetServerSocketsNonZeroStartID(t *testing.T) {
441-
czCleanup := channelz.NewChannelzStorageForTesting()
442-
defer cleanupWrapper(czCleanup, t)
443431
svrID := channelz.RegisterServer(&dummyServer{}, "")
444432
defer channelz.RemoveEntry(svrID)
445433
refNames := []string{"listen socket 1", "normal socket 1", "normal socket 2"}
@@ -470,9 +458,6 @@ func (s) TestGetServerSocketsNonZeroStartID(t *testing.T) {
470458
}
471459

472460
func (s) TestGetChannel(t *testing.T) {
473-
czCleanup := channelz.NewChannelzStorageForTesting()
474-
defer cleanupWrapper(czCleanup, t)
475-
476461
refNames := []string{"top channel 1", "nested channel 1", "sub channel 2", "nested channel 3"}
477462
ids := make([]*channelz.Identifier, 4)
478463
ids[0] = channelz.RegisterChannel(&dummyChannel{}, nil, refNames[0])
@@ -584,8 +569,7 @@ func (s) TestGetSubChannel(t *testing.T) {
584569
subchanConnectivityChange = fmt.Sprintf("Subchannel Connectivity change to %v", connectivity.Ready)
585570
subChanPickNewAddress = fmt.Sprintf("Subchannel picks a new address %q to connect", "0.0.0.0")
586571
)
587-
czCleanup := channelz.NewChannelzStorageForTesting()
588-
defer cleanupWrapper(czCleanup, t)
572+
589573
refNames := []string{"top channel 1", "sub channel 1", "socket 1", "socket 2"}
590574
ids := make([]*channelz.Identifier, 4)
591575
ids[0] = channelz.RegisterChannel(&dummyChannel{}, nil, refNames[0])
@@ -662,8 +646,6 @@ func (s) TestGetSubChannel(t *testing.T) {
662646
}
663647

664648
func (s) TestGetSocket(t *testing.T) {
665-
czCleanup := channelz.NewChannelzStorageForTesting()
666-
defer cleanupWrapper(czCleanup, t)
667649
ss := []*dummySocket{
668650
{
669651
streamsStarted: 10,

clientconn.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"google.golang.org/grpc/internal/backoff"
3939
"google.golang.org/grpc/internal/channelz"
4040
"google.golang.org/grpc/internal/grpcsync"
41+
"google.golang.org/grpc/internal/idle"
4142
"google.golang.org/grpc/internal/pretty"
4243
iresolver "google.golang.org/grpc/internal/resolver"
4344
"google.golang.org/grpc/internal/transport"
@@ -266,7 +267,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
266267
// Configure idleness support with configured idle timeout or default idle
267268
// timeout duration. Idleness can be explicitly disabled by the user, by
268269
// setting the dial option to 0.
269-
cc.idlenessMgr = newIdlenessManager(cc, cc.dopts.idleTimeout)
270+
cc.idlenessMgr = idle.NewManager(idle.ManagerOptions{Enforcer: (*idler)(cc), Timeout: cc.dopts.idleTimeout, Logger: logger})
270271

271272
// Return early for non-blocking dials.
272273
if !cc.dopts.block {
@@ -317,6 +318,16 @@ func (cc *ClientConn) addTraceEvent(msg string) {
317318
channelz.AddTraceEvent(logger, cc.channelzID, 0, ted)
318319
}
319320

321+
type idler ClientConn
322+
323+
func (i *idler) EnterIdleMode() error {
324+
return (*ClientConn)(i).enterIdleMode()
325+
}
326+
327+
func (i *idler) ExitIdleMode() error {
328+
return (*ClientConn)(i).exitIdleMode()
329+
}
330+
320331
// exitIdleMode moves the channel out of idle mode by recreating the name
321332
// resolver and load balancer.
322333
func (cc *ClientConn) exitIdleMode() error {
@@ -639,7 +650,7 @@ type ClientConn struct {
639650
channelzID *channelz.Identifier // Channelz identifier for the channel.
640651
resolverBuilder resolver.Builder // See parseTargetAndFindResolver().
641652
balancerWrapper *ccBalancerWrapper // Uses gracefulswitch.balancer underneath.
642-
idlenessMgr idlenessManager
653+
idlenessMgr idle.Manager
643654

644655
// The following provide their own synchronization, and therefore don't
645656
// require cc.mu to be held to access them.
@@ -1268,7 +1279,7 @@ func (cc *ClientConn) Close() error {
12681279
rWrapper.close()
12691280
}
12701281
if idlenessMgr != nil {
1271-
idlenessMgr.close()
1282+
idlenessMgr.Close()
12721283
}
12731284

12741285
for ac := range conns {

internal/channelz/funcs.go

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@
2424
package channelz
2525

2626
import (
27-
"context"
2827
"errors"
29-
"fmt"
3028
"sort"
3129
"sync"
3230
"sync/atomic"
@@ -40,8 +38,11 @@ const (
4038
)
4139

4240
var (
43-
db dbWrapper
44-
idGen idGenerator
41+
// IDGen is the global channelz entity ID generator. It should not be used
42+
// outside this package except by tests.
43+
IDGen IDGenerator
44+
45+
db dbWrapper
4546
// EntryPerPage defines the number of channelz entries to be shown on a web page.
4647
EntryPerPage = int64(50)
4748
curState int32
@@ -52,14 +53,14 @@ var (
5253
func TurnOn() {
5354
if !IsOn() {
5455
db.set(newChannelMap())
55-
idGen.reset()
56+
IDGen.Reset()
5657
atomic.StoreInt32(&curState, 1)
5758
}
5859
}
5960

6061
// IsOn returns whether channelz data collection is on.
6162
func IsOn() bool {
62-
return atomic.CompareAndSwapInt32(&curState, 1, 1)
63+
return atomic.LoadInt32(&curState) == 1
6364
}
6465

6566
// SetMaxTraceEntry sets maximum number of trace entry per entity (i.e. channel/subchannel).
@@ -97,43 +98,6 @@ func (d *dbWrapper) get() *channelMap {
9798
return d.DB
9899
}
99100

100-
// NewChannelzStorageForTesting initializes channelz data storage and id
101-
// generator for testing purposes.
102-
//
103-
// Returns a cleanup function to be invoked by the test, which waits for up to
104-
// 10s for all channelz state to be reset by the grpc goroutines when those
105-
// entities get closed. This cleanup function helps with ensuring that tests
106-
// don't mess up each other.
107-
func NewChannelzStorageForTesting() (cleanup func() error) {
108-
db.set(newChannelMap())
109-
idGen.reset()
110-
111-
return func() error {
112-
cm := db.get()
113-
if cm == nil {
114-
return nil
115-
}
116-
117-
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
118-
defer cancel()
119-
ticker := time.NewTicker(10 * time.Millisecond)
120-
defer ticker.Stop()
121-
for {
122-
cm.mu.RLock()
123-
topLevelChannels, servers, channels, subChannels, listenSockets, normalSockets := len(cm.topLevelChannels), len(cm.servers), len(cm.channels), len(cm.subChannels), len(cm.listenSockets), len(cm.normalSockets)
124-
cm.mu.RUnlock()
125-
126-
if err := ctx.Err(); err != nil {
127-
return fmt.Errorf("after 10s the channelz map has not been cleaned up yet, topchannels: %d, servers: %d, channels: %d, subchannels: %d, listen sockets: %d, normal sockets: %d", topLevelChannels, servers, channels, subChannels, listenSockets, normalSockets)
128-
}
129-
if topLevelChannels == 0 && servers == 0 && channels == 0 && subChannels == 0 && listenSockets == 0 && normalSockets == 0 {
130-
return nil
131-
}
132-
<-ticker.C
133-
}
134-
}
135-
}
136-
137101
// GetTopChannels returns a slice of top channel's ChannelMetric, along with a
138102
// boolean indicating whether there's more top channels to be queried for.
139103
//
@@ -193,7 +157,7 @@ func GetServer(id int64) *ServerMetric {
193157
//
194158
// If channelz is not turned ON, the channelz database is not mutated.
195159
func RegisterChannel(c Channel, pid *Identifier, ref string) *Identifier {
196-
id := idGen.genID()
160+
id := IDGen.genID()
197161
var parent int64
198162
isTopChannel := true
199163
if pid != nil {
@@ -229,7 +193,7 @@ func RegisterSubChannel(c Channel, pid *Identifier, ref string) (*Identifier, er
229193
if pid == nil {
230194
return nil, errors.New("a SubChannel's parent id cannot be nil")
231195
}
232-
id := idGen.genID()
196+
id := IDGen.genID()
233197
if !IsOn() {
234198
return newIdentifer(RefSubChannel, id, pid), nil
235199
}
@@ -251,7 +215,7 @@ func RegisterSubChannel(c Channel, pid *Identifier, ref string) (*Identifier, er
251215
//
252216
// If channelz is not turned ON, the channelz database is not mutated.
253217
func RegisterServer(s Server, ref string) *Identifier {
254-
id := idGen.genID()
218+
id := IDGen.genID()
255219
if !IsOn() {
256220
return newIdentifer(RefServer, id, nil)
257221
}
@@ -277,7 +241,7 @@ func RegisterListenSocket(s Socket, pid *Identifier, ref string) (*Identifier, e
277241
if pid == nil {
278242
return nil, errors.New("a ListenSocket's parent id cannot be 0")
279243
}
280-
id := idGen.genID()
244+
id := IDGen.genID()
281245
if !IsOn() {
282246
return newIdentifer(RefListenSocket, id, pid), nil
283247
}
@@ -297,7 +261,7 @@ func RegisterNormalSocket(s Socket, pid *Identifier, ref string) (*Identifier, e
297261
if pid == nil {
298262
return nil, errors.New("a NormalSocket's parent id cannot be 0")
299263
}
300-
id := idGen.genID()
264+
id := IDGen.genID()
301265
if !IsOn() {
302266
return newIdentifer(RefNormalSocket, id, pid), nil
303267
}
@@ -776,14 +740,17 @@ func (c *channelMap) GetServer(id int64) *ServerMetric {
776740
return sm
777741
}
778742

779-
type idGenerator struct {
743+
// IDGenerator is an incrementing atomic that tracks IDs for channelz entities.
744+
type IDGenerator struct {
780745
id int64
781746
}
782747

783-
func (i *idGenerator) reset() {
748+
// Reset resets the generated ID back to zero. Should only be used at
749+
// initialization or by tests sensitive to the ID number.
750+
func (i *IDGenerator) Reset() {
784751
atomic.StoreInt64(&i.id, 0)
785752
}
786753

787-
func (i *idGenerator) genID() int64 {
754+
func (i *IDGenerator) genID() int64 {
788755
return atomic.AddInt64(&i.id, 1)
789756
}

internal/channelz/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ type tracedChannel interface {
628628

629629
type channelTrace struct {
630630
cm *channelMap
631+
clearCalled bool
631632
createdTime time.Time
632633
eventCount int64
633634
mu sync.Mutex
@@ -656,6 +657,10 @@ func (c *channelTrace) append(e *TraceEvent) {
656657
}
657658

658659
func (c *channelTrace) clear() {
660+
if c.clearCalled {
661+
return
662+
}
663+
c.clearCalled = true
659664
c.mu.Lock()
660665
for _, e := range c.events {
661666
if e.RefID != 0 {

0 commit comments

Comments
 (0)