go.uber.org/yarpc@v1.72.1/api/transport/handler_invoker.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package transport 22 23 import ( 24 "context" 25 "fmt" 26 "log" 27 "runtime/debug" 28 "time" 29 30 "go.uber.org/yarpc/yarpcerrors" 31 "go.uber.org/zap" 32 ) 33 34 // UnaryInvokeRequest encapsulates arguments to invoke a unary handler. 35 type UnaryInvokeRequest struct { 36 Context context.Context 37 StartTime time.Time 38 Request *Request 39 ResponseWriter ResponseWriter 40 Handler UnaryHandler 41 Logger *zap.Logger // optional 42 } 43 44 // OnewayInvokeRequest encapsulates arguments to invoke a unary handler. 45 type OnewayInvokeRequest struct { 46 Context context.Context 47 Request *Request 48 Handler OnewayHandler 49 Logger *zap.Logger // optional 50 } 51 52 // StreamInvokeRequest encapsulates arguments to invoke a unary handler. 53 type StreamInvokeRequest struct { 54 Stream *ServerStream 55 Handler StreamHandler 56 Logger *zap.Logger // optional 57 } 58 59 // InvokeUnaryHandler calls the handler h, recovering panics and timeout errors, 60 // converting them to YARPC errors. All other errors are passed through. 61 func InvokeUnaryHandler( 62 i UnaryInvokeRequest, 63 ) (err error) { 64 defer func() { 65 if r := recover(); r != nil { 66 err = handlePanic(Unary, i.Logger, r, i.Request.ToRequestMeta()) 67 } 68 }() 69 70 err = i.Handler.Handle(i.Context, i.Request, i.ResponseWriter) 71 72 // The handler stopped work on context deadline. 73 if err == context.DeadlineExceeded && err == i.Context.Err() { 74 deadline, _ := i.Context.Deadline() 75 err = yarpcerrors.Newf( 76 yarpcerrors.CodeDeadlineExceeded, 77 "call to procedure %q of service %q from caller %q timed out after %v", 78 i.Request.Procedure, i.Request.Service, i.Request.Caller, deadline.Sub(i.StartTime)) 79 } 80 return err 81 } 82 83 // InvokeOnewayHandler calls the oneway handler, recovering from panics as 84 // errors. 85 func InvokeOnewayHandler( 86 i OnewayInvokeRequest, 87 ) (err error) { 88 defer func() { 89 if r := recover(); r != nil { 90 err = handlePanic(Oneway, i.Logger, r, i.Request.ToRequestMeta()) 91 } 92 }() 93 94 return i.Handler.HandleOneway(i.Context, i.Request) 95 } 96 97 // InvokeStreamHandler calls the stream handler, recovering from panics as 98 // errors. 99 func InvokeStreamHandler( 100 i StreamInvokeRequest, 101 ) (err error) { 102 defer func() { 103 if r := recover(); r != nil { 104 err = handlePanic(Streaming, i.Logger, r, i.Stream.Request().Meta) 105 } 106 }() 107 108 return i.Handler.HandleStream(i.Stream) 109 } 110 111 func handlePanic(rpcType Type, logger *zap.Logger, recovered interface{}, reqMeta *RequestMeta) error { 112 err := fmt.Errorf("panic: %v", recovered) 113 if logger != nil { 114 logPanic(rpcType, logger, err, reqMeta) 115 return err 116 } 117 log.Printf("%s handler panicked: %v\n%s", rpcType, recovered, debug.Stack()) 118 return err 119 } 120 121 func logPanic(rpcType Type, logger *zap.Logger, err error, reqMeta *RequestMeta) { 122 logger.Error(fmt.Sprintf("%s handler panicked", rpcType), 123 zap.String("service", reqMeta.Service), 124 zap.String("transport", reqMeta.Transport), 125 zap.String("procedure", reqMeta.Procedure), 126 zap.String("encoding", string(reqMeta.Encoding)), 127 zap.String("caller", reqMeta.Caller), 128 zap.Error(err), 129 zap.Stack("stack"), 130 ) 131 }