Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scope_exit utilities #3840

Closed
wants to merge 3 commits into from
Closed

Conversation

HowardHinnant
Copy link
Contributor

High Level Overview of Change

Add scope_exit utilities from Library Fundamental, Version 3

Basic design of idea: https://www.youtube.com/watch?v=WjTrfoiB0MQ
Specification:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4873.html#scopeguard

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

Test Plan

Unit tests added.

Copy link
Collaborator

@seelabs seelabs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks great! Left some minor comments, but nothing that should hold this up. Thanks for coding this up; as you know I'll have a good place to use it already.

scope_exit(scope_exit&& rhs) noexcept(
std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
: exit_function_{std::forward<EF>(rhs.exit_function_)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since rhs is an rvalue, not a universal ref, shouldn't this be std::move, not std::forward?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forward is used because EF is allowed to be an lvalue reference to a functor. So this is a move if EF is an object, and a reference binding if EF is an lvalue reference. With std::move this would fail to compile if EF is a lvalue reference.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks for the explanation.

std::enable_if_t<
!std::is_same_v<std::remove_cv_t<EFP>, scope_exit> &&
std::is_constructible_v<EF, EFP>>* =
0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame the enable_if has to be a parameter - which hides it away from where we see it in the rest of the rippled code. But I think we're forced to do that because this is a constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. And requires isn't in our toolbox yet. (requires is going to be great...)

try : exit_function_
{
std::forward<EFP>(f)
}
Copy link
Collaborator

@seelabs seelabs May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have left the try block off myself (which really means leaving off this whole overload). The idea that the move constructor for the function would throw just isn't going to happen in rippled code. But I know this is meant to mimic standard library code, not ripple code. No change needed. I'll also note I think this is the first time I've seen function try block used in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also the first time I've used a function try block. :-)

@scottschurr
Copy link
Collaborator

It looks like all of the gcc builds are failing CI. I didn't look at them all, but one of the failing gcc builds says this:

1567 In file included from /home/travis/build/ripple/rippled/src/test/basics/scope_test.cpp:20,
1568                  from CMakeFiles/rippled.dir/Unity/unity_40.cxx:6:
1569 ../../src/ripple/basics/scope.h: In instantiation of 'ripple::scope_exit<EF>::scope_exit(EFP&&, std::enable_if_t<((! is_same_v<typename std::remove_cv<_BoundArg>::type, ripple::scope_exit<EF> >) && is_constructible_v<EF, EFP>)>*) [with EFP = ripple::test::scope_test::test_scope_exit()::<lambda()>; EF = ripple::test::scope_test::test_scope_exit()::<lambda()>; std::enable_if_t<((! is_same_v<typename std::remove_cv<_BoundArg>::type, ripple::scope_exit<EF> >) && is_constructible_v<EF, EFP>)> = void]':
1570 /home/travis/build/ripple/rippled/src/test/basics/scope_test.cpp:35:43:   required from here
1571 ../../src/ripple/basics/scope.h:75:9: error: throw will always call terminate() [-Werror=terminate]
1572          throw;
1573          ^~~~~

A quick scan of some of the other gcc builds showed a similar complaint.

The Windows builds show a similar complaint:

552 ..\src\ripple/basics/scope.h(75): error C2220: warning treated as error - no 'object' file generated
553 ..\src\ripple/basics/scope.h(75): warning C4297: 'ripple::scope_exit<ripple::test::scope_test::test_scope_exit::<lambda_9e24fdaf89f344e157db56bfc6f19973>>::scope_exit': function assumed not to throw an exception but does
554 ..\src\ripple/basics/scope.h(75): note: __declspec(nothrow), throw(), noexcept(true), or noexcept was specified on the function
555 C:/Users/travis/build/ripple/rippled/src/test/basics/scope_test.cpp(35): note: see reference to function template instantiation 'ripple::scope_exit<ripple::test::scope_test::test_scope_exit::<lambda_9e24fdaf89f344e157db56bfc6f19973>>::scope_exit<ripple::test::scope_test::test_scope_exit::<lambda_9e24fdaf89f344e157db56bfc6f19973>>(EFP &&,void *) noexcept' being compiled
556         with
557         [
558             EFP=ripple::test::scope_test::test_scope_exit::<lambda_9e24fdaf89f344e157db56bfc6f19973>
559         ]

@HowardHinnant
Copy link
Contributor Author

Ah. Yes, when the functor construction can't throw an exception the function try catch is superfluous. And instead of optimizing it away the compiler is whining about it. :-\

The only way I see to fix this is to create two constructors: one constrained when noexcept is false and the other when noexcept is true, and only put the try/catch on the noexcept false version. And that's just ugly enough for me to balk.

I think instead the best path forward is to eliminate the try/catch, and static_assert that the construction is noexcept. I'll test that a bit before I push it.

       of scope_exit and scope_fail.
@HowardHinnant
Copy link
Contributor Author

HowardHinnant commented May 7, 2021

Ok, this is working on clang. This means (for example), one can not construct scope_exit with an lvalue std::function because that might throw (error caught at compile-time). But you can construct from an rvalue std::function. Though it would be poor form to construct the std::function within the scope of the handled resource, because that construction can throw too.

Best use case is to use a lambda that has a noexcept constructor and move constructor.

Copy link
Collaborator

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks great. I'm doubtful that anyone at Ripple will use scope_fail or scope_success, but since we're exercising things for the committee I think it's okay to include them.

I left a few notes for you to consider on the unit tests. But they are at your discretion. Thanks for doing this!

// permitted to throw an exception. This was done because some compilers
// did not like the superfluous try/catch in the common instantiations
// where the construction was noexcept. Instead a static_assert is used
// to enforce this restriction.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice comment. Thanks.

}
BEAST_EXPECT(i == 1);
{
scope_exit x{[&i]() { i = 3; }};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you think of changing this test to i += 2? That would demonstrate that exactly one of the two scope_exit objects executed.

BEAST_DEFINE_TESTSUITE(scope, ripple_basics, ripple);

} // namespace test
} // namespace ripple
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no newline at the end of scope_test.cpp. Might be nice to add one.

}
}
BEAST_EXPECT(i == 5);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. One other note that I'd apparently failed to save. All of the tests currently use lambdas. What would you think of testing a bog standard function as well? Perhaps something like...

struct scope_test : beast::unit_test::suite
{
    inline static int testVal = 0;

    // static function to execute in tests
    static void
    scopeFunc()
    {
        testVal = 10;
    }
...
        testVal = 0;
        {
            scope_exit x{scopeFunc};
        }
        BEAST_EXPECT(testVal == 10);

That works, BTW. It just seems like we should exercise it in the unit tests. Your call...

Copy link
Collaborator

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@HowardHinnant HowardHinnant added the Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required. label May 13, 2021
@ximinez ximinez mentioned this pull request May 14, 2021
3 tasks
This was referenced Jun 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants