@@ -39,9 +39,11 @@ import (
39
39
"github.com/google/go-cmp/cmp"
40
40
"golang.org/x/net/http2"
41
41
"golang.org/x/net/http2/hpack"
42
+
42
43
"google.golang.org/grpc/attributes"
43
44
"google.golang.org/grpc/codes"
44
45
"google.golang.org/grpc/credentials"
46
+ "google.golang.org/grpc/internal"
45
47
"google.golang.org/grpc/internal/channelz"
46
48
"google.golang.org/grpc/internal/grpctest"
47
49
"google.golang.org/grpc/internal/leakcheck"
@@ -3260,3 +3262,125 @@ func (s) TestClientTransport_Handle1xxHeaders(t *testing.T) {
3260
3262
})
3261
3263
}
3262
3264
}
3265
+
3266
+ func (s ) TestDeleteStreamMetricsIncrementedOnlyOnce (t * testing.T ) {
3267
+ // Enable channelz for metrics collection
3268
+ defer internal .ChannelzTurnOffForTesting ()
3269
+ if ! channelz .IsOn () {
3270
+ channelz .TurnOn ()
3271
+ }
3272
+
3273
+ for _ , test := range []struct {
3274
+ name string
3275
+ eosReceived bool
3276
+ wantStreamSucceeded int64
3277
+ wantStreamFailed int64
3278
+ }{
3279
+ {
3280
+ name : "StreamsSucceeded" ,
3281
+ eosReceived : true ,
3282
+ wantStreamSucceeded : 1 ,
3283
+ wantStreamFailed : 0 ,
3284
+ },
3285
+ {
3286
+ name : "StreamsFailed" ,
3287
+ eosReceived : false ,
3288
+ wantStreamSucceeded : 0 ,
3289
+ wantStreamFailed : 1 ,
3290
+ },
3291
+ } {
3292
+ t .Run (test .name , func (t * testing.T ) {
3293
+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
3294
+ defer cancel ()
3295
+
3296
+ // Setup server configuration with channelz support
3297
+ serverConfig := & ServerConfig {
3298
+ ChannelzParent : channelz .RegisterServer (t .Name ()),
3299
+ }
3300
+ defer channelz .RemoveEntry (serverConfig .ChannelzParent .ID )
3301
+
3302
+ // Create server and client with normal handler (not notifyCall)
3303
+ server , client , cancel := setUpWithOptions (t , 0 , serverConfig , normal , ConnectOptions {})
3304
+ defer func () {
3305
+ client .Close (fmt .Errorf ("test cleanup" ))
3306
+ server .stop ()
3307
+ cancel ()
3308
+ }()
3309
+
3310
+ // Wait for connection to be established
3311
+ waitWhileTrue (t , func () (bool , error ) {
3312
+ server .mu .Lock ()
3313
+ defer server .mu .Unlock ()
3314
+ if len (server .conns ) == 0 {
3315
+ return true , fmt .Errorf ("timed-out while waiting for connection" )
3316
+ }
3317
+ return false , nil
3318
+ })
3319
+
3320
+ // Get the server transport
3321
+ server .mu .Lock ()
3322
+ var serverTransport * http2Server
3323
+ for st := range server .conns {
3324
+ serverTransport = st .(* http2Server )
3325
+ break
3326
+ }
3327
+ server .mu .Unlock ()
3328
+
3329
+ if serverTransport == nil {
3330
+ t .Fatal ("Server transport not found" )
3331
+ }
3332
+
3333
+ clientStream , err := client .NewStream (ctx , & CallHdr {})
3334
+ if err != nil {
3335
+ t .Fatalf ("Failed to create stream: %v" , err )
3336
+ }
3337
+
3338
+ // Wait for the stream to be created on the server side
3339
+ var serverStream * ServerStream
3340
+ waitWhileTrue (t , func () (bool , error ) {
3341
+ serverTransport .mu .Lock ()
3342
+ defer serverTransport .mu .Unlock ()
3343
+ for _ , v := range serverTransport .activeStreams {
3344
+ if v .id == clientStream .id {
3345
+ serverStream = v
3346
+ return false , nil
3347
+ }
3348
+ }
3349
+ return true , nil
3350
+ })
3351
+
3352
+ if serverStream == nil {
3353
+ t .Fatalf ("Server stream not found for client stream ID %d" , clientStream .id )
3354
+ }
3355
+
3356
+ // First call to deleteStream should remove the stream from activeStreams and update metrics
3357
+ serverTransport .deleteStream (serverStream , test .eosReceived )
3358
+
3359
+ // Check metrics after first deleteStream call
3360
+ streamsSucceeded := serverTransport .channelz .SocketMetrics .StreamsSucceeded .Load ()
3361
+ streamsFailed := serverTransport .channelz .SocketMetrics .StreamsFailed .Load ()
3362
+
3363
+ if streamsSucceeded != test .wantStreamSucceeded {
3364
+ t .Errorf ("After first deleteStream - StreamsSucceeded: got %d, want %d" , streamsSucceeded , test .wantStreamSucceeded )
3365
+ }
3366
+ if streamsFailed != test .wantStreamFailed {
3367
+ t .Errorf ("After first deleteStream - StreamsFailed: got %d, want %d" , streamsFailed , test .wantStreamFailed )
3368
+ }
3369
+
3370
+ // Additional calls to deleteStream should not change metrics (stream already deleted)
3371
+ serverTransport .deleteStream (serverStream , test .eosReceived )
3372
+ serverTransport .deleteStream (serverStream , test .eosReceived )
3373
+
3374
+ // Verify metrics haven't changed after subsequent calls
3375
+ additionalStreamsSucceeded := serverTransport .channelz .SocketMetrics .StreamsSucceeded .Load ()
3376
+ additionalStreamsFailed := serverTransport .channelz .SocketMetrics .StreamsFailed .Load ()
3377
+
3378
+ if additionalStreamsSucceeded != test .wantStreamSucceeded {
3379
+ t .Errorf ("After multiple deleteStream calls - StreamsSucceeded changed: got %d, want %d" , additionalStreamsSucceeded , test .wantStreamSucceeded )
3380
+ }
3381
+ if additionalStreamsFailed != test .wantStreamFailed {
3382
+ t .Errorf ("After multiple deleteStream calls - StreamsFailed changed: got %d, want %d" , additionalStreamsFailed , test .wantStreamFailed )
3383
+ }
3384
+ })
3385
+ }
3386
+ }
0 commit comments