From 2c09a97f47615b07bb1a4fc426a7163433c0d3ea Mon Sep 17 00:00:00 2001 From: Agrim Prasad Date: Thu, 20 Aug 2020 16:50:03 +0800 Subject: [PATCH 1/2] HandlerWithKey:recover panic in callback need a panic recoverer as separate recoverer middleware can't catch panics in new goroutine --- http.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/http.go b/http.go index f0d92d6..4b2144b 100644 --- a/http.go +++ b/http.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "runtime/debug" "strings" "time" ) @@ -76,10 +77,23 @@ func HandlerWithKey(cacheSize int, ttl time.Duration, keyFunc ...func(r *http.Re } // mark the request that actually processes the response - first := false + var ( + err error + first bool + val interface{} + ) // process request (single flight) - val, err := cache.GetFresh(r.Context(), key, func(ctx context.Context) (interface{}, error) { + val, err = cache.GetFresh(r.Context(), key, func(ctx context.Context) (interface{}, error) { + defer func() { + // need a panic recoverer as separate recoverer middleware can't catch panics in new goroutine + if r := recover(); r != nil { + // shouldn't return in case of panic as it has not responded to client + first = false + err = fmt.Errorf("recovered panicking request:%#v, stack:%s", r, string(debug.Stack())) + } + }() + first = true buf := bytes.NewBuffer(nil) ww := &responseWriter{ResponseWriter: w, tee: buf} From 43cd1d9dd66eea05c28bd946ae1aa23f2bcaca78 Mon Sep 17 00:00:00 2001 From: Agrim Prasad Date: Thu, 20 Aug 2020 16:56:09 +0800 Subject: [PATCH 2/2] HandlerWithKey: handle panic error separately for stacktrace --- http.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/http.go b/http.go index 4b2144b..2c3ba91 100644 --- a/http.go +++ b/http.go @@ -78,9 +78,10 @@ func HandlerWithKey(cacheSize int, ttl time.Duration, keyFunc ...func(r *http.Re // mark the request that actually processes the response var ( - err error - first bool - val interface{} + err error + panicErr error + first bool + val interface{} ) // process request (single flight) @@ -88,9 +89,7 @@ func HandlerWithKey(cacheSize int, ttl time.Duration, keyFunc ...func(r *http.Re defer func() { // need a panic recoverer as separate recoverer middleware can't catch panics in new goroutine if r := recover(); r != nil { - // shouldn't return in case of panic as it has not responded to client - first = false - err = fmt.Errorf("recovered panicking request:%#v, stack:%s", r, string(debug.Stack())) + panicErr = fmt.Errorf("recovered panicking request:%#v, stack:%s", r, string(debug.Stack())) } }() @@ -108,6 +107,11 @@ func HandlerWithKey(cacheSize int, ttl time.Duration, keyFunc ...func(r *http.Re return val, nil }) + // handle panic for first or other listeners + if panicErr != nil { + panic(fmt.Sprintf("stampede: encountered unexpected panic, %v", panicErr)) + } + // the first request to trigger the fetch should return as it's already // responded to the client if first {