Skip to content

Optional Recursive Mutex in Asio Integration#1846

Open
RobertLeahy wants to merge 2 commits intoNVIDIA:mainfrom
RobertLeahy:optional_recursive_mutex_20260214
Open

Optional Recursive Mutex in Asio Integration#1846
RobertLeahy wants to merge 2 commits intoNVIDIA:mainfrom
RobertLeahy:optional_recursive_mutex_20260214

Conversation

@RobertLeahy
Copy link
Contributor

Please do not squash.

Resolves #1781.

Reduces the size of a "frame" by removing the std::unique_lock member
variable and making the "frame" itself the lock guard. This is the lock
management method shown when presenting asioexec::completion_token in
the CppCon 2025 talk "std::execution in Asio Codebases: Adopting Senders
Without a Rewrite."
For general purpose (i.e. potentially multithreaded) use the
asynchronous operations which result when passing the asioexec::
completion_token and ::use_sender completion tokens must use a recursive
mutex internally. However if the user knows that no multithreaded use
will occur this recursive mutex is pure overhead. Provided the
asioexec::thread_unsafe_completion_token and _use_sender completion
tokens which do not make use of a recursive mutex for the aforementioned
use case.
@copy-pr-bot
Copy link

copy-pr-bot bot commented Feb 14, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copy link
Collaborator

@ericniebler ericniebler left a comment

Choose a reason for hiding this comment

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

nit picks

Receiver r_;
asio_impl::cancellation_signal signal_;
std::recursive_mutex m_;
[[no_unique_address]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

in operation states, this should be STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS

Comment on lines +209 to +215
const auto prev = self.frames_->prev_;
self.frames_->prev_ = nullptr;
self.frames_ = prev;
self.m_.unlock();
if (!prev) {
break;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not use std::exchange?

Suggested change
const auto prev = self.frames_->prev_;
self.frames_->prev_ = nullptr;
self.frames_ = prev;
self.m_.unlock();
if (!prev) {
break;
}
self.frames_ = std::exchange(self.frames_->prev_, nullptr);
self.m_.unlock();
if (!self.frames_) {
break;
}

@@ -326,7 +343,7 @@ namespace asioexec {
[&](auto&&... args) {
std::invoke(
Copy link
Collaborator

Choose a reason for hiding this comment

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

STDEXEC::__invoke compiles about ~2x faster than libstdc++'s std::invoke.

Comment on lines +164 to +167
: self_([&]() noexcept {
self.m_.lock();
return &self;
}())
Copy link
Collaborator

Choose a reason for hiding this comment

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

lambdas are expensive at compile time. this could simply be:

Suggested change
: self_([&]() noexcept {
self.m_.lock();
return &self;
}())
: self_((self.m_.lock(), &self))

or if you find that distasteful, you could use a delegating constructor:

  explicit frame_(operation_state_base& self) noexcept
    : frame_(std::unique_lock(self.m_), self) {
  }
private:
  explicit frame_(std::unique_lock<Mutex> lk, operation_state_base& self) noexcept
    : self_(&self) {
    lk.release();
  }

};

template <typename Mutex>
struct t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

please give this a more descriptive name


template <typename Signatures, typename Receiver, typename Allocator>
template <typename Mutex, typename Signatures, typename Receiver, typename Allocator>
requires requires(const Receiver& r) { ::STDEXEC::get_allocator(::STDEXEC::get_env(r)); }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
requires requires(const Receiver& r) { ::STDEXEC::get_allocator(::STDEXEC::get_env(r)); }
requires STDEXEC::__callable<STDEXEC::get_allocator_t, STDEXEC::env_of_t<Receiver>>

explicit sender(Sender) -> sender<Sender>;

template <typename Mutex>
struct t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

same

@ericniebler
Copy link
Collaborator

/ok to test 72c179e

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.

Recursive Mutex in Asio Integration Should Be Optional

2 participants