github.com/cloudwego/kitex@v0.9.0/pkg/streaming/timeout.go (about) 1 /* 2 * Copyright 2023 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 streaming 18 19 import ( 20 "context" 21 "fmt" 22 "runtime/debug" 23 "time" 24 25 "github.com/bytedance/gopkg/util/gopool" 26 27 "github.com/cloudwego/kitex/pkg/kerrors" 28 ) 29 30 // CallWithTimeout executes a function with timeout. 31 // If timeout is 0, the function will be executed without timeout; panic is not recovered in this case; 32 // If time runs out, it will return a kerrors.ErrRPCTimeout; 33 // If your function panics, it will also return a kerrors.ErrRPCTimeout with the panic details; 34 // Other kinds of errors are always returned by your function. 35 // 36 // NOTE: the `cancel` function is necessary to cancel the underlying transport, otherwise the recv/send call 37 // will block until the transport is closed, which may cause surge of goroutines. 38 func CallWithTimeout(timeout time.Duration, cancel context.CancelFunc, f func() (err error)) error { 39 if timeout <= 0 { 40 return f() 41 } 42 if cancel == nil { 43 panic("nil cancel func. Please pass in the cancel func in the context (set in context BEFORE " + 44 "the client method call, NOT just before recv/send), to avoid blocking recv/send calls") 45 } 46 begin := time.Now() 47 timer := time.NewTimer(timeout) 48 defer timer.Stop() 49 finishChan := make(chan error, 1) // non-blocking channel to avoid goroutine leak 50 gopool.Go(func() { 51 var bizErr error 52 defer func() { 53 if r := recover(); r != nil { 54 cancel() 55 timeoutErr := fmt.Errorf( 56 "CallWithTimeout: panic in business code, timeout_config=%v, panic=%v, stack=%s", 57 timeout, r, debug.Stack()) 58 bizErr = kerrors.ErrRPCTimeout.WithCause(timeoutErr) 59 } 60 finishChan <- bizErr 61 }() 62 bizErr = f() 63 }) 64 select { 65 case <-timer.C: 66 cancel() 67 timeoutErr := fmt.Errorf("CallWithTimeout: timeout in business code, timeout_config=%v, actual=%v", 68 timeout, time.Since(begin)) 69 return kerrors.ErrRPCTimeout.WithCause(timeoutErr) 70 case bizErr := <-finishChan: 71 return bizErr 72 } 73 }