Skip to content

Commit e7506d3

Browse files
authored
🐛 Fix cancel SEGV due to noop_coroutine() usage (#59)
It turns out that the noop_coroutine and is well documented in cppreference, that supsequent calls to noop_coroutine() may not point to the same handle, meaning that they may not compare equal. To fix this I've added a `context::noop_sentinel` which acts as the GND or basis for all coroutines. Resolves #57
1 parent 8148340 commit e7506d3

2 files changed

Lines changed: 34 additions & 24 deletions

File tree

CMakeLists.txt

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ cmake_minimum_required(VERSION 3.28)
1616

1717
# Generate compile commands for anyone using our libraries.
1818
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
19+
# The rest are self explanatory...
1920
set(CMAKE_COLOR_DIAGNOSTICS ON)
2021
set(BUILD_UNIT_TESTS ON)
22+
set(RUN_UNIT_TESTS ON)
2123
set(BUILD_BENCHMARKS ON)
24+
set(RUN_BENCHMARKS OFF)
2225

2326
project(async_context LANGUAGES CXX)
2427

@@ -73,10 +76,7 @@ add_custom_target(copy_compile_commands ALL
7376
# Unit testing
7477
# ==============================================================================
7578

76-
if(BUILD_UNIT_TESTS)
77-
if(CMAKE_CROSSCOMPILING)
78-
message(STATUS "Cross compiling, skipping unit test execution")
79-
else()
79+
if(NOT CMAKE_CROSSCOMPILING AND BUILD_UNIT_TESTS)
8080
find_package(ut REQUIRED)
8181
message(STATUS "Building unit tests!")
8282
add_executable(async_unit_test)
@@ -112,18 +112,19 @@ else()
112112
-fsanitize=address)
113113
target_link_libraries(async_unit_test PRIVATE async_context Boost::ut)
114114

115-
add_custom_target(run_tests ALL DEPENDS async_unit_test COMMAND async_unit_test)
116-
endif()
117-
endif() # BUILD_UNIT_TESTS
115+
if(RUN_UNIT_TESTS)
116+
add_custom_target(run_tests ALL DEPENDS async_unit_test
117+
COMMAND async_unit_test)
118+
endif() # RUN_UNIT_TESTS
119+
else()
120+
message(STATUS "Cross compiling, skipping unit test execution")
121+
endif() # NOT CMAKE_CROSSCOMPILING AND BUILD_UNIT_TESTS
118122

119123
# ==============================================================================
120124
# Benchmarking
121125
# ==============================================================================
122126

123-
if(BUILD_BENCHMARKS)
124-
if(CMAKE_CROSSCOMPILING)
125-
message(STATUS "Cross compiling, skipping benchmarks")
126-
else()
127+
if(NOT CMAKE_CROSSCOMPILING AND BUILD_BENCHMARKS)
127128
find_package(benchmark REQUIRED)
128129
message(STATUS "Building benchmarks tests!")
129130
add_executable(async_benchmark)
@@ -144,6 +145,10 @@ else()
144145
async_context
145146
benchmark::benchmark)
146147

147-
add_custom_target(run_benchmark ALL DEPENDS async_benchmark COMMAND async_benchmark)
148-
endif()
149-
endif() # BUILD_BENCHMARKS
148+
if(RUN_BENCHMARKS)
149+
add_custom_target(run_benchmark ALL DEPENDS async_benchmark
150+
COMMAND async_benchmark)
151+
endif() # RUN_BENCHMARKS
152+
else()
153+
message(STATUS "Cross compiling, skipping benchmarks")
154+
endif() # if(NOT CMAKE_CROSSCOMPILING AND BUILD_BENCHMARKS)

modules/async_context.cppm

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ class promise_base;
182182
export class context
183183
{
184184
public:
185+
// We store a single reference to a noop_coroutine() as multiple calls to this
186+
// function are not guaranteed to compare equal. In order to have a
187+
// noop_coroutine that plays nicely with our cancellation code, we need a
188+
// single handle for all to reference as a "done" state.
189+
inline static auto const noop_sentinel = std::noop_coroutine();
185190
static auto constexpr default_timeout = sleep_duration(0);
186191

187192
context() = default;
@@ -258,7 +263,7 @@ public:
258263

259264
constexpr bool done()
260265
{
261-
return m_active_handle == std::noop_coroutine();
266+
return m_active_handle == noop_sentinel;
262267
}
263268

264269
void cancel();
@@ -380,12 +385,12 @@ private:
380385
block_info p_block_info) noexcept = 0;
381386
friend class proxy_context;
382387

383-
/* vtable ptr */ // word 1
384-
std::coroutine_handle<> m_active_handle = std::noop_coroutine(); // word 2
385-
std::span<uptr> m_stack{}; // word 3-4
386-
uptr* m_stack_pointer = nullptr; // word 5
387-
blocked_by m_state = blocked_by::nothing; // word 6
388-
proxy_info m_proxy{}; // word 7-8
388+
/* vtable ptr */ // word 1
389+
std::coroutine_handle<> m_active_handle = noop_sentinel; // word 2
390+
std::span<uptr> m_stack{}; // word 3-4
391+
uptr* m_stack_pointer = nullptr; // word 5
392+
blocked_by m_state = blocked_by::nothing; // word 6
393+
proxy_info m_proxy{}; // word 7-8
389394
};
390395

391396
// Context should stay close to a standard cache-line of 64 bytes (8 words) for
@@ -422,7 +427,7 @@ public:
422427
private:
423428
proxy_context(context& p_parent)
424429
{
425-
m_active_handle = std::noop_coroutine();
430+
m_active_handle = context::noop_sentinel;
426431
m_proxy = {};
427432

428433
// We need to manually set:
@@ -487,7 +492,7 @@ public:
487492
*/
488493
void sync_wait(std::invocable<sleep_duration> auto&& p_delay)
489494
{
490-
while (active_handle() != std::noop_coroutine()) {
495+
while (active_handle() != context::noop_sentinel) {
491496
active_handle().resume();
492497

493498
if (state() == blocked_by::time && m_pending_delay > sleep_duration(0)) {
@@ -753,7 +758,7 @@ protected:
753758
// Consider m_continuation as the return address of the coroutine. The
754759
// coroutine handle for the coroutine that called and awaited the future that
755760
// generated this promise is stored here.
756-
std::coroutine_handle<> m_continuation = std::noop_coroutine();
761+
std::coroutine_handle<> m_continuation = context::noop_sentinel;
757762
class context* m_context = nullptr;
758763
cancellation_fn* m_cancel = nullptr;
759764
};

0 commit comments

Comments
 (0)