Re: [boost] [scope] scope_fail is unimplementable

czw., 6 kwi 2023 o 23:51 Andrey Semashev via Boost
On 4/6/23 21:26, Andrzej Krzemienski via Boost wrote:
Hi Everyone, It is my understanding that the reason scope_fail was left to die in the C++ Extensions for Library Fundamentals v3 was that it is not implementable.
I'm not sure what you mean in the "left to die" part. Do you mean that these facilities are being deprecated?
There are two motivations for putting something into a TS. One is to encourage vendors to implement the feature and gather usage and deployment experience. The other is to postpone the addition of the feature or to politely say that it is not wanted in the Standard. I *believe* this is the case with the subset of scope guards. I have seen no movement towards moving scope_fail into the Standard. and not moving them into the standard in reality means abandoning them.
One cannot detect if the scope is being left due to an exception thrown from within the scope. Observing the number of uncaught exceptions in the constructor and in the destructor will not work, given that we now have coroutines that can be suspended and then resumed in another thread (with a different number of uncaught exceptions), or even in the same thread but at some later point, when the number of uncaught exceptions has changed.
The enclosed example illustrates how a scope_fail fails to call the callback when an exception is thrown from a coroutine. It uses Lewis Baker's CppCoro implementation of a generator.
The only reliable way to call a callback when the scope is left due to an exception is through using the explicit call to `deactivate()` function. It is more verbose, but reliable.
I must say I have no experience with coroutines, but my understanding is that they are basically incompatible with any mechanism that relies on thread-specific state, including the uncaught exceptions counter.
Indeed. Note that the example I provided uses just one thread. The problem is the suspension itself.
Thus I wouldn't say scope_success/scope_fail are unimplementable - they clearly are - but that they are incompatible with coroutines. That is, these scope guards will work as expected as long as you don't switch coroutines within the guarded scope.
Maybe this is just a question of the choice of words. But the docs do not present the situation in this way. "Although it is possible to specify arbitrary condition function objects, typically scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe... invokes its action when the scope is left normally (i.e. not via an exception) and scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... should typically be used to handle errors, including exceptions." This guarantee (when scope is exited not normally) cannot be satisfied in general. One cannot use it in a coroutine: directly or indirectly. One might not even know the context. "By default, scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe... will invoke its action if it is destroyed normally, scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... - if it is destroyed due to an exception being thrown." (BTW, The synopsis in https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... does not indicate that there is any default Cond.) This is not true when a scope guard is used across coroutine suspensions: by default [...] scope_fail is not destroyed due to exception being thrown. The reality is, it is destroyed based on the comparison of two measurements of the number of uncaught exceptions, potentially measured in different threads.
I admit this is a notable limitation, and I will document it. But I do not consider this limitation fatal, as there is plenty code out there that doesn't use coroutines - I'd even say, much more code than that does.
This is one way of looking at it: that the problem is rare. A different way is to say that if something works in 99% of the cases and fails only in 1%, we have a dangerous situation, as we are unprepared when the 1% case happens.
Also, I forgot to mention that Boost.Scope's scope_success/scope_fail support custom failure predicates which may not rely on the exception state at all.
Can it be used to fix the coroutine case? (That is to detect that the scope is left due to an exception being thrown?) Regards, &rzej;

On 4/7/23 15:55, Andrzej Krzemienski wrote:
czw., 6 kwi 2023 o 23:51 Andrey Semashev via Boost
mailto:boost@lists.boost.org> napisał(a): On 4/6/23 21:26, Andrzej Krzemienski via Boost wrote: > Hi Everyone, > It is my understanding that the reason scope_fail was left to die in the > C++ Extensions for Library Fundamentals v3 was that it is not > implementable.
I'm not sure what you mean in the "left to die" part. Do you mean that these facilities are being deprecated?
There are two motivations for putting something into a TS. One is to encourage vendors to implement the feature and gather usage and deployment experience. The other is to postpone the addition of the feature or to politely say that it is not wanted in the Standard. I *believe* this is the case with the subset of scope guards. I have seen no movement towards moving scope_fail into the Standard. and not moving them into the standard in reality means abandoning them.
My understanding is that the TS exist to gather practical usage experience in real world with the components before including (or not including) them into the main standard. In fact, this very thread is a piece of this real world feedback. I see no point in including something into a TS only to abandon it afterwards. That said, I have no close knowledge of the standard committee processes and may be wrong. In any case, the problem you highlighted may well be a reason for reluctance to include scope_success/scope_fail into the standard, at least not with the current wording that is given in the TS. But I think the components can be defined differently, with coroutines in mind. That new definition would not use uncaught_exceptions() as a criteria for detecting failure but some other, possibly "implementation-defined" or otherwise undisclosed mechanism that allows to implement the correct semantics wrt. coroutines.
I must say I have no experience with coroutines, but my understanding is that they are basically incompatible with any mechanism that relies on thread-specific state, including the uncaught exceptions counter.
Indeed. Note that the example I provided uses just one thread. The problem is the suspension itself.
The problem is that the uncaught exceptions counter does not correspond to the current coroutine, but to the current thread.
Thus I wouldn't say scope_success/scope_fail are unimplementable - they clearly are - but that they are incompatible with coroutines. That is, these scope guards will work as expected as long as you don't switch coroutines within the guarded scope.
Maybe this is just a question of the choice of words. But the docs do not present the situation in this way.
"Although it is possible to specify arbitrary condition function objects, typically |scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe...| invokes its action when the scope is left normally (i.e. not via an exception) and |scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail....| should typically be used to handle errors, including exceptions."
This guarantee (when scope is exited not normally) cannot be satisfied in general. One cannot use it in a coroutine: directly or indirectly. One might not even know the context.
"By default, |scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe...| will invoke its action if it is destroyed normally, |scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail....| - if it is destroyed due to an exception being thrown."
Note the "typically". :) In this documentation I tried to describe these facilities in more or less simple language, describing the most typical use cases. For this simplicity, I had to omit some formalities and corner cases that would detract the reader from the main purpose and intended use case of the components. Perhaps, I should improve the wording, but I would still like the docs to be easily readable. And, as I admitted earlier, I completely forgot about coroutines. I will add a note about coroutenes in relation to the default failure condition used by scope_success/scope_fail.
(BTW, The synopsis in https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... does not indicate that there is any default Cond.)
Hmm, for some reason Doxygen removed the default template arguments for scope_success/scope_fail. I'll see if I can fix this. Thanks for noticing.
This is not true when a scope guard is used across coroutine suspensions: by default [...] scope_fail is not destroyed due to exception being thrown. The reality is, it is destroyed based on the comparison of two measurements of the number of uncaught exceptions, potentially measured in different threads.
I admit this is a notable limitation, and I will document it. But I do not consider this limitation fatal, as there is plenty code out there that doesn't use coroutines - I'd even say, much more code than that does.
This is one way of looking at it: that the problem is rare. A different way is to say that if something works in 99% of the cases and fails only in 1%, we have a dangerous situation, as we are unprepared when the 1% case happens.
Well, every technology has its limits. And if a utility is not usable in 1% of use cases, I don't see why it shouldn't exist to benefit the rest 99%.
Also, I forgot to mention that Boost.Scope's scope_success/scope_fail support custom failure predicates which may not rely on the exception state at all.
Can it be used to fix the coroutine case? (That is to detect that the scope is left due to an exception being thrown?)
To my knowledge, not in pure C++. With current real coroutine implementations? I don't know, probably not either. You could use this feature with your own error reporting mechanism, like checking an error code. I could imagine a mechanism that would capture the unhandled exceptions count upon entering a coroutine. Throwing and catching exceptions from the coroutine would then update this cached counter the same way as it does for the thread-specific counter. Resuming the coroutine would maintain its own counter regardless of the caller's state. Then scope_success/scope_fail could use some internal API to use this counter (say, co_unhandled_exceptions()) instead of unhandled_exceptions() to detect a thrown exception.

On 4/7/23 18:42, Andrey Semashev wrote:
On 4/7/23 15:55, Andrzej Krzemienski wrote:
czw., 6 kwi 2023 o 23:51 Andrey Semashev via Boost
mailto:boost@lists.boost.org> napisał(a): Thus I wouldn't say scope_success/scope_fail are unimplementable - they clearly are - but that they are incompatible with coroutines. That is, these scope guards will work as expected as long as you don't switch coroutines within the guarded scope.
Maybe this is just a question of the choice of words. But the docs do not present the situation in this way.
"Although it is possible to specify arbitrary condition function objects, typically |scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe...| invokes its action when the scope is left normally (i.e. not via an exception) and |scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail....| should typically be used to handle errors, including exceptions."
This guarantee (when scope is exited not normally) cannot be satisfied in general. One cannot use it in a coroutine: directly or indirectly. One might not even know the context.
"By default, |scope_success https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_succe...| will invoke its action if it is destroyed normally, |scope_fail https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail....| - if it is destroyed due to an exception being thrown."
Note the "typically". :) In this documentation I tried to describe these facilities in more or less simple language, describing the most typical use cases. For this simplicity, I had to omit some formalities and corner cases that would detract the reader from the main purpose and intended use case of the components. Perhaps, I should improve the wording, but I would still like the docs to be easily readable.
And, as I admitted earlier, I completely forgot about coroutines. I will add a note about coroutenes in relation to the default failure condition used by scope_success/scope_fail.
(BTW, The synopsis in https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... https://lastique.github.io/scope/libs/scope/doc/html/boost/scope/scope_fail.... does not indicate that there is any default Cond.)
Hmm, for some reason Doxygen removed the default template arguments for scope_success/scope_fail. I'll see if I can fix this. Thanks for noticing.
I have updated the docs to fix those issues and added the note re. coroutines. I've also improved wording so that the general discussion does not focus on exceptions as much but rather failure and non-failure conditions.
participants (2)
-
Andrey Semashev
-
Andrzej Krzemienski