@@ -5,11 +5,15 @@ import (
55 "log"
66 "runtime"
77 "strings"
8+ "sync"
89 "time"
910
1011 pkgerrors "github.com/pkg/errors"
1112)
1213
14+ // CaptureTimeout limits how long to wait for a capture ID to be returned from a capture handler.
15+ var CaptureTimeout = 500 * time .Millisecond
16+
1317type CaptureProvider string // i.e. "sentry"
1418
1519type CaptureID string // may be a URL or any string that allows a captured error to be looked up
@@ -117,11 +121,12 @@ func alert(exception error) error {
117121 // infinite recursion. Here, we try to prevent that. This is relatively expensive, but we're alerting, which
118122 // shouldn't happen often.
119123 pc := make ([]uintptr , 42 )
120- runtime .Callers (1 , pc ) // skip 1 (runtime.Callers)
124+ runtime .Callers (1 , pc ) // skip 1 (the one skipped is runtime.Callers)
121125 cf := runtime .CallersFrames (pc )
122126 us , _ := cf .Next ()
123127 for them , ok := cf .Next (); ok ; them , ok = cf .Next () {
124- if us .Func .Name () == them .Func .Name () {
128+ // use HasPrefix here, not simple equality, because handlers are called from goroutine (below)
129+ if strings .HasPrefix (them .Func .Name (), us .Func .Name ()) {
125130 log .Printf ("cannot alert, recursion detected (%s): %+v" , us .Func .Name (), exception )
126131 return exception // don't recurse again
127132 }
@@ -149,18 +154,56 @@ func alert(exception error) error {
149154 return true
150155 })
151156
157+ // Run handlers in goroutines, so that if one handler is deadlocked
158+ // it does not prevent others from running, or us from returning.
159+
160+ timer := time .NewTimer (CaptureTimeout )
161+ defer timer .Stop ()
162+
163+ done := make (chan struct {})
164+ finish := sync .OnceFunc (func () {close (done )})
165+ var mu sync.Mutex
166+
167+ // start a goroutine for each handler
152168 for provider , handler := range capture {
153- defer func () {
154- if r := recover (); r != nil {
155- log .Printf ("failed to capture exception (%q): %+v" , provider , r )
169+ provider := provider
170+ handler := handler
171+ go func () {
172+ defer func () {
173+ if r := recover (); r != nil {
174+ log .Printf ("failed to capture exception (%q): %+v" , provider , r )
175+ }
176+ }()
177+
178+ id := handler (exception , arg ... )
179+
180+ mu .Lock ()
181+ defer mu .Unlock ()
182+ select {
183+ case <- done :
184+ // we are too late
185+ default :
186+ e .id [provider ] = id
187+ if len (e .id ) == len (capture ) {
188+ finish ()
189+ }
156190 }
157191 }()
192+ }
158193
159- id := handler (exception , arg ... )
160- if id != "" {
161- e .id [provider ] = id
194+ // wait until done or timed out
195+ waitLoop:
196+ for {
197+ select {
198+ case <- timer .C :
199+ mu .Lock ()
200+ defer mu .Unlock ()
201+ finish ()
202+ case <- done :
203+ break waitLoop
162204 }
163205 }
206+
164207 return e
165208}
166209
0 commit comments