forked from grpc/grpc-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransport_test.go
More file actions
304 lines (274 loc) · 10.1 KB
/
Copy pathtransport_test.go
File metadata and controls
304 lines (274 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
*
* Copyright 2023 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package test
import (
"context"
"encoding/binary"
"io"
"net"
"sync"
"testing"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/status"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
// connWrapperWithCloseCh wraps a net.Conn and fires an event when closed.
type connWrapperWithCloseCh struct {
net.Conn
close *grpcsync.Event
}
// Close closes the connection and sends a value on the close channel.
func (cw *connWrapperWithCloseCh) Close() error {
cw.close.Fire()
return cw.Conn.Close()
}
// These custom creds are used for storing the connections made by the client.
// The closeCh in conn can be used to detect when conn is closed.
type transportRestartCheckCreds struct {
mu sync.Mutex
connections []*connWrapperWithCloseCh
}
func (c *transportRestartCheckCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
return rawConn, nil, nil
}
func (c *transportRestartCheckCreds) ClientHandshake(_ context.Context, _ string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
c.mu.Lock()
defer c.mu.Unlock()
conn := &connWrapperWithCloseCh{Conn: rawConn, close: grpcsync.NewEvent()}
c.connections = append(c.connections, conn)
return conn, nil, nil
}
func (c *transportRestartCheckCreds) Info() credentials.ProtocolInfo {
return credentials.ProtocolInfo{}
}
func (c *transportRestartCheckCreds) Clone() credentials.TransportCredentials {
return c
}
func (c *transportRestartCheckCreds) OverrideServerName(string) error {
return nil
}
// Tests that the client transport drains and restarts when next stream ID exceeds
// MaxStreamID. This test also verifies that subsequent RPCs use a new client
// transport and the old transport is closed.
func (s) TestClientTransportRestartsAfterStreamIDExhausted(t *testing.T) {
// Set the transport's MaxStreamID to 4 to cause connection to drain after 2 RPCs.
originalMaxStreamID := transport.MaxStreamID
transport.MaxStreamID = 4
defer func() {
transport.MaxStreamID = originalMaxStreamID
}()
ss := &stubserver.StubServer{
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
if _, err := stream.Recv(); err != nil {
return status.Errorf(codes.Internal, "unexpected error receiving: %v", err)
}
if err := stream.Send(&testpb.StreamingOutputCallResponse{}); err != nil {
return status.Errorf(codes.Internal, "unexpected error sending: %v", err)
}
if recv, err := stream.Recv(); err != io.EOF {
return status.Errorf(codes.Internal, "Recv = %v, %v; want _, io.EOF", recv, err)
}
return nil
},
}
creds := &transportRestartCheckCreds{}
if err := ss.Start(nil, grpc.WithTransportCredentials(creds)); err != nil {
t.Fatalf("Starting stubServer: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
var streams []testgrpc.TestService_FullDuplexCallClient
const numStreams = 3
// expected number of conns when each stream is created i.e., 3rd stream is created
// on a new connection.
expectedNumConns := [numStreams]int{1, 1, 2}
// Set up 3 streams.
for i := 0; i < numStreams; i++ {
s, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("Creating FullDuplex stream: %v", err)
}
streams = append(streams, s)
// Verify expected num of conns after each stream is created.
if len(creds.connections) != expectedNumConns[i] {
t.Fatalf("Got number of connections created: %v, want: %v", len(creds.connections), expectedNumConns[i])
}
}
// Verify all streams still work.
for i, stream := range streams {
if err := stream.Send(&testpb.StreamingOutputCallRequest{}); err != nil {
t.Fatalf("Sending on stream %d: %v", i, err)
}
if _, err := stream.Recv(); err != nil {
t.Fatalf("Receiving on stream %d: %v", i, err)
}
}
for i, stream := range streams {
if err := stream.CloseSend(); err != nil {
t.Fatalf("CloseSend() on stream %d: %v", i, err)
}
}
// Verifying first connection was closed.
select {
case <-creds.connections[0].close.Done():
case <-ctx.Done():
t.Fatal("Timeout expired when waiting for first client transport to close")
}
}
// Tests that an RST_STREAM frame that causes an io.ErrUnexpectedEOF while
// reading a gRPC message is correctly converted to a gRPC status with code
// CANCELLED. The test sends a data frame with a partial gRPC message, followed
// by an RST_STREAM frame with HTTP/2 code CANCELLED. The test asserts the
// client receives the correct status.
func (s) TestRSTDuringMessageRead(t *testing.T) {
lis, err := testutils.LocalTCPListener()
if err != nil {
t.Fatal(err)
}
defer lis.Close()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("grpc.NewClient(%s) = %v", lis.Addr().String(), err)
}
defer cc.Close()
go func() {
conn, err := lis.Accept()
if err != nil {
t.Errorf("lis.Accept() = %v", err)
return
}
defer conn.Close()
framer := http2.NewFramer(conn, conn)
if _, err := io.ReadFull(conn, make([]byte, len(clientPreface))); err != nil {
t.Errorf("Error while reading client preface: %v", err)
return
}
if err := framer.WriteSettings(); err != nil {
t.Errorf("Error while writing settings: %v", err)
return
}
if err := framer.WriteSettingsAck(); err != nil {
t.Errorf("Error while writing settings: %v", err)
return
}
for ctx.Err() == nil {
frame, err := framer.ReadFrame()
if err != nil {
return
}
switch frame := frame.(type) {
case *http2.HeadersFrame:
// When the client creates a stream, write a partial gRPC
// message followed by an RST_STREAM.
const messageLen = 2048
buf := make([]byte, messageLen/2)
// Write the gRPC message length header.
binary.BigEndian.PutUint32(buf[1:5], uint32(messageLen))
if err := framer.WriteData(1, false, buf); err != nil {
return
}
framer.WriteRSTStream(1, http2.ErrCodeCancel)
default:
t.Logf("Server received frame: %v", frame)
}
}
}()
// The server will send a partial gRPC message before cancelling the stream.
// The client should get a gRPC status with code CANCELLED.
client := testgrpc.NewTestServiceClient(cc)
if _, err := client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Canceled {
t.Fatalf("client.EmptyCall() returned %v; want status with code %v", err, codes.Canceled)
}
}
// Test verifies that a client-side cancellation correctly frees up resources on
// the server. The test setup is designed to simulate a scenario where a server
// is blocked from sending a large message due to a full client-side flow
// control window. The client-side cancellation of this blocked RPC then frees
// up the max concurrent streams quota on the server, allowing a new RPC to be
// created successfully.
func (s) TestCancelWhileServerWaitingForFlowControl(t *testing.T) {
serverDoneCh := make(chan struct{}, 2)
const flowControlWindowSize = 65535
ss := &stubserver.StubServer{
StreamingOutputCallF: func(_ *testpb.StreamingOutputCallRequest, stream testpb.TestService_StreamingOutputCallServer) error {
// Send a large message to exhaust the client's flow control window.
stream.Send(&testpb.StreamingOutputCallResponse{
Payload: &testpb.Payload{
Body: make([]byte, flowControlWindowSize+1),
},
})
serverDoneCh <- struct{}{}
return nil
},
}
// Create a server that allows only 1 stream at a time.
ss = stubserver.StartTestService(t, ss, grpc.MaxConcurrentStreams(1))
defer ss.Stop()
// Use a static flow control window.
if err := ss.StartClient(grpc.WithInitialWindowSize(flowControlWindowSize)); err != nil {
t.Fatalf("Error while start test service client: %v", err)
}
client := ss.Client
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
streamCtx, streamCancel := context.WithCancel(ctx)
defer streamCancel()
if _, err := client.StreamingOutputCall(streamCtx, &testpb.StreamingOutputCallRequest{}); err != nil {
t.Fatalf("Failed to create server streaming RPC: %v", err)
}
// Wait for the server handler to return. This should cause the trailers to
// be buffered on the server, waiting for flow control quota to first send
// the data frame.
select {
case <-ctx.Done():
t.Fatal("Context timed out waiting for server handler to return.")
case <-serverDoneCh:
}
// Attempt to create a stream. It should fail since the previous stream is
// still blocked.
shortCtx, shortCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
defer shortCancel()
_, err := client.StreamingOutputCall(shortCtx, &testpb.StreamingOutputCallRequest{})
if status.Code(err) != codes.DeadlineExceeded {
t.Fatalf("Server stream creation returned error with unexpected status code: %v, want code: %v", err, codes.DeadlineExceeded)
}
// Cancel the RPC, this should free up concurrent stream quota on the
// server.
streamCancel()
// Attempt to create another stream.
stream, err := client.StreamingOutputCall(ctx, &testpb.StreamingOutputCallRequest{})
if err != nil {
t.Fatalf("Failed to create server streaming RPC: %v", err)
}
_, err = stream.Recv()
if err != nil {
t.Fatalf("Failed to read from the stream: %v", err)
}
}