github.com/cloudwego/kitex@v0.9.0/client/rpctimeout_test.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package client 18 19 import ( 20 "context" 21 "errors" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/cloudwego/kitex/internal/test" 27 "github.com/cloudwego/kitex/pkg/endpoint" 28 "github.com/cloudwego/kitex/pkg/kerrors" 29 "github.com/cloudwego/kitex/pkg/rpcinfo" 30 "github.com/cloudwego/kitex/pkg/rpctimeout" 31 ) 32 33 var panicMsg = "mock panic" 34 35 func block(ctx context.Context, request, response interface{}) (err error) { 36 time.Sleep(1 * time.Second) 37 return nil 38 } 39 40 func pass(ctx context.Context, request, response interface{}) (err error) { 41 time.Sleep(200 * time.Millisecond) 42 return nil 43 } 44 45 func panicEp(ctx context.Context, request, response interface{}) (err error) { 46 panic(panicMsg) 47 } 48 49 func TestNewRPCTimeoutMW(t *testing.T) { 50 t.Parallel() 51 52 s := rpcinfo.NewEndpointInfo("mockService", "mockMethod", nil, nil) 53 c := rpcinfo.NewRPCConfig() 54 r := rpcinfo.NewRPCInfo(nil, s, nil, c, rpcinfo.NewRPCStats()) 55 m := rpcinfo.AsMutableRPCConfig(c) 56 m.SetRPCTimeout(time.Millisecond * 500) 57 58 var moreTimeout time.Duration 59 ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), r) 60 mwCtx := context.Background() 61 mwCtx = context.WithValue(mwCtx, rpctimeout.TimeoutAdjustKey, &moreTimeout) 62 63 var err error 64 var mw1, mw2 endpoint.Middleware 65 66 // 1. normal 67 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 68 mw2 = rpcTimeoutMW(mwCtx) 69 err = mw1(mw2(pass))(ctx, nil, nil) 70 test.Assert(t, err == nil) 71 72 // 2. block to mock timeout 73 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 74 mw2 = rpcTimeoutMW(mwCtx) 75 err = mw1(mw2(block))(ctx, nil, nil) 76 test.Assert(t, err != nil, err) 77 test.Assert(t, err.(*kerrors.DetailedError).ErrorType() == kerrors.ErrRPCTimeout) 78 79 // 3. block, pass more timeout, timeout won't happen 80 mw1 = rpctimeout.MiddlewareBuilder(510 * time.Millisecond)(mwCtx) 81 mw2 = rpcTimeoutMW(mwCtx) 82 err = mw1(mw2(block))(ctx, nil, nil) 83 test.Assert(t, err == nil) 84 85 // 4. cancel 86 cancelCtx, cancelFunc := context.WithCancel(ctx) 87 time.AfterFunc(100*time.Millisecond, func() { 88 cancelFunc() 89 }) 90 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 91 mw2 = rpcTimeoutMW(mwCtx) 92 err = mw1(mw2(block))(cancelCtx, nil, nil) 93 test.Assert(t, errors.Is(err, context.Canceled), err) 94 95 // 5. panic with timeout, the panic is recovered 96 // < v1.1.* panic happen, >=v1.1* wrap panic to error 97 m.SetRPCTimeout(time.Millisecond * 500) 98 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 99 mw2 = rpcTimeoutMW(mwCtx) 100 err = mw1(mw2(panicEp))(ctx, nil, nil) 101 test.Assert(t, strings.Contains(err.Error(), panicMsg)) 102 103 // 6. panic without timeout, panic won't be recovered in timeout mw, but it will be recoverd in client.Call 104 m.SetRPCTimeout(0) 105 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 106 mw2 = rpcTimeoutMW(mwCtx) 107 test.Panic(t, func() { mw1(mw2(panicEp))(ctx, nil, nil) }) 108 109 // 7. panic with streaming, panic won't be recovered in timeout mw, but it will be recoverd in client.Call 110 m = rpcinfo.AsMutableRPCConfig(c) 111 m.SetInteractionMode(rpcinfo.Streaming) 112 mw1 = rpctimeout.MiddlewareBuilder(0)(mwCtx) 113 mw2 = rpcTimeoutMW(mwCtx) 114 test.Panic(t, func() { mw1(mw2(panicEp))(ctx, nil, nil) }) 115 } 116 117 func TestIsBusinessTimeout(t *testing.T) { 118 type args struct { 119 start time.Time 120 kitexTimeout time.Duration 121 actualDDL time.Time 122 threshold time.Duration 123 } 124 start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 125 tests := []struct { 126 name string 127 args args 128 want bool 129 }{ 130 { 131 name: "business ddl ahead of kitex ddl by more than threshold", 132 args: args{ 133 start: start, 134 kitexTimeout: time.Second * 2, 135 actualDDL: start.Add(time.Second), 136 threshold: time.Millisecond * 500, 137 }, 138 want: true, 139 }, 140 { 141 name: "business ddl ahead of kitex ddl by less than threshold", 142 args: args{ 143 start: start, 144 kitexTimeout: time.Second, 145 actualDDL: start.Add(time.Millisecond * 800), 146 threshold: time.Millisecond * 500, 147 }, 148 want: false, 149 }, 150 } 151 for _, tt := range tests { 152 t.Run(tt.name, func(t *testing.T) { 153 if got := isBusinessTimeout(tt.args.start, tt.args.kitexTimeout, tt.args.actualDDL, tt.args.threshold); got != tt.want { 154 t.Errorf("isBusinessTimeout() = %v, want %v", got, tt.want) 155 } 156 }) 157 } 158 } 159 160 func TestRpcTimeoutMWTimeoutByBusiness(t *testing.T) { 161 runTimeoutMW := func(timeout time.Duration) error { 162 mw := rpcTimeoutMW(context.Background()) 163 processor := mw(func(ctx context.Context, req, rsp interface{}) error { 164 time.Sleep(time.Millisecond * 100) 165 return nil 166 }) 167 168 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*30) 169 defer cancel() 170 171 ctx = rpcinfo.NewCtxWithRPCInfo(ctx, mockRPCInfo(timeout)) 172 173 return processor(ctx, nil, nil) 174 } 175 176 t.Run("Timeout by business with no need fine grained err code", func(t *testing.T) { 177 rpctimeout.DisableGlobalNeedFineGrainedErrCode() 178 if err := runTimeoutMW(time.Second); !kerrors.IsTimeoutError(err) { 179 t.Errorf("rpcTimeoutMW(1s) = %v, want %v", err, kerrors.ErrRPCTimeout) 180 } 181 }) 182 183 t.Run("Timeout by business with no rpc timeout and no need fine grained err code", func(t *testing.T) { 184 rpctimeout.DisableGlobalNeedFineGrainedErrCode() 185 if err := runTimeoutMW(0); !kerrors.IsTimeoutError(err) { 186 t.Errorf("rpcTimeoutMW(0) = %v, want %v", err, kerrors.ErrRPCTimeout) 187 } 188 }) 189 190 t.Run("Timeout by business with need fine grained err code", func(t *testing.T) { 191 rpctimeout.EnableGlobalNeedFineGrainedErrCode() 192 if err := runTimeoutMW(time.Second); err.(*kerrors.DetailedError).ErrorType() != kerrors.ErrTimeoutByBusiness { 193 t.Errorf("rpcTimeoutMW(1s) = %v, want %v", err, kerrors.ErrTimeoutByBusiness) 194 } 195 rpctimeout.DisableGlobalNeedFineGrainedErrCode() 196 }) 197 } 198 199 func TestRpcTimeoutMWCancelByBusiness(t *testing.T) { 200 runTimeoutMW := func() error { 201 mw := rpcTimeoutMW(context.Background()) 202 processor := mw(func(ctx context.Context, req, rsp interface{}) error { 203 time.Sleep(time.Millisecond * 100) 204 return nil 205 }) 206 207 ctx, cancel := context.WithCancel(context.Background()) 208 go func() { 209 time.Sleep(10 * time.Millisecond) 210 cancel() 211 }() 212 213 ctx = rpcinfo.NewCtxWithRPCInfo(ctx, mockRPCInfo(time.Second)) 214 215 return processor(ctx, nil, nil) 216 } 217 218 t.Run("Cancel by business with no need fine grained err code", func(t *testing.T) { 219 rpctimeout.DisableGlobalNeedFineGrainedErrCode() 220 if err := runTimeoutMW(); !kerrors.IsTimeoutError(err) { 221 t.Errorf("rpcTimeoutMW() = %v, want %v", err, kerrors.ErrRPCTimeout) 222 } 223 }) 224 225 t.Run("Cancel by business with need fine grained err code", func(t *testing.T) { 226 rpctimeout.EnableGlobalNeedFineGrainedErrCode() 227 if err := runTimeoutMW(); err.(*kerrors.DetailedError).ErrorType() != kerrors.ErrCanceledByBusiness { 228 t.Errorf("rpcTimeoutMW() = %v, want %v", err, kerrors.ErrCanceledByBusiness) 229 } 230 rpctimeout.DisableGlobalNeedFineGrainedErrCode() 231 }) 232 } 233 234 func mockRPCInfo(timeout time.Duration) rpcinfo.RPCInfo { 235 s := rpcinfo.NewEndpointInfo("mockService", "mockMethod", nil, nil) 236 c := rpcinfo.NewRPCConfig() 237 mc := rpcinfo.AsMutableRPCConfig(c) 238 _ = mc.SetRPCTimeout(timeout) 239 return rpcinfo.NewRPCInfo(nil, s, nil, c, rpcinfo.NewRPCStats()) 240 } 241 242 func Test_isBusinessTimeout(t *testing.T) { 243 type args struct { 244 start time.Time 245 kitexTimeout time.Duration 246 actualDDL time.Time 247 threshold time.Duration 248 } 249 now, _ := time.Parse(time.RFC3339, "2023-01-01T00:00:00Z08:00") 250 threshold := rpctimeout.LoadBusinessTimeoutThreshold() 251 tests := []struct { 252 name string 253 args args 254 want bool 255 }{ 256 { 257 name: "zero-timeout", 258 args: args{ 259 start: now, 260 kitexTimeout: 0, 261 actualDDL: now.Add(time.Second), 262 threshold: threshold, 263 }, 264 want: true, 265 }, 266 { 267 name: "after-timeout", 268 args: args{ 269 start: now, 270 kitexTimeout: time.Second, 271 actualDDL: now.Add(time.Second).Add(time.Millisecond), 272 threshold: threshold, 273 }, 274 want: false, 275 }, 276 { 277 name: "after-(timeout-threshold)", 278 args: args{ 279 start: now, 280 kitexTimeout: time.Second, 281 actualDDL: now.Add(time.Second).Add(-threshold).Add(time.Millisecond), 282 threshold: threshold, 283 }, 284 want: false, 285 }, 286 { 287 name: "before-(timeout-threshold)", 288 args: args{ 289 start: now, 290 kitexTimeout: time.Second, 291 actualDDL: now.Add(time.Second).Add(-threshold).Add(-time.Millisecond), 292 threshold: threshold, 293 }, 294 want: true, 295 }, 296 } 297 for _, tt := range tests { 298 t.Run(tt.name, func(t *testing.T) { 299 if got := isBusinessTimeout(tt.args.start, tt.args.kitexTimeout, tt.args.actualDDL, tt.args.threshold); got != tt.want { 300 t.Errorf("isBusinessTimeout() = %v, want %v", got, tt.want) 301 } 302 }) 303 } 304 }