C++ Logo

std-discussion

Advanced search

Re: Some feedback on scope guards

From: Lee Howes <xrikcus_at_[hidden]>
Date: Thu, 13 Apr 2023 09:42:30 -0700
I don't think co_unhandled_exceptions adds anything does it? Either we
automate storing the thread_local on yield and loading on resume or we
don't. If we do, then the current thread_local query works fine.

If we had a way to *write* to unhandled exceptions we could do this in
coroutine library code and make it QOI for a given coroutine
implementation, and that doesn't have to be coroutine specific:
std::set_uncaught_exceptions(cnt). We can already inject arbitrary state
into the coroutine frame that's hidden from the coroutine author: in folly
we maintain a stack trace for the debugger to walk, as one example.

All that said, uncaught_exceptions is a nasty piece of global state in the
way of the optimizer, and this is a good example of more overhead that
depending on it could create. It might be better to warn about using
scope_fail across a yield point in the same way we need to be better at
warning against use of locks or thread_locals in general. One big advantage
that co_await/co_yield have over fibers is that we know where the yield
points are and can make this logic work.

Lee

On Thu, 13 Apr 2023 at 06:04, Edward Catmur via Std-Discussion <
std-discussion_at_[hidden]> wrote:

>
>
> On Thu, 13 Apr 2023, 06:35 Andrey Semashev via Std-Discussion, <
> std-discussion_at_[hidden]> wrote:
>
>> On 4/13/23 05:16, Edward Catmur wrote:
>> >
>> >
>> > On Wed, 12 Apr 2023 at 15:27, Andrey Semashev via Std-Discussion
>> > <std-discussion_at_[hidden]
>> > <mailto:std-discussion_at_[hidden]>> wrote:
>> >
>> > On 4/12/23 18:53, Edward Catmur wrote:
>> > > On Mon, 10 Apr 2023, 08:57 Andrey Semashev via Std-Discussion,
>> > > <std-discussion_at_[hidden]
>> > <mailto:std-discussion_at_[hidden]>
>> > > <mailto:std-discussion_at_[hidden]
>> > <mailto:std-discussion_at_[hidden]>>> wrote:
>> > >
>> > > I don't think that updating a pointer during a context switch
>> > would be a
>> > > deal breaker. The co_unhandled_exceptions() mechanism only
>> > needs to know
>> > > the current coroutine state of the current thread, it doesn't
>> > need a
>> > > list. If a linked list is needed to be able to return from one
>> > coroutine
>> > > to another then such a list must be already in place.
>> > >
>> > > Now that I think of it, co_unhandled_exceptions() might not
>> > require any
>> > > new pointers. The coroutine already has to keep a pointer to
>> > its state
>> > > somewhere to be able to reference its local variables. I don't
>> > see why
>> > > co_unhandled_exceptions() couldn't use that pointer.
>> > >
>> > > But how can co_unhandled_exceptions() *find* that pointer? The
>> current
>> > > coroutine state is not stored in per thread memory; it's solely
>> on the
>> > > stack. (The linked list that's "already in place" is the stack
>> > itself.)
>> >
>> > The compiler could pass this pointer as a hidden parameter to
>> > co_unhandled_exceptions() based on implementation-specific
>> attribute, or
>> > co_unhandled_exceptions() could be a compiler intrinsic to begin
>> with.
>> > Or, if the pointer is stored in a known fixed location of the parent
>> > stack frame, co_unhandled_exceptions() could extract it from there.
>> All
>> > this is to say this task doesn't seem impossible.
>> >
>> > > That said, you might be able to find it when unwinding through a
>> > > coroutine stack frame. I'm still thinking about it, but it seems
>> > like it
>> > > should work. In that case you've only got overhead added to
>> coroutine
>> > > creation, coroutine footprint, unwinding through a coroutine, and
>> > you've
>> > > doubled the stack footprint and memory access of the
>> success/failure
>> > > scope guards, compared to a C++17 implementation using
>> > > unhandled_exceptions only.
>> >
>> > Not sure what you mean by doubling the stack footprint and memory
>> access
>> > of the scope guards. The size of the scope guards didn't change.
>> >
>> > But *how* do scope_success and scope_failure access the current
>> > coroutine's co_uncaught_exceptions() counter? Obviously it's not a
>> > problem if they're complete automatic objects of the coroutine frame,
>> > but otherwise? e.g. if they're subobjects or dynamically allocated? I'd
>> > think this would require stack walking (non-destructive unwinding),
>> > which is hugely expensive, or constructing a thread-local linked list of
>> > coroutines, which has continuous overhead.
>>
>> I think you are confusing the storage used for the scope guard object
>> with the stack frame. co_uncaught_exceptions() doesn't need or use the
>> scope guard object, it doesn't care where it is allocated or whether it
>> exists at all. What co_uncaught_exceptions() *may* need is its caller's
>> stack frame, if it is implemented in such a way that it uses the stack
>> frame to obtain the pointer to the coroutine state (or to discover
>> whether it is a coroutine at all).
>>
>> Now that I think of it more, perhaps using the caller's stack frame is
>> not a viable idea after all, since the immediate caller of
>> co_uncaught_exceptions() may not be a coroutine, but a normal function
>> called within a coroutine. However, as I noted earlier, there are other
>> possible implementations, including not involving TLS. But using TLS to
>> store a pointer to the current coroutine state would be the simplest
>> solution, of course.
>>
>
> Right. But that would require a linked list, since otherwise there's no
> way to restore the previous value when a coroutine suspends or returns.
>
> > And what for scope_success and scope_failure that aren't constructed
>> > from within coroutines at all? How far do they look to determine to use
>> > uncaught_exceptions() instead?
>>
>> The scope guard would always use co_uncaught_exceptions(). When called
>> not within a coroutine (meaning, there are no coroutines higher up the
>> stack), co_uncaught_exceptions() would be equivalent to the
>> uncaught_exceptions() we currently have.
>>
>
> And how does co_uncaught_exceptions() know that there are no coroutines
> higher up the stack? It sounds like a thread local linked list is a
> necessity.
>
> > And what if a dynamically allocated scope_success/scope_failure is
>> > constructed in one coroutine, but then has ownership transferred to
>> > another stack (which may or may not be a coroutine)?
>>
>> In that case, the cached number of uncaught exceptions may become not
>> actual, depending on what kind of transfer you make. Yes, this may break
>> scope_success/scope_fail, unfortunately. I'd be willing to mark such use
>> of scope_success/scope_fail UB, as this is arguably a case of the user
>> explicitly doing something incompatible with those scope guards' design,
>> as opposed to using scope guards in the conventional way within
>> coroutines.
>>
>
> Right, so this design is still fairly fragile. Which behaviors would you
> define, beyond the absolute minimum of scope guards as complete automatic
> objects?
>
> > > When I say "the destruction of the scope guard is caused by a
>> > stack
>> > > unwinding procedure" I mean literally that the stack unwinding
>> > procedure
>> > > is present higher up in the call stack from the scope guard
>> > destructor.
>> > > I'm choosing this criteria because that's what makes most
>> > sense to me in
>> > > relation to the expected behavior of the scope guards.
>> > >
>> > > That doesn't work. Having an unwind caused destructor above in the
>> > stack
>> > > from a scope guard destructor does not always mean that the scope
>> > guard
>> > > is in a failure state. It feels like we're going round in circles
>> > here.
>> >
>> > A scope guard destructor would conceal this information by setting
>> the
>> > uncaught exceptions counter to zero, as described below.
>> >
>> > Is this a revision to your design, an extension, or an alternate design?
>> > I'm having trouble keeping track.
>>
>> No, it's the same design. Or you could say it's an evolution of the same
>> design during the discussion. It's all about co_uncaught_exceptions()
>> and co_set_uncaught_exceptions(), the latter being used for concealing
>> the pending exceptions from the scope guard action, as I have shown
>> earlier.
>>
>
> Is this a necessary part of the design or can it be omitted? Which
> scenarios does it help with?
>
>> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2023-04-13 16:42:44