trpc.group/trpc-go/trpc-go@v1.0.3/http/value_detached_ctx_test.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package http
    15  
    16  import (
    17  	"context"
    18  	"runtime"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  type ctxKey struct{}
    26  
    27  func TestValueDetachedCtxDeadline(t *testing.T) {
    28  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10)
    29  	defer cancel()
    30  	detachedCtx := detachCtxValue(ctx)
    31  	deadline, ok := ctx.Deadline()
    32  	newDeadline, newOk := detachedCtx.Deadline()
    33  	require.Equal(t, ok, newOk)
    34  	require.Equal(t, deadline, newDeadline)
    35  	<-ctx.Done()
    36  	newDeadline, newOk = detachedCtx.Deadline()
    37  	require.Equal(t, ok, newOk, "deadline should not change after ctx done")
    38  	require.Equal(t, deadline, newDeadline, "deadline should not change after ctx done")
    39  }
    40  
    41  func TestValueDetachedCtxDone(t *testing.T) {
    42  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10)
    43  	defer cancel()
    44  	ctx = detachCtxValue(context.WithValue(ctx, ctxKey{}, struct{}{}))
    45  	require.Nil(t, ctx.Err(), "should not timed out yet")
    46  	deadline, ok := ctx.Deadline()
    47  	require.True(t, ok)
    48  	require.NotEqual(t, deadline, time.Time{})
    49  	<-ctx.Done()
    50  	// Sleep a while to wait ctx done propagate to detached ctx.
    51  	time.Sleep(time.Millisecond * 20)
    52  	require.NotNil(t, ctx.Err(), "ctx has timed out")
    53  }
    54  
    55  func TestValueDetachedCtxValue(t *testing.T) {
    56  	ctx, cancel := context.WithCancel(context.Background())
    57  	defer cancel()
    58  	ctx = context.WithValue(ctx, ctxKey{}, struct{}{})
    59  	ctx = detachCtxValue(ctx)
    60  	require.Equal(t, ctx.Value(ctxKey{}), nil)
    61  }
    62  
    63  func TestValueDetachedCtxGC(t *testing.T) {
    64  	newValueCtx := func() (ctx context.Context, valGCed func() bool) {
    65  		var i int
    66  		iGCed := make(chan struct{})
    67  		runtime.SetFinalizer(&i, func(*int) {
    68  			close(iGCed)
    69  		})
    70  		return context.WithValue(context.Background(), ctxKey{}, &i), func() bool {
    71  			// Wait a while before finalizer could run.
    72  			select {
    73  			case <-time.After(time.Millisecond * 100):
    74  				return false
    75  			case <-iGCed:
    76  				return true
    77  			}
    78  		}
    79  	}
    80  
    81  	ctx, valGCed := newValueCtx()
    82  	_ = detachCtxValue(ctx)
    83  
    84  	// The original ctx is only swept in second GC circle due to go's tri-color GC algorithm.
    85  	runtime.GC()
    86  	runtime.GC()
    87  	require.True(t, valGCed(), "allocated val should be GCed")
    88  }
    89  
    90  func TestValueDetachedCtxGCCancelableCtx(t *testing.T) {
    91  	newValueCtx := func() (ctx context.Context, cancel func(), valGCed func() bool) {
    92  		var i int
    93  		iGCed := make(chan struct{})
    94  		runtime.SetFinalizer(&i, func(*int) {
    95  			close(iGCed)
    96  		})
    97  		ctx, cancel = context.WithCancel(context.Background())
    98  		return context.WithValue(ctx, ctxKey{}, &i),
    99  			cancel,
   100  			func() bool {
   101  				// Wait a while before finalizer could run.
   102  				select {
   103  				case <-time.After(time.Millisecond * 100):
   104  					return false
   105  				case <-iGCed:
   106  					return true
   107  				}
   108  			}
   109  	}
   110  
   111  	ctx, cancel, valGCed := newValueCtx()
   112  	_ = detachCtxValue(ctx)
   113  
   114  	// The original ctx is not swept before second GC circle due to go's tri-color GC algorithm.
   115  	runtime.GC()
   116  	runtime.GC()
   117  	require.False(t, valGCed(), "allocated resource can not be GCed before cancel original ctx")
   118  
   119  	cancel()
   120  	runtime.GC()
   121  	runtime.GC()
   122  	require.True(t, valGCed(), "allocated resource should be GCed after cancel original ctx")
   123  }