Skip to content

Commit 72c179e

Browse files
committed
asioexec::thread_unsafe_completion_token & _use_sender
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.
1 parent 28535c6 commit 72c179e

4 files changed

Lines changed: 102 additions & 75 deletions

File tree

include/asioexec/completion_token.hpp

Lines changed: 81 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ namespace asioexec {
136136
::STDEXEC::set_stopped_t()
137137
>;
138138

139-
template <typename, typename>
140-
class completion_handler;
141-
142-
template <typename Signatures, typename Receiver>
139+
template <typename Mutex, typename Signatures, typename Receiver>
143140
struct operation_state_base {
144141
class frame_;
145142

@@ -153,7 +150,8 @@ namespace asioexec {
153150

154151
Receiver r_;
155152
asio_impl::cancellation_signal signal_;
156-
std::recursive_mutex m_;
153+
[[no_unique_address]]
154+
Mutex m_;
157155
frame_* frames_{nullptr};
158156
std::exception_ptr ex_;
159157
bool abandoned_{false};
@@ -251,11 +249,12 @@ namespace asioexec {
251249
callback_;
252250
};
253251

254-
template <typename Signatures, typename Receiver>
252+
template <typename Mutex, typename Signatures, typename Receiver>
255253
class completion_handler {
256-
operation_state_base<Signatures, Receiver>* self_;
254+
using operation_state_type_ = operation_state_base<Mutex, Signatures, Receiver>;
255+
operation_state_type_* self_;
257256
public:
258-
explicit completion_handler(operation_state_base<Signatures, Receiver>& self) noexcept
257+
explicit completion_handler(operation_state_type_& self) noexcept
259258
: self_(&self) {
260259
}
261260

@@ -270,7 +269,7 @@ namespace asioexec {
270269
// When this goes out of scope it might send set stopped or set error, or
271270
// it might defer that to the executor frames above us on the call stack
272271
// (if any)
273-
const typename operation_state_base<Signatures, Receiver>::frame_ frame(*self_);
272+
const typename operation_state_type_::frame_ frame(*self_);
274273
self_->abandoned_ = true;
275274
}
276275
}
@@ -309,15 +308,20 @@ namespace asioexec {
309308
return self_->signal_.slot();
310309
}
311310

312-
operation_state_base<Signatures, Receiver>& state() const noexcept {
311+
operation_state_type_& state() const noexcept {
313312
STDEXEC_ASSERT(self_);
314313
return *self_;
315314
}
316315
};
317316

318-
template <typename Signatures, typename Receiver, typename Initiation, typename Args>
319-
class operation_state : operation_state_base<Signatures, Receiver> {
320-
using base_ = operation_state_base<Signatures, Receiver>;
317+
template <
318+
typename Mutex,
319+
typename Signatures,
320+
typename Receiver,
321+
typename Initiation,
322+
typename Args>
323+
class operation_state : operation_state_base<Mutex, Signatures, Receiver> {
324+
using base_ = operation_state_base<Mutex, Signatures, Receiver>;
321325
Initiation init_;
322326
Args args_;
323327
public:
@@ -339,7 +343,7 @@ namespace asioexec {
339343
[&](auto&&... args) {
340344
std::invoke(
341345
static_cast<Initiation&&>(init_),
342-
completion_handler<Signatures, Receiver>(*this),
346+
completion_handler<Mutex, Signatures, Receiver>(*this),
343347
static_cast<decltype(args)&&>(args)...);
344348
},
345349
std::move(args_));
@@ -362,9 +366,12 @@ namespace asioexec {
362366
}
363367
};
364368

365-
template <typename Signatures, typename Initiation, typename... Args>
369+
template <typename Mutex, typename Signatures, typename Initiation, typename... Args>
366370
class sender {
367371
using args_type_ = std::tuple<std::decay_t<Args>...>;
372+
template <typename Receiver>
373+
using operation_state_type_ =
374+
operation_state<Mutex, Signatures, std::remove_cvref_t<Receiver>, Initiation, args_type_>;
368375
public:
369376
using sender_concept = ::STDEXEC::sender_t;
370377

@@ -388,32 +395,25 @@ namespace asioexec {
388395
template <typename Receiver>
389396
requires ::STDEXEC::receiver_of<
390397
std::remove_cvref_t<Receiver>,
391-
::STDEXEC::completion_signatures_of_t<const sender&, ::STDEXEC::env_of_t<Receiver>>
392-
>
393-
constexpr auto connect(Receiver&& receiver) const & noexcept(
394-
std::is_nothrow_constructible_v<
395-
operation_state<Signatures, std::remove_cvref_t<Receiver>, Initiation, args_type_>,
396-
Receiver,
397-
const Initiation&,
398-
const args_type_&
399-
>) {
400-
return operation_state<Signatures, std::remove_cvref_t<Receiver>, Initiation, args_type_>(
401-
static_cast<Receiver&&>(receiver), init_, args_);
398+
::STDEXEC::completion_signatures_of_t<const sender&, ::STDEXEC::env_of_t<Receiver>>>
399+
constexpr auto connect(Receiver&& receiver) const & noexcept(std::is_nothrow_constructible_v<
400+
operation_state_type_<Receiver>,
401+
Receiver,
402+
const Initiation&,
403+
const args_type_&>) {
404+
return operation_state_type_<Receiver>(static_cast<Receiver&&>(receiver), init_, args_);
402405
}
403406

404407
template <typename Receiver>
405408
requires ::STDEXEC::receiver_of<
406409
std::remove_cvref_t<Receiver>,
407-
::STDEXEC::completion_signatures_of_t<sender, ::STDEXEC::env_of_t<Receiver>>
408-
>
409-
constexpr auto connect(Receiver&& receiver) && noexcept(
410-
std::is_nothrow_constructible_v<
411-
operation_state<Signatures, std::remove_cvref_t<Receiver>, Initiation, args_type_>,
412-
Receiver,
413-
Initiation,
414-
args_type_
415-
>) {
416-
return operation_state<Signatures, std::remove_cvref_t<Receiver>, Initiation, args_type_>(
410+
::STDEXEC::completion_signatures_of_t<sender, ::STDEXEC::env_of_t<Receiver>>>
411+
constexpr auto connect(Receiver&& receiver) && noexcept(std::is_nothrow_constructible_v<
412+
operation_state_type_<Receiver>,
413+
Receiver,
414+
Initiation,
415+
args_type_>) {
416+
return operation_state_type_<Receiver>(
417417
static_cast<Receiver&&>(receiver),
418418
static_cast<Initiation&&>(init_),
419419
static_cast<args_type_&&>(args_));
@@ -423,9 +423,10 @@ namespace asioexec {
423423
args_type_ args_;
424424
};
425425

426-
template <typename Signatures, typename Receiver, typename Executor>
426+
template <typename Mutex, typename Signatures, typename Receiver, typename Executor>
427427
class executor {
428-
operation_state_base<Signatures, Receiver>& self_;
428+
using operation_state_type_ = operation_state_base<Mutex, Signatures, Receiver>;
429+
operation_state_type_& self_;
429430
Executor ex_;
430431

431432
template <typename F>
@@ -435,9 +436,7 @@ namespace asioexec {
435436
};
436437
}
437438
public:
438-
constexpr explicit executor(
439-
operation_state_base<Signatures, Receiver>& self,
440-
const Executor& ex) noexcept
439+
constexpr explicit executor(operation_state_type_& self, const Executor& ex) noexcept
441440
: self_(self)
442441
, ex_(ex) {
443442
}
@@ -456,7 +455,7 @@ namespace asioexec {
456455
}
457456
constexpr decltype(auto) prefer(Args&&... args) const noexcept {
458457
const auto ex = asio_impl::prefer(ex_, static_cast<Args&&>(args)...);
459-
return executor<Signatures, Receiver, std::remove_cvref_t<decltype(ex)>>(self_, ex);
458+
return executor<Mutex, Signatures, Receiver, std::remove_cvref_t<decltype(ex)>>(self_, ex);
460459
}
461460

462461
template <typename... Args>
@@ -465,7 +464,7 @@ namespace asioexec {
465464
}
466465
constexpr decltype(auto) require(Args&&... args) const noexcept {
467466
const auto ex = asio_impl::require(ex_, static_cast<Args&&>(args)...);
468-
return executor<Signatures, Receiver, std::remove_cvref_t<decltype(ex)>>(self_, ex);
467+
return executor<Mutex, Signatures, Receiver, std::remove_cvref_t<decltype(ex)>>(self_, ex);
469468
}
470469

471470
template <typename T>
@@ -516,59 +515,78 @@ namespace asioexec {
516515
bool operator!=(const executor& rhs) const = default;
517516
};
518517

518+
template <typename Mutex>
519+
struct t {
520+
static constexpr auto as_default_on = ::asioexec::as_default_on<t>;
521+
template <typename IoObject>
522+
using as_default_on_t = ::asioexec::as_default_on_t<t, IoObject>;
523+
};
524+
525+
struct null_basic_lockable {
526+
constexpr void lock() noexcept {
527+
}
528+
constexpr void unlock() noexcept {
529+
}
530+
};
531+
519532
} // namespace detail::completion_token
520533

521-
struct completion_token_t {
522-
static constexpr auto as_default_on = asioexec::as_default_on<completion_token_t>;
523-
template <typename IoObject>
524-
using as_default_on_t = asioexec::as_default_on_t<completion_token_t, IoObject>;
525-
};
534+
using completion_token_t = detail::completion_token::t<std::recursive_mutex>;
526535

527536
inline const completion_token_t completion_token{};
528537

538+
using thread_unsafe_completion_token_t =
539+
detail::completion_token::t<detail::completion_token::null_basic_lockable>;
540+
541+
inline const thread_unsafe_completion_token_t thread_unsafe_completion_token{};
542+
529543
} // namespace asioexec
530544

531545
namespace ASIOEXEC_ASIO_NAMESPACE {
532546

533-
template <typename... Signatures>
534-
struct async_result<::asioexec::completion_token_t, Signatures...> {
547+
template <typename Mutex, typename... Signatures>
548+
struct async_result<::asioexec::detail::completion_token::t<Mutex>, Signatures...> {
535549
template <typename Initiation, typename... Args>
536550
requires(std::is_constructible_v<std::decay_t<Args>, Args> && ...)
537-
static constexpr auto
538-
initiate(Initiation&& i, const ::asioexec::completion_token_t&, Args&&... args) {
551+
static constexpr auto initiate(
552+
Initiation&& i,
553+
const ::asioexec::detail::completion_token::t<Mutex>&,
554+
Args&&... args) {
539555
return ::asioexec::detail::completion_token::sender<
556+
Mutex,
540557
::asioexec::detail::completion_token::completion_signatures<Signatures...>,
541558
std::remove_cvref_t<Initiation>,
542-
Args...
543-
>(static_cast<Initiation&&>(i), static_cast<Args&&>(args)...);
559+
Args...>(static_cast<Initiation&&>(i), static_cast<Args&&>(args)...);
544560
}
545561
};
546562

547-
template <typename Signatures, typename Receiver, typename Executor>
563+
template <typename Mutex, typename Signatures, typename Receiver, typename Executor>
548564
struct associated_executor<
549-
::asioexec::detail::completion_token::completion_handler<Signatures, Receiver>,
550-
Executor
551-
> {
552-
using type = ::asioexec::detail::completion_token::executor<Signatures, Receiver, Executor>;
565+
::asioexec::detail::completion_token::completion_handler<Mutex, Signatures, Receiver>,
566+
Executor> {
567+
using type =
568+
::asioexec::detail::completion_token::executor<Mutex, Signatures, Receiver, Executor>;
553569

554570
static type get(
555-
const ::asioexec::detail::completion_token::completion_handler<Signatures, Receiver>& h,
571+
const ::asioexec::detail::completion_token::completion_handler<Mutex, Signatures, Receiver>&
572+
h,
556573
const Executor& ex) noexcept {
557574
return type(h.state(), ex);
558575
}
559576
};
560577

561-
template <typename Signatures, typename Receiver, typename Allocator>
578+
template <typename Mutex, typename Signatures, typename Receiver, typename Allocator>
562579
requires requires(const Receiver& r) { ::STDEXEC::get_allocator(::STDEXEC::get_env(r)); }
563580
struct associated_allocator<
564-
::asioexec::detail::completion_token::completion_handler<Signatures, Receiver>,
581+
::asioexec::detail::completion_token::completion_handler<Mutex, Signatures, Receiver>,
565582
Allocator
566583
> {
567584
using type = std::remove_cvref_t<decltype(::STDEXEC::get_allocator(
568585
::STDEXEC::get_env(std::declval<const Receiver&>())))>;
569586

570587
static type get(
571-
const ::asioexec::detail::completion_token::completion_handler<Signatures, Receiver>& h,
588+
const ::asioexec::detail::completion_token::completion_handler<Mutex, Signatures, Receiver>&
589+
h,
572590
const Allocator&) noexcept {
573591
return ::STDEXEC::get_allocator(::STDEXEC::get_env(h.state().r_));
574592
}

include/asioexec/use_sender.hpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <asioexec/completion_token.hpp>
2424
#include <concepts>
2525
#include <exception>
26+
#include <mutex>
2627
#include <stdexec/execution.hpp>
2728
#include <system_error>
2829
#include <type_traits>
@@ -190,30 +191,38 @@ namespace asioexec {
190191
template <typename Sender>
191192
explicit sender(Sender) -> sender<Sender>;
192193

194+
template <typename Mutex>
195+
struct t {
196+
static constexpr auto as_default_on = ::asioexec::as_default_on<t>;
197+
template <typename IoObject>
198+
using as_default_on_t = ::asioexec::as_default_on_t<t, IoObject>;
199+
};
200+
193201
} // namespace detail::use_sender
194202

195-
struct use_sender_t {
196-
static constexpr auto as_default_on = asioexec::as_default_on<use_sender_t>;
197-
template <typename IoObject>
198-
using as_default_on_t = asioexec::as_default_on_t<use_sender_t, IoObject>;
199-
};
203+
using use_sender_t = detail::use_sender::t<std::recursive_mutex>;
200204

201205
inline const use_sender_t use_sender{};
202206

207+
using thread_unsafe_use_sender_t =
208+
detail::use_sender::t<detail::completion_token::null_basic_lockable>;
209+
210+
inline const thread_unsafe_use_sender_t thread_unsafe_use_sender{};
211+
203212
} // namespace asioexec
204213

205214
namespace ASIOEXEC_ASIO_NAMESPACE {
206215

207-
template <typename... Signatures>
208-
struct async_result<::asioexec::use_sender_t, Signatures...> {
216+
template <typename Mutex, typename... Signatures>
217+
struct async_result<::asioexec::detail::use_sender::t<Mutex>, Signatures...> {
209218
template <typename Initiation, typename... Args>
210219
requires(std::is_constructible_v<std::decay_t<Args>, Args> && ...)
211220
static constexpr auto
212-
initiate(Initiation&& i, const ::asioexec::use_sender_t&, Args&&... args) {
221+
initiate(Initiation&& i, const ::asioexec::detail::use_sender::t<Mutex>&, Args&&... args) {
213222
return ::asioexec::detail::use_sender::sender(
214-
async_result<::asioexec::completion_token_t, Signatures...>::initiate(
223+
async_result<::asioexec::detail::completion_token::t<Mutex>, Signatures...>::initiate(
215224
static_cast<Initiation&&>(i),
216-
::asioexec::completion_token,
225+
::asioexec::detail::completion_token::t<Mutex>{},
217226
static_cast<Args&&>(args)...));
218227
}
219228
};

test/asioexec/test_completion_token.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ namespace {
162162
asio_impl::io_context ctx;
163163
asio_impl::system_timer t(ctx);
164164
t.expires_after(std::chrono::years(1));
165-
auto sender = t.async_wait(completion_token);
165+
auto sender = t.async_wait(thread_unsafe_completion_token);
166166
static_assert(set_equivalent<
167167
completion_signatures_of_t<decltype(sender), env<>>,
168168
completion_signatures<

test/asioexec/test_use_sender.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ namespace {
8282
asio_impl::io_context ctx;
8383
asio_impl::system_timer t(ctx);
8484
t.expires_after(std::chrono::years(1));
85-
auto sender = t.async_wait(use_sender);
85+
auto sender = t.async_wait(thread_unsafe_use_sender);
8686
static_assert(::STDEXEC::sender_in<decltype(sender)>);
8787
static_assert(
8888
::STDEXEC::sender_of<

0 commit comments

Comments
 (0)