LEAF has been refactored, it now has a much simplified error handling interface

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
LEAF is a C++11 error handling library for use in low-latency environments.

I'm looking for a Boost Review Manager. Any one of you good people willing
to volunteer? :)

https://zajo.github.io/leaf/

Features:

-Header-only, no dependencies.

- No dynamic memory allocations.

- Any error-related object of any movable type is efficiently delivered to
the correct error handler.

- Compatible with std::error_code, errno and any other error code type.

- Support for multi-thread programming.

- Can be used with or without exception handling.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
On Wed, Jan 16, 2019 at 11:51 AM Emil Dotchevski via Boost
<[hidden email]> wrote:
>
> LEAF is a C++11 error handling library for use in low-latency environments.
>
> - Can be used with or without exception handling.
>

Is it possible to have a common error handling logic for both error
codes and exceptions?
Something like:

leaf::result<T> function_that_may_throw();
...
leaf::handle_all(
    [i]() -> leaf::result<int> {
        LEAF_AUTO(r, leaf::exception_to_result<std::exception>([] {
function_that_may_throw(); }));
        ...
        return 0;
    },
    [](std::exception const &e) {
        // Handle exception(s)
        return 1;
    },
    [](custom_error_code ec) {
        // Handle error code(s)
        return 2;
    })

The attached program shows that once leaf::exception_to_result() is
used, only the exceptions reach the handlers in `handle_all`.


Also, is there a way to access the "what" message of the exceptions in
the error handlers, instead of just "std::exception"?

Thanks,
Sorin


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

leaf_test.cpp (3K) Download Attachment
output.txt (262 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
On Mon, Jan 21, 2019 at 6:14 PM Sorin Fetche via Boost <
[hidden email]> wrote:
>
> On Wed, Jan 16, 2019 at 11:51 AM Emil Dotchevski via Boost
> <[hidden email]> wrote:
> >
> > LEAF is a C++11 error handling library for use in low-latency
environments.

> >
> > - Can be used with or without exception handling.
> >
>
> Is it possible to have a common error handling logic for both error
> codes and exceptions?
> Something like:
>
> leaf::result<T> function_that_may_throw();
> ...
> leaf::handle_all(
>     [i]() -> leaf::result<int> {
>         LEAF_AUTO(r, leaf::exception_to_result<std::exception>([] {
> function_that_may_throw(); }));
>         ...
>         return 0;
>     },
>     [](std::exception const &e) {
>         // Handle exception(s)
>         return 1;
>     },
>     [](custom_error_code ec) {
>         // Handle error code(s)
>         return 2;
>     })
>
> The attached program shows that once leaf::exception_to_result() is
> used, only the exceptions reach the handlers in `handle_all`.

You found a bug, thank you. I had not tested exception_to_result with a
function that returns result<T>, and it incorrectly deduced its return type
as result<result<T>>. I've fixed this in my current working branch,
"feature/error_code", feel free to use that for testing. This new
refactoring pass will make it to "master" in a day probably.

However you're raising an interesting question, should I add explicit
support for handling errors from functions which return result<T> but may
also throw? I don't like exception_to_result very much, I think it can be
useful but the slicing feels a little dangerous. I'll make leaf::try_
detect when the passed try_block returns a result<T>, and handle errors
like leaf::handle_some, in addition to catching exceptions. Thoughts?

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
Using try_ will now handle result<T> errors as well, see
https://github.com/zajo/leaf/blob/feature/error_code/test/try_exception_and_result_test.cpp
.

With this, in your example instead of handle_all you can just use try_:

leaf::try_(
  [i]() -> leaf::result<int> {
      LEAF_AUTO(r, f2(i));
      (void) r;
      return 0;
  },
  //handlers...
);

The only difference is that in case f2 does not throw, try_ is not
equivalent to handle_all but to handle_some, so the result<int> that will
pop out of try_ should not be discarded (in case it is communicating an
unmatched error).

By the way, in this new iteration handle_some, handle_all and try_ don't
require leaf::result<T>, they can work with any "result" type which defines
.value() and .error(), as long as .error() returns std::error_code or
leaf::error.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
On Tue, Jan 22, 2019 at 2:34 PM Emil Dotchevski via Boost
<[hidden email]> wrote:

>
> Using try_ will now handle result<T> errors as well, see
> https://github.com/zajo/leaf/blob/feature/error_code/test/try_exception_and_result_test.cpp
> .
>
> With this, in your example instead of handle_all you can just use try_:
>
> leaf::try_(
>   [i]() -> leaf::result<int> {
>       LEAF_AUTO(r, f2(i));
>       (void) r;
>       return 0;
>   },
>   //handlers...
Yes, the output of the attached program shows closer to what I had in
mind, if compiled with g++ 7 or 8, release mode.

It has two problems though:
- in release mode the program crashes if I try to print
'verbose_diagnostic_info'
    <snip>
    f2(7)
    Segmentation fault (core dumped)
- in debug mode it crashes right at the beginning
     f2(0)
     leaf_test:
/mnt/d/Sorin/Work/C++/cpp-playground/tmp/include/boost/leaf/detail/teleport.hpp:195:
const std::exception* boost::leaf::error_info::exception() const:
Assertion `has_exception()' failed.
    Aborted (core dumped)

Thanks,
Sorin


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

leaf_test.cpp (4K) Download Attachment
output.txt (466 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
Thank you for testing.

I fixed the first assert, I hadn't tested catch_<> when there is no
exception. It works now.

The diagnostic_info and verbose_diagnostic_info asserts are because those
types were designed to detect attempts to propagate types which no handler
cares about. In your example this doesn't happen, so LEAF doesn't
instantiate diagnostic_info/verbose_diagonstic_info, but it thinks they
must have been instantiated. :)

I'll have to think a bit about this one. I'll probably make these types
wrappers with a pointer inside, which then can be null in this case. In the
mean time, the workaround is to make the handler take these types by const
*, e.g.:

[ ]( leaf::verbose_diagnostic_info const * v )
{
  if(v) std::cerr << *v;
}

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
On Wed, Jan 23, 2019, 2:07 AM Emil Dotchevski via Boost <
[hidden email] wrote:

> Thank you for testing.
>
> I fixed the first assert, I hadn't tested catch_<> when there is no
> exception. It works now.
>

I'm trying to evaluate how the LEAF library would work for transporting
additional information besides error codes when implementing Asio
asynchronous operations.

So, my next test is to capture both errors and exceptions together with
additional info and pass them to the completion handler that will do a
detailed error analysis.

I have two unsuccessful attempts at this on wandbox (together with
leaf/all.hpp merged in a single file by a little Python script):

- with try_:
https://wandbox.org/permlink/t0r0IUntnga6zUUK

- with capture_result:
https://wandbox.org/permlink/OCfmA9WXEgSySE1E

Any suggestions on how to change the test(s) to achieve this?
Would this be a use case supported (efficiently, when no error occurred) by
the LEAF library?

Thanks,
Sorin

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
> I'm trying to evaluate how the LEAF library would work for transporting
> additional information besides error codes when implementing Asio
> asynchronous operations.
>
> So, my next test is to capture both errors and exceptions together with
> additional info and pass them to the completion handler that will do a
> detailed error analysis.

The canonical way to implement this is to customise ASIO's async_result
to pass additional info. See
https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/async_result.html.

As it would be useful for Outcome to document this, I've logged an issue
to https://github.com/ned14/outcome/issues/165 to explain how to
customise ASIO's async_result for outcome::result<T>.

Niall


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
On Wed, Jan 23, 2019 at 11:47 AM Sorin Fetche via Boost <
[hidden email]> wrote:

>
> On Wed, Jan 23, 2019, 2:07 AM Emil Dotchevski via Boost <
> [hidden email] wrote:
>
> > Thank you for testing.
> >
> > I fixed the first assert, I hadn't tested catch_<> when there is no
> > exception. It works now.
> >
>
> I'm trying to evaluate how the LEAF library would work for transporting
> additional information besides error codes when implementing Asio
> asynchronous operations.
>
> So, my next test is to capture both errors and exceptions together with
> additional info and pass them to the completion handler that will do a
> detailed error analysis.
>
> I have two unsuccessful attempts at this on wandbox (together with
> leaf/all.hpp merged in a single file by a little Python script):
>
> - with try_:
> https://wandbox.org/permlink/t0r0IUntnga6zUUK
>
> - with capture_result:
> https://wandbox.org/permlink/OCfmA9WXEgSySE1E
>
> Any suggestions on how to change the test(s) to achieve this?
> Would this be a use case supported (efficiently, when no error occurred)
by
> the LEAF library?

Yes, I should put this in FAQ in the documentation because it is subtle yet
critical.

This is correct:

leaf::try_(
  [ ]() -> leaf::result<T>
  {
    return do_work(); //but usually the do_work body goes here, rather than
a single function call.
  },
  ..... //handlers
);

This is incorrect:

leaf::result<T> r = do_work();
leaf::try_(
  [r]() -> leaf::result<T>
  {
    return r;
  },
  ..... //handlers
);

The problem is that the memory where any errors reported to LEAF by do_work
will be moved to is in the scope of leaf::try_, so anything that happens
before you enter leaf::try_ won't be captured by *this* leaf::try_, but by
some *other* leaf::try_, leaf::handle_some, or leaf::handle_all scope up
the call chain, if any. You can _almost_ forget that leaf::result<T>
doesn't contain any actual error objects... but not quite. :)

If you haven't already, you can look at example/capture_eh.cpp and
example/capture_result.cpp, they demonstrate the canonical way to transport
error objects across threads.

Let me know if I can help more.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
śr., 16 sty 2019 o 20:51 Emil Dotchevski via Boost <[hidden email]>
napisał(a):

> LEAF is a C++11 error handling library for use in low-latency environments.
>
> I'm looking for a Boost Review Manager. Any one of you good people willing
> to volunteer? :)
>
> https://zajo.github.io/leaf/
>
> Features:
>
> -Header-only, no dependencies.
>
> - No dynamic memory allocations.
>
> - Any error-related object of any movable type is efficiently delivered to
> the correct error handler.
>
> - Compatible with std::error_code, errno and any other error code type.
>
> - Support for multi-thread programming.
>
> - Can be used with or without exception handling.
>

Hi Emil,
In the docs we can see that one of the requirements for result<T> is that
"T must be movable, and its move constructor may not throw."

This seems to preclude guard-like types that are not movable to be returned
from functions via result<T>. Like std::mutex. When a wrapper type is
returned by value there is no need to move the internally stored T. At east
in C++17. In principle, a wrapper should not require the wrapped type to be
movable. Types like boost::optional or outcome::result do not have this
requirement. They provide an "in-place" constructor for this purpose. Is it
possible to lift the requirement in leaf::result?

Also, the part "move constructor may not throw" is a bit ambiguous. It
could mean:
1. The program will not compile unless T's move constructor is annotated as
`noexcept` or `throw()`.
2. If a move constructor of T is invoked and it ends by throwing an
exception the behavior of the program is undefined.

IOW, can I pass types potentially throwing from move constructors (like
std::list<int>) at my own risk or is it statically prevented?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: LEAF has been refactored, it now has a much simplified error handling interface

Boost - Dev mailing list
On Tue, Jan 29, 2019 at 1:36 AM Andrzej Krzemienski via Boost <
[hidden email]> wrote:
>
> śr., 16 sty 2019 o 20:51 Emil Dotchevski via Boost <[hidden email]>
> napisał(a):
>
> > LEAF is a C++11 error handling library for use in low-latency
environments.
> >
> > I'm looking for a Boost Review Manager. Any one of you good people
willing

> > to volunteer? :)
> >
> > https://zajo.github.io/leaf/
> >
> > Features:
> >
> > -Header-only, no dependencies.
> >
> > - No dynamic memory allocations.
> >
> > - Any error-related object of any movable type is efficiently delivered
to

> > the correct error handler.
> >
> > - Compatible with std::error_code, errno and any other error code type.
> >
> > - Support for multi-thread programming.
> >
> > - Can be used with or without exception handling.
> >
>
> Hi Emil,
> In the docs we can see that one of the requirements for result<T> is that
> "T must be movable, and its move constructor may not throw."
>
> This seems to preclude guard-like types that are not movable to be
returned
> from functions via result<T>. Like std::mutex. When a wrapper type is
> returned by value there is no need to move the internally stored T. At
east
> in C++17. In principle, a wrapper should not require the wrapped type to
be
> movable. Types like boost::optional or outcome::result do not have this
> requirement. They provide an "in-place" constructor for this purpose. Is
it
> possible to lift the requirement in leaf::result?
>
> Also, the part "move constructor may not throw" is a bit ambiguous. It
> could mean:
> 1. The program will not compile unless T's move constructor is annotated
as
> `noexcept` or `throw()`.
> 2. If a move constructor of T is invoked and it ends by throwing an
> exception the behavior of the program is undefined.
>
> IOW, can I pass types potentially throwing from move constructors (like
> std::list<int>) at my own risk or is it statically prevented?
>
> Regards,
> &rzej;

Thank you for looking into this. You're right, the documentation is
imprecise. The answer is that leaf::result<T> is a variant<T, int,
std::shared_ptr<>>, so the restrictions on move/copy etc are restricted
only by T (LEAF is exception-safe).

I should also add that in its current iteration (that hasn't yet made it to
master), LEAF can work with 3rd-party result types, and in addition is able
to transport the error associations (e.g. the int) in a std::error_code.
This brings interoperability to a whole new level: if you have a 3rd-party
API able to report a std::error_code in case of errors, LEAF can propagate
any number of error objects through that API.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost