C++ Logo

std-proposals

Advanced search

Re: Stacktrace from exception

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Wed, 28 Apr 2021 18:28:23 +0100
On Wed, 28 Apr 2021 at 17:42, Andrey Semashev via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On 4/28/21 6:41 PM, Edward Catmur wrote:
> > 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.
>

As in constructing a vector of pointers from iterator pair and then
appending to it? It's pretty much close to optimal.

> 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.
>

You use the base of the handler stack frame. alloca is still a thing. And
it doesn't need to be persistent, only to be copied out by anything that
wants to transport it out from the handler (in a std::stacktrace or
possibly std::exception_ptr object).


> 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.
>

It's possible to copy the partial stacktrace to below each destructor to
avoid clobbering.

Although that might end up being O(n^2) in number of stack frames with
destructors, so maybe not. So we're back at allocating sizeof(void (*)()) *
stack depth (non-inlined) from throw site to handler, if required.

> You're using exceptions in situations where you don't care about the
> > stacktrace? Does that mean you're using exceptions as flow control?
>
> 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.
>

But those are exceptions that you're throwing. Where stack traces come in
useful is investigating exceptions thrown by (standard or third-party)
library code.

That is, you're talking from the point of view of a library author. I'm
talking from the point of view of an application author.

> 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.
>

If an application is repeatedly encountering unexpected/invalid input, it's
going to be generating large amounts of log traffic (much of it, yes, stack
traces), generating issues, paging operations, etc. Anything that slows it
down from this is more than welcome.

> 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.
>

But you're against the aspect of the feature that I find useful, which is
collecting the stacktrace from the throw site?

> 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.
>

Well, obviously you'd use both std::nested_exception and Boost.Exception,
not to mention your own exception hierarchy inheriting from both. And
std::exception is polymorphic, as is boost::exception, so almost all of the
time (except with some really old libraries) there's no problem using
dynamic_cast.

The point of std::nested_exception is more that it allows you to attach
your own exception hierarchy to an existing exception of arbitrary type.

Received on 2021-04-28 12:28:37