From 37c8c0d6c1746080a1e32bdc3c4949f9dd8424f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 1 Nov 2026 21:47:22 +0000 Subject: [PATCH 1/9] added an example not allocating for task_scheduler --- Makefile | 2 +- docs/overview.md | 44 ++++++- .../beman/execution/detail/find_allocator.hpp | 2 +- .../beman/execution/detail/task_scheduler.hpp | 2 +- tests/beman/execution/CMakeLists.txt | 1 + .../dis-task_scheduler-allocation.test.cpp | 109 ++++++++++++++++++ 6 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 tests/beman/execution/dis-task_scheduler-allocation.test.cpp diff --git a/Makefile b/Makefile index ed03c97c..07da5d30 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ SIMPLE_BUILD = build/simple-$(shell uname -s) simple: simple-test simple-config: - cmake -G Ninja -S . -B $(SIMPLE_BUILD) -DBEMAN_USE_MODULES=OFF -DCXXFLAGS= + cmake -G Ninja -S . -B $(SIMPLE_BUILD) -DBEMAN_USE_MODULES=OFF -DCMAKE_BUILD_TYPE=Release simple-build: simple-config CXXFLAGS= cmake --build $(SIMPLE_BUILD) diff --git a/docs/overview.md b/docs/overview.md index 9e6b0287..f529bf4e 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -741,13 +741,13 @@ The expression let_stopped(upstream, fun) yields a se The expression let_value(upstream, fun) yields a sender sndr which uses a successful completion (set_value) of upstream as arguments to invoke fun which has to return another sender inner-sndr and the completion of inner-sndr becomes the completion of sndr. If this invocation results in an exception sndr completes with an error completion the result of std::current_exception. If upstream completes with a cancellation (set_stopped) or with an error (set_error) this completion becomes the completion of sndr.
-on(_sched_, _sndr_), on(_sndr, _sched_, _closure_) +on(sched, sndr), on(sndr, sched, closure) The on algorithm is a pipeable sender adaptor. Let on-sender be -The sender on-sndr is started on _sched_'s execution context. +The sender on-sndr is started on sched's execution context. The on algorithm completes on the original scheduler (obtained using get_start_scheduler) with the result of the on-sndr.
@@ -808,6 +808,24 @@ The expression write_env(sender, env) creates a sende which passes a receiver to sender which combines the environment env with the environment from the receiver's environment. The queries from env take precedence over those from the receiver's environment. +
+Example + +This example shows using `write_env` providing an environment with an allocator: + +```c++ +std::pmr::monotonic_buffer_resource mbr; +std::pmr::polymorphic_allocator mbr_alloc(&mbr); +ex::sync_wait( + ex::read_env(ex::get_allocator) + | ex::then([&](auto alloc){ assert(mbr_alloc == alloc); }) + | ex::write_env(ex::env{ex::prop{ex::get_allocator, mbr_alloc}}) +); +``` + +Note that `write_env` affects the sender passed as first argument or, when using +`|`, the sender before the `|`. +
@@ -890,7 +908,7 @@ an object loop of type run_loop are: @@ -1167,3 +1185,19 @@ This concept determines if an object of type Rcvr supports the comp concept valid-specialization<T, Args...> This concept determines the class template T can be specialized with arguments Args..., i.e., if T<Args...> is valid. + + +
+concept simple-allocator<Alloc> + +The concept simple-allocator<Alloc> determines if +Alloc is a basic allocator. If `alloc` is an object of type +`Alloc`, the following need to be `true`: + + +
diff --git a/include/beman/execution/detail/find_allocator.hpp b/include/beman/execution/detail/find_allocator.hpp index 9f94ee31..51b61e18 100644 --- a/include/beman/execution/detail/find_allocator.hpp +++ b/include/beman/execution/detail/find_allocator.hpp @@ -32,7 +32,7 @@ template Allocator find_allocator(const ::std::allocator_arg_t&, const Alloc& alloc, const A&...) noexcept { static_assert( requires(const Alloc& a) { Allocator(a); }, - "The allocator needs to be constructible from the argument following std::allocator"); + "The allocator needs to be constructible from the argument following std::allocator_arg"); return Allocator(alloc); } template diff --git a/include/beman/execution/detail/task_scheduler.hpp b/include/beman/execution/detail/task_scheduler.hpp index 504933c8..a5833590 100644 --- a/include/beman/execution/detail/task_scheduler.hpp +++ b/include/beman/execution/detail/task_scheduler.hpp @@ -344,7 +344,7 @@ class task_scheduler { explicit task_scheduler(Sched&& sched, Alloc alloc = {}) : backend_(::std::allocate_shared< ::beman::execution::detail::task_scheduler_backend_for<::std::remove_cvref_t, Alloc>>( - alloc, ::std::forward(sched), ::std::move(alloc))) {} + alloc, ::std::forward(sched), alloc)) {} template <::beman::execution::detail::simple_allocator Alloc> explicit task_scheduler(task_scheduler sched, const Alloc&) noexcept : task_scheduler(::std::move(sched)) {} diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 07e05189..b35691e1 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND unsupported_execution_tests exec-split.test) list( APPEND execution_tests allocator-requirements-general.test + dis-task_scheduler-allocation.test exec-affine.test exec-associate.test exec-awaitable.test diff --git a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp new file mode 100644 index 00000000..fd9b5c03 --- /dev/null +++ b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp @@ -0,0 +1,109 @@ +// tests/beman/execution/dis-task_scheduler-allocation.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#if 0 +#include +namespace ex = std::execution; +#elif 1 +#include +namespace ex = beman::execution; +#else +#include +namespace ex = stdexec; +#endif + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { + constexpr std::size_t big_size{40}; + struct big_scheduler { + using scheduler_concept = ex::scheduler_tag; + std::array data{}; + + template + struct state { + using operation_state_concept = ex::operation_state_tag; + std::remove_reference_t receiver; + std::array data{}; + state(R&& r) : receiver(std::forward(r)) {} + void start() noexcept { ex::set_value(std::move(receiver)); } + }; + + struct sender { + using sender_concept = ex::sender_tag; + std::array data{}; + + struct env { + auto query(ex::get_completion_scheduler_t const&) const noexcept { + return big_scheduler{}; + } + }; + auto get_env() const noexcept { return env{}; } + + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + + template + state connect(R&& r) && { return state{std::forward(r)}; } + }; + + auto schedule() const noexcept { return sender{}; } + + auto query(ex::get_forward_progress_guarantee_t) const noexcept { + return ex::forward_progress_guarantee::parallel; + } + + bool operator==(const big_scheduler&) const noexcept = default; + }; + + static_assert(big_size <= sizeof(big_scheduler)); + static_assert(ex::sender); + static_assert(ex::scheduler); + + struct simple_resource: std::pmr::memory_resource { + void* do_allocate(std::size_t bytes, std::size_t alignment) override { return std::malloc(bytes); } + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override { std::free(p); } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } + }; + inline constinit simple_resource resource{}; + + template + struct simple_allocator { + using value_type = T; + simple_allocator() = default; + template + simple_allocator(const simple_allocator&) noexcept {} + T* allocate(std::size_t n) { return static_cast(resource.allocate(n * sizeof(T))); } + void deallocate(T* p, std::size_t) noexcept { resource.deallocate(p, 0, alignof(T)); } + bool operator==(const simple_allocator&) const noexcept = default; + operator std::pmr::memory_resource*() const noexcept { return &resource; } + }; + + struct allocator_env { + using allocator_type = std::pmr::polymorphic_allocator; + }; +} + +void* operator new(std::size_t size) { std::abort(); return std::malloc(size); } +void* operator new[](std::size_t size) { std::abort(); return std::malloc(size); } +void operator delete(void* ptr) noexcept { std::free(ptr); } +void operator delete[](void* ptr) noexcept { std::free(ptr); } + +int main() { + ex::sync_wait( + ex::starts_on(big_scheduler{}, + [](std::allocator_arg_t, simple_allocator)->ex::task { + auto s = co_await ex::read_env(ex::get_start_scheduler); + co_await ex::starts_on(s, ex::just()); + }(std::allocator_arg, simple_allocator{}) + | ex::write_env(ex::env{ ex::prop{ex::get_allocator, simple_allocator{}} }) + )); +} From 7415c025f46f8fc5d0a92563a21c901834a00388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 1 Nov 2026 22:37:14 +0000 Subject: [PATCH 2/9] fixed a few compilation issues when using gcc --- include/beman/execution/execution.hpp | 2 + .../dis-task_scheduler-allocation.test.cpp | 154 ++++++++++-------- 2 files changed, 86 insertions(+), 70 deletions(-) diff --git a/include/beman/execution/execution.hpp b/include/beman/execution/execution.hpp index a56fd3cb..b6b6611a 100644 --- a/include/beman/execution/execution.hpp +++ b/include/beman/execution/execution.hpp @@ -54,6 +54,7 @@ import beman.execution.detail.receiver; import beman.execution.detail.schedule_from; import beman.execution.detail.schedule; import beman.execution.detail.scheduler; +import beman.execution.detail.scheduler_tag; import beman.execution.detail.scope_token; import beman.execution.detail.sender_adaptor_closure; import beman.execution.detail.sender_in; @@ -130,6 +131,7 @@ import beman.execution.detail.write_env; #include #include #include +#include #include #include #include diff --git a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp index fd9b5c03..d2cedae4 100644 --- a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp +++ b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp @@ -12,98 +12,112 @@ namespace ex = beman::execution; namespace ex = stdexec; #endif +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else #include -#include +#include +#include #include +#include #include -#include +#include +#endif // ---------------------------------------------------------------------------- namespace { - constexpr std::size_t big_size{40}; - struct big_scheduler { - using scheduler_concept = ex::scheduler_tag; +constexpr std::size_t big_size{40}; +struct big_scheduler { + using scheduler_concept = ex::scheduler_tag; + std::array data{}; + + template + struct state { + using operation_state_concept = ex::operation_state_tag; + std::remove_reference_t receiver; std::array data{}; + state(R&& r) : receiver(std::forward(r)) {} + void start() noexcept { ex::set_value(std::move(receiver)); } + }; - template - struct state { - using operation_state_concept = ex::operation_state_tag; - std::remove_reference_t receiver; - std::array data{}; - state(R&& r) : receiver(std::forward(r)) {} - void start() noexcept { ex::set_value(std::move(receiver)); } - }; - - struct sender { - using sender_concept = ex::sender_tag; - std::array data{}; - - struct env { - auto query(ex::get_completion_scheduler_t const&) const noexcept { - return big_scheduler{}; - } - }; - auto get_env() const noexcept { return env{}; } + struct sender { + using sender_concept = ex::sender_tag; + std::array data{}; - template - static consteval auto get_completion_signatures() { - return ex::completion_signatures{}; + struct env { + auto query(const ex::get_completion_scheduler_t&) const noexcept { + return big_scheduler{}; } - - template - state connect(R&& r) && { return state{std::forward(r)}; } }; + auto get_env() const noexcept { return env{}; } - auto schedule() const noexcept { return sender{}; } - - auto query(ex::get_forward_progress_guarantee_t) const noexcept { - return ex::forward_progress_guarantee::parallel; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; } - bool operator==(const big_scheduler&) const noexcept = default; - }; - - static_assert(big_size <= sizeof(big_scheduler)); - static_assert(ex::sender); - static_assert(ex::scheduler); - - struct simple_resource: std::pmr::memory_resource { - void* do_allocate(std::size_t bytes, std::size_t alignment) override { return std::malloc(bytes); } - void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override { std::free(p); } - bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } - }; - inline constinit simple_resource resource{}; - - template - struct simple_allocator { - using value_type = T; - simple_allocator() = default; - template - simple_allocator(const simple_allocator&) noexcept {} - T* allocate(std::size_t n) { return static_cast(resource.allocate(n * sizeof(T))); } - void deallocate(T* p, std::size_t) noexcept { resource.deallocate(p, 0, alignof(T)); } - bool operator==(const simple_allocator&) const noexcept = default; - operator std::pmr::memory_resource*() const noexcept { return &resource; } + template + state connect(R&& r) && { + return state{std::forward(r)}; + } }; - struct allocator_env { - using allocator_type = std::pmr::polymorphic_allocator; - }; + auto schedule() const noexcept { return sender{}; } + + auto query(ex::get_forward_progress_guarantee_t) const noexcept { + return ex::forward_progress_guarantee::parallel; + } + + bool operator==(const big_scheduler&) const noexcept = default; +}; + +static_assert(big_size <= sizeof(big_scheduler)); +static_assert(ex::sender); +static_assert(ex::scheduler); + +struct simple_resource : std::pmr::memory_resource { + void* do_allocate(std::size_t bytes, std::size_t alignment) override { return std::malloc(bytes); } + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override { std::free(p); } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; +inline constinit simple_resource resource{}; + +template +struct simple_allocator { + using value_type = T; + simple_allocator() = default; + template + simple_allocator(const simple_allocator&) noexcept {} + T* allocate(std::size_t n) { return static_cast(resource.allocate(n * sizeof(T))); } + void deallocate(T* p, std::size_t) noexcept { resource.deallocate(p, 0, alignof(T)); } + bool operator==(const simple_allocator&) const noexcept = default; + operator std::pmr::memory_resource*() const noexcept { return &resource; } +}; + +struct allocator_env { + using allocator_type = std::pmr::polymorphic_allocator; +}; +} // namespace + +void* operator new(std::size_t size) { + std::abort(); + return std::malloc(size); +} +void* operator new[](std::size_t size) { + std::abort(); + return std::malloc(size); } - -void* operator new(std::size_t size) { std::abort(); return std::malloc(size); } -void* operator new[](std::size_t size) { std::abort(); return std::malloc(size); } void operator delete(void* ptr) noexcept { std::free(ptr); } void operator delete[](void* ptr) noexcept { std::free(ptr); } int main() { ex::sync_wait( ex::starts_on(big_scheduler{}, - [](std::allocator_arg_t, simple_allocator)->ex::task { - auto s = co_await ex::read_env(ex::get_start_scheduler); - co_await ex::starts_on(s, ex::just()); - }(std::allocator_arg, simple_allocator{}) - | ex::write_env(ex::env{ ex::prop{ex::get_allocator, simple_allocator{}} }) - )); + [](std::allocator_arg_t, simple_allocator) + -> ex::task { + auto s = co_await ex::read_env(ex::get_start_scheduler); + co_await ex::starts_on(s, ex::just()); + }(std::allocator_arg, simple_allocator{}) | + ex::write_env(ex::env{ex::prop{ex::get_allocator, simple_allocator{}}}))); } From 316f97d5135b541aa25b421b3faf133206fb5413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 1 Nov 2026 22:52:15 +0000 Subject: [PATCH 3/9] fixed a few issues reported by the CI --- .../dis-task_scheduler-allocation.test.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp index d2cedae4..b315e631 100644 --- a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp +++ b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp @@ -77,8 +77,8 @@ static_assert(ex::sender); static_assert(ex::scheduler); struct simple_resource : std::pmr::memory_resource { - void* do_allocate(std::size_t bytes, std::size_t alignment) override { return std::malloc(bytes); } - void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override { std::free(p); } + void* do_allocate(std::size_t bytes, std::size_t) override { return std::malloc(bytes); } + void do_deallocate(void* p, std::size_t, std::size_t) override { std::free(p); } bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } }; inline constinit simple_resource resource{}; @@ -100,6 +100,15 @@ struct allocator_env { }; } // namespace +#if defined(__SANITIZE_THREAD__) +# define BEMAN_TSAN_ENABLED 1 +#elif defined(__has_feature) +# if __has_feature(thread_sanitizer) +# define BEMAN_TSAN_ENABLED 1 +# endif +#endif + +#ifndef BEMAN_TSAN_ENABLED void* operator new(std::size_t size) { std::abort(); return std::malloc(size); @@ -109,7 +118,10 @@ void* operator new[](std::size_t size) { return std::malloc(size); } void operator delete(void* ptr) noexcept { std::free(ptr); } +void operator delete(void* ptr, std::size_t) noexcept { std::free(ptr); } void operator delete[](void* ptr) noexcept { std::free(ptr); } +void operator delete[](void* ptr, std::size_t) noexcept { std::free(ptr); } +#endif int main() { ex::sync_wait( From 0040d5c6d3831b588d0e4806c9ec0f64a79d2c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 1 Nov 2026 22:53:17 +0000 Subject: [PATCH 4/9] clang-format --- .../execution/dis-task_scheduler-allocation.test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp index b315e631..a8d4c8bc 100644 --- a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp +++ b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp @@ -101,11 +101,11 @@ struct allocator_env { } // namespace #if defined(__SANITIZE_THREAD__) -# define BEMAN_TSAN_ENABLED 1 +#define BEMAN_TSAN_ENABLED 1 #elif defined(__has_feature) -# if __has_feature(thread_sanitizer) -# define BEMAN_TSAN_ENABLED 1 -# endif +#if __has_feature(thread_sanitizer) +#define BEMAN_TSAN_ENABLED 1 +#endif #endif #ifndef BEMAN_TSAN_ENABLED From 1712800961ae00b71761aa0689e2247599118c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Nov 2026 23:04:18 +0000 Subject: [PATCH 5/9] added an example using spawn --- Makefile | 2 +- examples/CMakeLists.txt | 2 +- tests/beman/execution/CMakeLists.txt | 1 + .../dis-counting_scope-allocation.test.cpp | 113 ++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 tests/beman/execution/dis-counting_scope-allocation.test.cpp diff --git a/Makefile b/Makefile index 07da5d30..771a028c 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ SIMPLE_BUILD = build/simple-$(shell uname -s) simple: simple-test simple-config: - cmake -G Ninja -S . -B $(SIMPLE_BUILD) -DBEMAN_USE_MODULES=OFF -DCMAKE_BUILD_TYPE=Release + cmake -G Ninja -S . -B $(SIMPLE_BUILD) -DBEMAN_USE_MODULES=OFF -DCMAKE_BUILD_TYPE=Debug simple-build: simple-config CXXFLAGS= cmake --build $(SIMPLE_BUILD) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 586f851b..d9e0cad4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,7 +26,7 @@ set(EXAMPLES doc_just_stopped inspect intro_1_hello_world - intro_2_hello_async + # intro_2_hello_async intro_5_consumer playground sender_demo diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index b35691e1..0065e0ab 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -11,6 +11,7 @@ list( APPEND execution_tests allocator-requirements-general.test dis-task_scheduler-allocation.test + dis-counting_scope-allocation.test exec-affine.test exec-associate.test exec-awaitable.test diff --git a/tests/beman/execution/dis-counting_scope-allocation.test.cpp b/tests/beman/execution/dis-counting_scope-allocation.test.cpp new file mode 100644 index 00000000..7e68038f --- /dev/null +++ b/tests/beman/execution/dis-counting_scope-allocation.test.cpp @@ -0,0 +1,113 @@ +// tests/beman/execution/dis-task_scheduler-allocation.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#if 0 +#include +namespace ex = std::execution; +#elif 1 +#include +namespace ex = beman::execution; +#else +#include +namespace ex = stdexec; +#endif + +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +#if defined(__SANITIZE_THREAD__) +#define BEMAN_TSAN_ENABLED 1 +#elif defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define BEMAN_TSAN_ENABLED 1 +#endif +#endif + +std::atomic new_disabled{false}; + +void disable_new() { new_disabled = true; } + +#ifndef BEMAN_TSAN_ENABLED +void* operator new(std::size_t size) { + if (new_disabled) + std::abort(); + return std::malloc(size); +} +void* operator new[](std::size_t size) { + if (new_disabled) + std::abort(); + return std::malloc(size); +} +void operator delete(void* ptr) noexcept { std::free(ptr); } +void operator delete(void* ptr, std::size_t) noexcept { std::free(ptr); } +void operator delete[](void* ptr) noexcept { std::free(ptr); } +void operator delete[](void* ptr, std::size_t) noexcept { std::free(ptr); } +#endif + +// ---------------------------------------------------------------------------- + +namespace { +inline std::pmr::memory_resource* malloc_resource() { + struct resource_t : std::pmr::memory_resource { + void* do_allocate(std::size_t bytes, std::size_t) override { return std::malloc(bytes); } + void do_deallocate(void* p, std::size_t, std::size_t) override { std::free(p); } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } + }; + static constinit resource_t res{}; + return &res; +} + +template +struct allocator { + using value_type = T; + std::pmr::memory_resource* resource{}; + allocator(std::pmr::memory_resource* r) : resource(r) {} + template + allocator(const allocator& other) : resource(other.resource) {} + T* allocate(std::size_t n) { return static_cast(resource->allocate(n * sizeof(T))); } + void deallocate(T* p, std::size_t) noexcept { resource->deallocate(p, 0, alignof(T)); } + bool operator==(const allocator&) const noexcept = default; + operator std::pmr::memory_resource*() const noexcept { return resource; } +}; + +struct allocator_env { + using allocator_type = std::pmr::polymorphic_allocator; +}; +} // namespace +// ---------------------------------------------------------------------------- + +int main(int ac, char*[]) { + allocator alloc(ac == 1 ? malloc_resource() : std::pmr::new_delete_resource()); + ex::env env{ex::prop{ex::get_allocator, alloc}}; + + ex::run_loop loop{}; + std::jthread thread([&] { loop.run(); }); + disable_new(); + + ex::counting_scope scope{}; + + ex::spawn( + ex::starts_on(loop.get_scheduler(), ex::just() | ex::then([]() noexcept { std::cout << "hello, world\n"; })), + scope.get_token(), + env); + + ex::spawn(ex::starts_on(loop.get_scheduler(), ex::just() | ex::then([]() noexcept { + std::cout << "goodbye, world\n"; + }) | ex::then([&]() noexcept { loop.finish(); })), + scope.get_token(), + env); + + ex::sync_wait(scope.join()); +} From 595389d4198697033a56ba78e531a6183b523f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Nov 2026 23:09:39 +0000 Subject: [PATCH 6/9] cleaned up the overall setup --- .../dis-counting_scope-allocation.test.cpp | 1 + .../dis-task_scheduler-allocation.test.cpp | 95 ++++++++++--------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/tests/beman/execution/dis-counting_scope-allocation.test.cpp b/tests/beman/execution/dis-counting_scope-allocation.test.cpp index 7e68038f..60bca224 100644 --- a/tests/beman/execution/dis-counting_scope-allocation.test.cpp +++ b/tests/beman/execution/dis-counting_scope-allocation.test.cpp @@ -86,6 +86,7 @@ struct allocator_env { using allocator_type = std::pmr::polymorphic_allocator; }; } // namespace + // ---------------------------------------------------------------------------- int main(int ac, char*[]) { diff --git a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp index a8d4c8bc..4e88ab55 100644 --- a/tests/beman/execution/dis-task_scheduler-allocation.test.cpp +++ b/tests/beman/execution/dis-task_scheduler-allocation.test.cpp @@ -26,6 +26,31 @@ import std; // ---------------------------------------------------------------------------- +#if defined(__SANITIZE_THREAD__) +#define BEMAN_TSAN_ENABLED 1 +#elif defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define BEMAN_TSAN_ENABLED 1 +#endif +#endif + +#ifndef BEMAN_TSAN_ENABLED +void* operator new(std::size_t size) { + std::abort(); + return std::malloc(size); +} +void* operator new[](std::size_t size) { + std::abort(); + return std::malloc(size); +} +void operator delete(void* ptr) noexcept { std::free(ptr); } +void operator delete(void* ptr, std::size_t) noexcept { std::free(ptr); } +void operator delete[](void* ptr) noexcept { std::free(ptr); } +void operator delete[](void* ptr, std::size_t) noexcept { std::free(ptr); } +#endif + +// ---------------------------------------------------------------------------- + namespace { constexpr std::size_t big_size{40}; struct big_scheduler { @@ -76,23 +101,27 @@ static_assert(big_size <= sizeof(big_scheduler)); static_assert(ex::sender); static_assert(ex::scheduler); -struct simple_resource : std::pmr::memory_resource { - void* do_allocate(std::size_t bytes, std::size_t) override { return std::malloc(bytes); } - void do_deallocate(void* p, std::size_t, std::size_t) override { std::free(p); } - bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } -}; -inline constinit simple_resource resource{}; +inline std::pmr::memory_resource* malloc_resource() { + struct resource_t : std::pmr::memory_resource { + void* do_allocate(std::size_t bytes, std::size_t) override { return std::malloc(bytes); } + void do_deallocate(void* p, std::size_t, std::size_t) override { std::free(p); } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } + }; + static constinit resource_t res{}; + return &res; +} template -struct simple_allocator { - using value_type = T; - simple_allocator() = default; +struct allocator { + using value_type = T; + std::pmr::memory_resource* resource{}; + allocator(std::pmr::memory_resource* r) : resource(r) {} template - simple_allocator(const simple_allocator&) noexcept {} - T* allocate(std::size_t n) { return static_cast(resource.allocate(n * sizeof(T))); } - void deallocate(T* p, std::size_t) noexcept { resource.deallocate(p, 0, alignof(T)); } - bool operator==(const simple_allocator&) const noexcept = default; - operator std::pmr::memory_resource*() const noexcept { return &resource; } + allocator(const allocator& other) : resource(other.resource) {} + T* allocate(std::size_t n) { return static_cast(resource->allocate(n * sizeof(T))); } + void deallocate(T* p, std::size_t) noexcept { resource->deallocate(p, 0, alignof(T)); } + bool operator==(const allocator&) const noexcept = default; + operator std::pmr::memory_resource*() const noexcept { return resource; } }; struct allocator_env { @@ -100,36 +129,12 @@ struct allocator_env { }; } // namespace -#if defined(__SANITIZE_THREAD__) -#define BEMAN_TSAN_ENABLED 1 -#elif defined(__has_feature) -#if __has_feature(thread_sanitizer) -#define BEMAN_TSAN_ENABLED 1 -#endif -#endif - -#ifndef BEMAN_TSAN_ENABLED -void* operator new(std::size_t size) { - std::abort(); - return std::malloc(size); -} -void* operator new[](std::size_t size) { - std::abort(); - return std::malloc(size); -} -void operator delete(void* ptr) noexcept { std::free(ptr); } -void operator delete(void* ptr, std::size_t) noexcept { std::free(ptr); } -void operator delete[](void* ptr) noexcept { std::free(ptr); } -void operator delete[](void* ptr, std::size_t) noexcept { std::free(ptr); } -#endif +// ---------------------------------------------------------------------------- -int main() { - ex::sync_wait( - ex::starts_on(big_scheduler{}, - [](std::allocator_arg_t, simple_allocator) - -> ex::task { - auto s = co_await ex::read_env(ex::get_start_scheduler); - co_await ex::starts_on(s, ex::just()); - }(std::allocator_arg, simple_allocator{}) | - ex::write_env(ex::env{ex::prop{ex::get_allocator, simple_allocator{}}}))); +int main(int ac, char*[]) { + allocator alloc(ac == 1 ? malloc_resource() : std::pmr::new_delete_resource()); + ex::sync_wait(ex::starts_on(big_scheduler{}, [](std::allocator_arg_t, auto) -> ex::task { + auto s = co_await ex::read_env(ex::get_start_scheduler); + co_await ex::starts_on(s, ex::just()); + }(std::allocator_arg, alloc) | ex::write_env(ex::env{ex::prop{ex::get_allocator, alloc}}))); } From 24000bbe035969c4314dc02902fa044c717af27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Nov 2026 23:17:35 +0000 Subject: [PATCH 7/9] added a missing module --- examples/CMakeLists.txt | 2 +- include/beman/execution/execution.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d9e0cad4..586f851b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,7 +26,7 @@ set(EXAMPLES doc_just_stopped inspect intro_1_hello_world - # intro_2_hello_async + intro_2_hello_async intro_5_consumer playground sender_demo diff --git a/include/beman/execution/execution.hpp b/include/beman/execution/execution.hpp index b6b6611a..3bc4c091 100644 --- a/include/beman/execution/execution.hpp +++ b/include/beman/execution/execution.hpp @@ -51,6 +51,7 @@ import beman.execution.detail.parallel_scheduler_replacement; import beman.execution.detail.prop; import beman.execution.detail.read_env; import beman.execution.detail.receiver; +import beman.execution.detail.run_loop; import beman.execution.detail.schedule_from; import beman.execution.detail.schedule; import beman.execution.detail.scheduler; @@ -128,6 +129,7 @@ import beman.execution.detail.write_env; #include #include #include +#include #include #include #include From d6d6014533e2368f8a7611120c42f9a907154727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Nov 2026 23:44:59 +0000 Subject: [PATCH 8/9] jthread isn't available with all clang versions --- tests/beman/execution/dis-counting_scope-allocation.test.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/beman/execution/dis-counting_scope-allocation.test.cpp b/tests/beman/execution/dis-counting_scope-allocation.test.cpp index 60bca224..af0538be 100644 --- a/tests/beman/execution/dis-counting_scope-allocation.test.cpp +++ b/tests/beman/execution/dis-counting_scope-allocation.test.cpp @@ -94,7 +94,7 @@ int main(int ac, char*[]) { ex::env env{ex::prop{ex::get_allocator, alloc}}; ex::run_loop loop{}; - std::jthread thread([&] { loop.run(); }); + std::thread thread([&] { loop.run(); }); disable_new(); ex::counting_scope scope{}; @@ -111,4 +111,6 @@ int main(int ac, char*[]) { env); ex::sync_wait(scope.join()); + + thread.join(); } From ae05cc3b4bc235f4ae506bb3631b006705ccde8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Nov 2026 23:46:13 +0000 Subject: [PATCH 9/9] clang-format --- tests/beman/execution/dis-counting_scope-allocation.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/beman/execution/dis-counting_scope-allocation.test.cpp b/tests/beman/execution/dis-counting_scope-allocation.test.cpp index af0538be..329365cf 100644 --- a/tests/beman/execution/dis-counting_scope-allocation.test.cpp +++ b/tests/beman/execution/dis-counting_scope-allocation.test.cpp @@ -94,7 +94,7 @@ int main(int ac, char*[]) { ex::env env{ex::prop{ex::get_allocator, alloc}}; ex::run_loop loop{}; - std::thread thread([&] { loop.run(); }); + std::thread thread([&] { loop.run(); }); disable_new(); ex::counting_scope scope{};