C++ Logo

std-proposals

Advanced search

Re: Stacktrace from exception

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Wed, 28 Apr 2021 22:22:54 +0300
On 4/28/21 8:28 PM, Edward Catmur wrote:
> On Wed, 28 Apr 2021 at 17:42, Andrey Semashev via Std-Proposals
> <std-proposals_at_[hidden] <mailto: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.

No. Optimal would be one allocation for the whole stacktrace, with no
reallocations later. Yet more optimal would be one allocation for both
the exception and the stacktrace, but that couples the exception and the
stacktrace, so probably not feasible. But the most optimal is to not
collect the stacktrace at all, unless explicitly requested by the user.

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

IMO, exceptions thrown by the standard library are close to useless. Not
in least part for the poor QoI of the standard libraries in this regard,
as many of them don't produce an informative error message with any
relevant information. Also because the standard library cannot provide
any context-specific details about the error (i.e. std::vector::at
cannot know what the vector is and where the index comes from, only the
caller knows that and can provide that information in the log). For this
reason, you should normally code so that you check the necessary
preconditions in your code before calling the standard library, and then
call standard library methods that don't throw.

Stacktraces attached to exceptions don't solve this problem, as they
don't provide you information that is crucial to the problem
investigation, namely the input and the state of the program when the
error occurred.

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

No, I'm talking from perspective of both, as I'm both a library user and
a public API implementer.

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

Quite the opposite. What you're suggesting is a recipe for a system
vulnerable to DoS. Yes, you would log the error, and you would possibly
log multiple times before throttling. But you do want to dispatch errors
efficiently to have some time for processing valid data or requests.

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

I'm opposed to doing this *implicitly* and *by default*. On the
contrary, I would welcome the feature if it was defined as explicitly
requested by the user.

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

Most types that you'd want to attach won't be polymorphic.

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

And why would you do that? I mean, unless you do literally have multiple
exceptions that you want to chain together, why would you want to wrap
your data, like std::stacktrace for example, into another exception
type, just to use it with std::nested_exception? Which is not designed
to work as a container of arbitrary data in the first place to boot?

No, there's no need to pull an owl on a globe since boost::exception can
already be a base class for an exception, and provides the necessary
infrastructure for adding and extracting the attached data.

Received on 2021-04-28 14:22:59