Skip to content

Commit 2d1bb21

Browse files
authored
grpc: ensure transports are closed when the channel enters IDLE (#6620)
1 parent 552525e commit 2d1bb21

File tree

2 files changed

+42
-14
lines changed

2 files changed

+42
-14
lines changed

clientconn.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,8 +1091,8 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) {
10911091
ac.cancel()
10921092
ac.ctx, ac.cancel = context.WithCancel(ac.cc.ctx)
10931093

1094-
// We have to defer here because GracefulClose => Close => onClose, which
1095-
// requires locking ac.mu.
1094+
// We have to defer here because GracefulClose => onClose, which requires
1095+
// locking ac.mu.
10961096
if ac.transport != nil {
10971097
defer ac.transport.GracefulClose()
10981098
ac.transport = nil
@@ -1680,16 +1680,7 @@ func (ac *addrConn) tearDown(err error) {
16801680
ac.updateConnectivityState(connectivity.Shutdown, nil)
16811681
ac.cancel()
16821682
ac.curAddr = resolver.Address{}
1683-
if err == errConnDrain && curTr != nil {
1684-
// GracefulClose(...) may be executed multiple times when
1685-
// i) receiving multiple GoAway frames from the server; or
1686-
// ii) there are concurrent name resolver/Balancer triggered
1687-
// address removal and GoAway.
1688-
// We have to unlock and re-lock here because GracefulClose => Close => onClose, which requires locking ac.mu.
1689-
ac.mu.Unlock()
1690-
curTr.GracefulClose()
1691-
ac.mu.Lock()
1692-
}
1683+
16931684
channelz.AddTraceEvent(logger, ac.channelzID, 0, &channelz.TraceEventDesc{
16941685
Desc: "Subchannel deleted",
16951686
Severity: channelz.CtInfo,
@@ -1703,6 +1694,29 @@ func (ac *addrConn) tearDown(err error) {
17031694
// being deleted right away.
17041695
channelz.RemoveEntry(ac.channelzID)
17051696
ac.mu.Unlock()
1697+
1698+
// We have to release the lock before the call to GracefulClose/Close here
1699+
// because both of them call onClose(), which requires locking ac.mu.
1700+
if curTr != nil {
1701+
if err == errConnDrain {
1702+
// Close the transport gracefully when the subConn is being shutdown.
1703+
//
1704+
// GracefulClose() may be executed multiple times if:
1705+
// - multiple GoAway frames are received from the server
1706+
// - there are concurrent name resolver or balancer triggered
1707+
// address removal and GoAway
1708+
curTr.GracefulClose()
1709+
} else {
1710+
// Hard close the transport when the channel is entering idle or is
1711+
// being shutdown. In the case where the channel is being shutdown,
1712+
// closing of transports is also taken care of by cancelation of cc.ctx.
1713+
// But in the case where the channel is entering idle, we need to
1714+
// explicitly close the transports here. Instead of distinguishing
1715+
// between these two cases, it is simpler to close the transport
1716+
// unconditionally here.
1717+
curTr.Close(err)
1718+
}
1719+
}
17061720
}
17071721

17081722
func (ac *addrConn) getState() connectivity.State {

internal/idle/idle_e2e_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ func (s) TestChannelIdleness_Disabled_NoActivity(t *testing.T) {
142142
}
143143

144144
// Tests the case where channel idleness is enabled by passing a small value for
145-
// idle_timeout. Verifies that a READY channel with no RPCs moves to IDLE.
145+
// idle_timeout. Verifies that a READY channel with no RPCs moves to IDLE, and
146+
// the connection to the backend is closed.
146147
func (s) TestChannelIdleness_Enabled_NoActivity(t *testing.T) {
147148
// Create a ClientConn with a short idle_timeout.
148149
r := manual.NewBuilderWithScheme("whatever")
@@ -159,7 +160,8 @@ func (s) TestChannelIdleness_Enabled_NoActivity(t *testing.T) {
159160
t.Cleanup(func() { cc.Close() })
160161

161162
// Start a test backend and push an address update via the resolver.
162-
backend := stubserver.StartTestService(t, nil)
163+
lis := testutils.NewListenerWrapper(t, nil)
164+
backend := stubserver.StartTestService(t, &stubserver.StubServer{Listener: lis})
163165
t.Cleanup(backend.Stop)
164166
r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: backend.Address}}})
165167

@@ -168,13 +170,25 @@ func (s) TestChannelIdleness_Enabled_NoActivity(t *testing.T) {
168170
defer cancel()
169171
testutils.AwaitState(ctx, t, cc, connectivity.Ready)
170172

173+
// Retrieve the wrapped conn from the listener.
174+
v, err := lis.NewConnCh.Receive(ctx)
175+
if err != nil {
176+
t.Fatalf("Failed to retrieve conn from test listener: %v", err)
177+
}
178+
conn := v.(*testutils.ConnWrapper)
179+
171180
// Verify that the ClientConn moves to IDLE as there is no activity.
172181
testutils.AwaitState(ctx, t, cc, connectivity.Idle)
173182

174183
// Verify idleness related channelz events.
175184
if err := channelzTraceEventFound(ctx, "entering idle mode"); err != nil {
176185
t.Fatal(err)
177186
}
187+
188+
// Verify that the previously open connection is closed.
189+
if _, err := conn.CloseCh.Receive(ctx); err != nil {
190+
t.Fatalf("Failed when waiting for connection to be closed after channel entered IDLE: %v", err)
191+
}
178192
}
179193

180194
// Tests the case where channel idleness is enabled by passing a small value for

0 commit comments

Comments
 (0)