@@ -116,6 +116,7 @@ package ctxgroup
116
116
117
117
import (
118
118
"context"
119
+ "fmt"
119
120
120
121
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
121
122
"github.com/cockroachdb/errors"
@@ -147,7 +148,7 @@ func (g Group) Wait() error {
147
148
err := g .wrapped .Wait ()
148
149
149
150
if g .panicMu .payload != nil {
150
- panic (g .panicMu .payload )
151
+ panic (verboseError { g .panicMu .payload } )
151
152
}
152
153
153
154
if err != nil {
@@ -224,15 +225,39 @@ func GoAndWait(ctx context.Context, fs ...func(ctx context.Context) error) error
224
225
}
225
226
226
227
// wrapPanic turns r into an error if it is not one already.
227
- //
228
- // TODO(dt): consider raw recovered payload as well.
229
- //
230
- // TODO(dt): replace this with logcrash.PanicAsError after moving that to a new
231
- // standalone pkg (since we cannot depend on `util/log` here) and teaching it to
232
- // preserve the original payload.
233
228
func wrapPanic (depth int , r interface {}) error {
229
+ // If r is already a verboseError, we remove the `verboseError` wrapper
230
+ // because it will be reapplied when the panic is rethrown by wait. The stack
231
+ // trace is attached to the inner error. Its desirable to remove the verbose
232
+ // format wrapper from the child because the parent verbose wrapper will
233
+ // print the child errors .Error() method and the child's stack. If we left
234
+ // the wrapper in place, this would print the child error's stack twice.
235
+ if err , ok := r .(verboseError ); ok {
236
+ return errors .WithStackDepth (err .error , depth + 1 )
237
+ }
234
238
if err , ok := r .(error ); ok {
235
239
return errors .WithStackDepth (err , depth + 1 )
236
240
}
237
241
return errors .NewWithDepthf (depth + 1 , "panic: %v" , r )
238
242
}
243
+
244
+ type verboseError struct {
245
+ error // always a errors.WithStack.withStack
246
+ }
247
+
248
+ // Error overrides WithStack.withStack()'s Error to include the stack.
249
+ //
250
+ // Typically withstack's Error() just delegates to the underlying error and
251
+ // requires formatting with %+v to print the stacktrace. However this wrapper is
252
+ // used to wrap a recovered panic that is *rethrown*. If this rethrown panic is
253
+ // not recovered, the runtime will eventually crash and print it... by calling
254
+ // .Error(), which will *not* indicate the stack to the original panic we so
255
+ // dutifully captured by using WithStack. So override .Error() to include the
256
+ // stack, but leave .Format() to fallthrough to withStack as usual.
257
+ func (p verboseError ) Error () string {
258
+ return fmt .Sprintf ("%+v" , p .error )
259
+ }
260
+
261
+ func (p verboseError ) Unwrap () error {
262
+ return p .error
263
+ }
0 commit comments