C++ Logo

std-proposals

Advanced search

Re: Stacktrace from exception

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Wed, 28 Apr 2021 19:42:24 +0300
On 4/28/21 6:41 PM, Edward Catmur wrote:
> On Wed, 28 Apr 2021 at 12:04, Andrey Semashev via Std-Proposals
> <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>>
> wrote:
>
> You're only unwinding up to the exception handler. And the handler may
> save the stacktrace to extend its lifetime beyond its scope. This
> means,
> to make the stacktrace persistent and not limited to the exception
> handler stack frame, one has to unwind and save information beyond the
> exception handler.
>
>
> Ah, but you only need to save the stacktrace up to the exception
> handler; you can collect the remaining pointers up to thread entry at
> the time you save the stacktrace.

If you're going to add new elements to the list of stacktrace entries
then you will probably have to reallocate memory to store the added
data. I'm not sure this would have optimal performance.

> And that means that you don't actually need a heap allocation; there's
> plenty of room on the stack that you've just unwound (each stack frame
> occupies at least a pointer's worth of stack space). Might be a bit
> tricky though.

I don't see how you would be able to use the stack space given that (a)
the stacktrace has to be persistent, and (b) the stack can be clobbered
by any code executing - the exception handler itself, the functions it
calls, etc.

Also note that the exception handler is executed after stack unwinding.
This means destructors have already run at that point and the stack is
probably clobbered. So the stacktrace must be captured and saved at a
non-stack memory (likely, heap) before unwinding starts, way before the
exception handler is run.

> You'd still need a heap allocation to attach a stacktrace to
> exception_ptr; this also raises the question of what a stacktrace from a
> rethrown exception transported in an exception_ptr should look like. I'd
> argue that it should contain the stacktrace from original throw to
> original handler, spliced onto the rethrow point.
>
> > That makes it significantly less useful; where we've found exception
> > stack traces most useful is when an exception is being thrown
> from deep
> > inside third-party library code, that we either don't control or
> know
> > enough about to modify all throw sites.
>
> I realize that. But at the same time, I would not want all that
> overhead
> to capture the stacktrace in cases when I don't use or don't care about
> the stacktrace, and that is the absolute majority of cases today.
>
> You're using exceptions in situations where you don't care about the
> stacktrace? Does that mean you're using exceptions as flow control?

In my current code base I don't use stacktraces, at all. Well, except
when the process crashes, which is when I do generate a crash report
with stacktraces of all threads, but that's not what we're discussing.

Regarding exceptions, there are definitely places where I don't and
won't want stacktraces collected - because the other diagnostic
information I attach to exceptions is far more important for problem
investigation than a stacktrace would be. I'm talking about data like
input and current state information that led to the exceptional case.
Together with logging, this is more useful than a stacktrace would be.
In other cases, the exceptions I throw are not handled by my code (e.g.
are passed on to the calling code, for example, via RPC). In these
cases, the throw site is often close to the entry point, and the
stacktrace would be useless there. In case of RPC, the stacktrace would
be discarded anyway, and in case of a direct call the caller is able to
(and will likely) catch the exception near the call site and log
whatever relevant information it has for further investigation.

As another data point, I've seen exceptions used to abort a blocking
opereation
  that would normally return a useful result.
  Not that I use this approach personally in the code I write, but I can
see the appeal. Naturally, stacktraces for such exceptions would be useless.



There probably are places where I would like to have a stacktrace to
output. I would estimate that the latter cases are by far a minority in
my code base. Probably because the code was designed with the premise
that stacktraces are not easily available and expensive to obtain.

> The way we use exceptions they are encountered rarely, and so the
> minimal overhead of capturing a partial stacktrace on throw (to handler)
> would be entirely acceptable. The more so if there is no heap allocation
> until std::current_exception_stacktrace() or std::current_exception() is
> called.

I would agree that exceptions are not intended to happen often. However,
it doesn't mean they *don't* happen often, and therefore if they do, you
normally want to dispatch them fast. Surely, you don't want to waste a
lot of cycles on handling the exceptions that repeatedly happen when
your program repeatedly encounters some sort of unexpected/invalid
input. Especially, when a lot of the cycles are wasted on stacktraces
that you don't use.

> In my view, this new feature has to be accompanied by the standard
> library additions, which would make attaching arbitrary data, including
> stacktraces, to exceptions easy. Something similar to Boost.Exception.
> As a result, this feature would be used where it truly matters.
>
> I hope I'm wrong, but this sounds to me as if you're trying to kill a
> concise feature by expanding its scope and tying the kitchen sink to it.

No, I'm opposed to the proposed feature as I view it as having too much
overhead, while I don't see myself (based on my experience) benefiting
from it that much. The idea of attaching arbitrary data to exceptions is
not mine, and I mentioned it only because *that* is how I view this
feature as being useful to me at an acceptable cost.

> If you want to attach arbitrary data to an exception, what's wrong with
> std::nested_exception?

std::nested_exception is a completely different story. It is not
designed to attach arbitrary data to exceptions, it's designed to chain
multiple exceptions together. It won't allow you to extract the attached
data, unless it is compatible with exception_ptr or cat be
dynamic_cast'ed to. It is not compatible with fundamental types (e.g.
const char* and unsigned int - the typical types used for source file
location).

Boost.Exception, on the other hand, makes it easy both to attach and
extract the data. This implies support for identification of the data
via tag types, which std::nested_exception completely lacks.

Received on 2021-04-28 11:42:31