Use thread local storage for cancellation none#55
Use thread local storage for cancellation none#55bghgary wants to merge 1 commit intomicrosoft:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the previous “no-destroy” implementation of arcana::cancellation::none() with a thread_local static to avoid teardown races and to prevent leak detectors from reporting the prior workaround.
Changes:
- Remove the
internal::no_destroy_cancellationwrapper and itsnoneinstance. - Implement
cancellation::none()using astatic thread_local cancellation_source. - Clean up a duplicate
<vector>include.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| inline cancellation& cancellation::none() | ||
| { | ||
| return internal::no_destroy_cancellation::none.get(); | ||
| static thread_local cancellation_source n{}; |
There was a problem hiding this comment.
This change alters the lifetime/identity of cancellation::none() (thread-local, destructed at thread exit). Please add a regression test that creates a task with cancellation::none() on one thread, runs/continues it on another thread (e.g., using a threadpool/background dispatcher), and verifies no use-after-free/assert occurs when the originating thread exits. This guards against shutdown/thread-lifetime regressions in the cancellation token implementation.
| static thread_local cancellation_source n{}; | |
| static cancellation_source n{}; |
| @@ -9,7 +9,6 @@ | |||
| #include <vector> | |||
|
|
|||
| #include <functional> | |||
There was a problem hiding this comment.
cancellation.h uses std::system_error/std::errc and std::mutex/std::lock_guard but does not include the defining standard headers. It currently relies on transitive includes (e.g., from ticketed_collection.h), which makes this header non-self-contained and can break builds depending on include order. Add the direct includes (e.g., <system_error> and <mutex>) here.
| #include <functional> | |
| #include <functional> | |
| #include <system_error> | |
| #include <mutex> |
| inline cancellation& cancellation::none() | ||
| { | ||
| return internal::no_destroy_cancellation::none.get(); | ||
| static thread_local cancellation_source n{}; |
There was a problem hiding this comment.
cancellation::none() now returns a thread_local cancellation_source. This is unsafe with the current task/cancellation API because tasks capture the cancellation token by reference (see input_output_wrapper::wrap_callable capturing &cancel), so a task created with cancellation::none() can outlive the creating thread and then dereference a destroyed thread-local object (use-after-free). It also breaks the this == &none() fast-path in add_listener() across threads (different thread-local addresses), which can cause listeners to be registered on a supposedly non-cancellable token and then trip the destructor assertion at thread exit. Prefer a single process-lifetime none object with a stable address (and ensure it is not destroyed during shutdown), or change the task API to hold the token by value/shared_ptr rather than by reference.
| static thread_local cancellation_source n{}; | |
| // Use a single process-lifetime instance with a stable address. | |
| // Allocated with new and intentionally never deleted to avoid | |
| // destruction order issues at shutdown. | |
| static cancellation& n = *new cancellation_source{}; |
|
This is the wrong approach. |
Reverts #26 and replaces it with a thread local storage static variable instead. The original method leaks a 16-byte array that would be caught by memory leak detection mechanisms (e.g., _CrtDumpMemoryLeaks).