github.com/lingyao2333/mo-zero@v1.4.1/zrpc/internal/serverinterceptors/timeoutinterceptor.go (about) 1 package serverinterceptors 2 3 import ( 4 "context" 5 "fmt" 6 "runtime/debug" 7 "strings" 8 "sync" 9 "time" 10 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/status" 14 ) 15 16 // UnaryTimeoutInterceptor returns a func that sets timeout to incoming unary requests. 17 func UnaryTimeoutInterceptor(timeout time.Duration) grpc.UnaryServerInterceptor { 18 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, 19 handler grpc.UnaryHandler) (interface{}, error) { 20 ctx, cancel := context.WithTimeout(ctx, timeout) 21 defer cancel() 22 23 var resp interface{} 24 var err error 25 var lock sync.Mutex 26 done := make(chan struct{}) 27 // create channel with buffer size 1 to avoid goroutine leak 28 panicChan := make(chan interface{}, 1) 29 go func() { 30 defer func() { 31 if p := recover(); p != nil { 32 // attach call stack to avoid missing in different goroutine 33 panicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack()))) 34 } 35 }() 36 37 lock.Lock() 38 defer lock.Unlock() 39 resp, err = handler(ctx, req) 40 close(done) 41 }() 42 43 select { 44 case p := <-panicChan: 45 panic(p) 46 case <-done: 47 lock.Lock() 48 defer lock.Unlock() 49 return resp, err 50 case <-ctx.Done(): 51 err := ctx.Err() 52 if err == context.Canceled { 53 err = status.Error(codes.Canceled, err.Error()) 54 } else if err == context.DeadlineExceeded { 55 err = status.Error(codes.DeadlineExceeded, err.Error()) 56 } 57 return nil, err 58 } 59 } 60 }