Skip to content

Tony tables showing async_scope_association#49

Draft
ispeters wants to merge 8 commits into
mainfrom
revert_to_r7
Draft

Tony tables showing async_scope_association#49
ispeters wants to merge 8 commits into
mainfrom
revert_to_r7

Conversation

@ispeters

Copy link
Copy Markdown
Owner

The result of our afternoon pairing on what nest's implementation would look like if we brought back async_scope_association.

ispeters added 4 commits May 22, 2025 21:11
The result of our afternoon pairing on what `nest`'s implementation
would look like if we brought back `async_scope_association`.
I *think* this makes _`nest-data`_ and its opstate for both rvalue and
lvalue connections with and without an association.

Things to consider:
 - what if `connect` throws?
 - what if the _`wrap-sender`_'s move-constructor throws?
 - what if the _`wrap-sender`_'s copy-constructor throws?
 - what if the association's copy-constructor throws?
 - what if the association's copy-constructor fails?

Icky things:
 - you *must* call `nest-data::get_assoc` before `nest-data::get_sender`
    - I'm not entirely sure the rvalue case is safe if the sender's
      move-constructor throws

Some of the problems would be solved if _`nest-data`_ stored an
optional<wrap-sender> instead of a possibly-uninitialized wrap-sender.
This diff introduces what I think is a good interface for the
association-enhanced _`nest-data`_. The central change is that
_`nest-data`_ now has this method:

```cpp
using scope_ref = unique_ptr<wrap_sender, deleter>;

pair<assoc_t, scope_ref> release() && noexcept;
```

where _`deleter`_ is a deleter that invokes the destructor on the
to-be-deleted pointer when it's not-null. This gives us a way to move
the association out of the source _`nest-data`_ and a reference to its
sender that we can also move from, with an attached guarantee that the
source sender will have its destructor invoked in all cases.

The one weird thing about this interface is that the returned `pair` is
only valid so long as the source _`nest-data`_ instance still exists
since the `scope_ref`'s destructor will reach into the source instance
to destroy the sender therein, which is clearly not allowed if the
source instance has already been destroyed.
Comment thread asyncscope.md Outdated
Comment thread asyncscope.md Outdated
Comment on lines +3072 to +3106
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = decltype(connect(declval<Sender>(), @_spawn-receiver_@{nullptr}));

@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token) noexcept(...)
: alloc(alloc),
op(connect(std::move(sndr), @_spawn-receiver_@{this})),
token(std::move(token)) {}
void @_run_@() {
if (@_token_@.try_associate())
op.start();
else
@_destroy_@();
}
void @_complete_@() override {
auto token = std::move(this->@_token_@);

@_destroy_@();

token.disassociate();
}

private:
using @_alloc-t_@ = typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;

@_alloc-t_@ alloc;
@_op-t_@ op;
Token @_token_@;

void @_destroy_@() noexcept {
auto alloc = std::move(this->alloc);

allocator_traits<@_alloc-t_@>::destroy(alloc, this);
allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1);
}

@ispeters ispeters May 26, 2025

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

To match below, there are a few things that need cleaning up:

  • more noexcept
  • more expo-only.
Suggested change
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = decltype(connect(declval<Sender>(), @_spawn-receiver_@{nullptr}));
@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token) noexcept(...)
: alloc(alloc),
op(connect(std::move(sndr), @_spawn-receiver_@{this})),
token(std::move(token)) {}
void @_run_@() {
if (@_token_@.try_associate())
op.start();
else
@_destroy_@();
}
void @_complete_@() override {
auto token = std::move(this->@_token_@);
@_destroy_@();
token.disassociate();
}
private:
using @_alloc-t_@ = typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;
@_alloc-t_@ alloc;
@_op-t_@ op;
Token @_token_@;
void @_destroy_@() noexcept {
auto alloc = std::move(this->alloc);
allocator_traits<@_alloc-t_@>::destroy(alloc, this);
allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1);
}
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = connect_result_t<Sender, @_spawn-receiver_@>;
@_spawn-state_@(Alloc a, Sender&& sndr, Token t) noexcept(...)
: @_alloc_@(std::move(a)),
@_op_@(connect(std::move(sndr), @_spawn-receiver_@{this})),
@_token_@(std::move(t)) {}
void @_run_@() {
if (@_token_@.try_associate())
op.start();
else
@_destroy_@();
}
void @_complete_@() noexcept override {
auto t = std::move(@_token_@);
@_destroy_@();
t.disassociate();
}
private:
using @_alloc-t_@ = // exposition only
typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;
@_alloc-t_@ @_alloc_@; // exposition only
@_op-t_@ @_op_@; // exposition only
Token @_token_@; // exposition only
void @_destroy_@() noexcept {
auto a = std::move(@_alloc_@);
allocator_traits<@_alloc-t_@>::destroy(a, this);
allocator_traits<@_alloc-t_@>::deallocate(a, this, 1);
}

Comment thread asyncscope.md Outdated
Comment on lines +3116 to +3150
template <class Alloc, async_scope_token Token, sender Sender>
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = decltype(connect(declval<Sender>(), @_spawn-receiver_@{nullptr}));
using @_assoc-t_@ = remove_cvref_t<decltype(declval<Token&>().try_associate())>;

@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token) noexcept(...)
: alloc(alloc),
op(connect(std::move(sndr), @_spawn-receiver_@{this})),
assoc(token.try_associate()) {}
void @_run_@() {
if (assoc)
op.start();
else
@_destroy_@();
}
void @_complete_@() override {
auto assoc = std::move(this->assoc);
@_destroy_@();
}

private:
using @_alloc-t_@ = typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;

@_alloc-t_@ alloc;
@_op-t_@ op;
@_assoc-t_@ assoc;

void @_destroy_@() noexcept {
auto alloc = std::move(this->alloc);

allocator_traits<@_alloc-t_@>::destroy(alloc, this);
allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1);
}

};

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I few thoughts:

  • I think we can simplify even a little more,
  • we should add some noexcepts to both sides of the comparison, and
  • the members should be in italics and expo-only.
Suggested change
template <class Alloc, async_scope_token Token, sender Sender>
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = decltype(connect(declval<Sender>(), @_spawn-receiver_@{nullptr}));
using @_assoc-t_@ = remove_cvref_t<decltype(declval<Token&>().try_associate())>;
@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token) noexcept(...)
: alloc(alloc),
op(connect(std::move(sndr), @_spawn-receiver_@{this})),
assoc(token.try_associate()) {}
void @_run_@() {
if (assoc)
op.start();
else
@_destroy_@();
}
void @_complete_@() override {
auto assoc = std::move(this->assoc);
@_destroy_@();
}
private:
using @_alloc-t_@ = typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;
@_alloc-t_@ alloc;
@_op-t_@ op;
@_assoc-t_@ assoc;
void @_destroy_@() noexcept {
auto alloc = std::move(this->alloc);
allocator_traits<@_alloc-t_@>::destroy(alloc, this);
allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1);
}
};
template <class Alloc, async_scope_token Token, sender Sender>
struct @_spawn-state_@ : @_spawn-state-base_@ {
using @_op-t_@ = connect_result_t<Sender, @_spawn-receiver_@>;
using @_assoc-t_@ = remove_cvref_t<decltype(declval<Token&>().try_associate())>;
@_spawn-state_@(Alloc a, Sender&& sndr, Token t) noexcept(...)
: @_alloc_@(std::move(a)),
@_op_@(connect(std::move(sndr), @_spawn-receiver_@{this})) {
@_assoc_@ = t.try_associate();
}
void @_run_@() noexcept {
if (@_assoc_@)
@_op_@.start();
else
@_destroy_@();
}
void @_complete_@() noexcept override {
auto a = std::move(@_assoc_@);
@_destroy_@();
}
private:
using @_alloc-t_@ = // exposition only
typename allocator_traits<Alloc>::template rebind_alloc<@_spawn-state_@>;
@_assoc-t_@ @_assoc_@; // exposition only
@_alloc-t_@ @_alloc_@; // exposition only
@_op-t_@ @_op_@; // exposition only
void @_destroy_@() noexcept { // exposition only
auto a = std::move(@_alloc_@);
allocator_traits<@_alloc-t_@>::destroy(a, this);
allocator_traits<@_alloc-t_@>::deallocate(a, this, 1);
}
};

Comment thread asyncscope.md
else
@_destroy_@();
}
void @_complete_@() noexcept(is_nothrow_move_constructible_v<Token>) override {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The token concept requires that tokens be no-throw move-constructible so we can leave this off.

Suggested change
void @_complete_@() noexcept(is_nothrow_move_constructible_v<Token>) override {
void @_complete_@() noexcept override {

Comment thread asyncscope.md
@_assoc_@ = t.try_associate();
}

void @_run_@() {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This can be noexcept because all the constituent ops are. Also, it's expo-only.

Suggested change
void @_run_@() {
void @_run_@() noexcept { // exposition only

Comment thread asyncscope.md
else
@_destroy_@();
}
void @_complete_@() noexcept(is_nothrow_move_constructible_v<@_assoc_t_@>) override {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Same as above, the association has to be no-throw move-constructible, and this is expo-only.

Suggested change
void @_complete_@() noexcept(is_nothrow_move_constructible_v<@_assoc_t_@>) override {
void @_complete_@() noexcept override { // exposition only

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants