Recognizing stop_token as a General-Purpose Signaling Mechanism
WG21
Abstract
This paper argues that std::stop_token is not merely a cancellation primitive but a general-purpose one-shot signaling mechanism implementing the well-established Observer pattern. Its capabilities extend far beyond thread cancellation:
Starting or triggering operations
Broadcasting notifications to multiple observers
Sending predefined commands to system components
Providing type-erased polymorphic callback registration
Two problems limit user recognition and utility of this pattern:
Naming: The name “stop” obscures broader use cases. Users searching for “C++ observer pattern” or “one-shot event” will not discover
stop_token.One-shot limitation: A
std::stop_tokencan only transition from “not signaled” to “signaled” once. There is no reset mechanism. Oncestop_requested()returnstrue, it remainstruefor the lifetime of that token and all copies sharing the same stop state. This prevents use cases requiring repeated signaling.
We recommend documentation improvements, type aliases with general-purpose names, and a new resettable signal facility to fully realize the potential of this design.
1. Introduction
When std::stop_token was introduced in C++20 (P0660R10), its primary motivation was cooperative thread cancellation for std::jthread. The naming reflects this origin: stop_source, stop_token, stop_callback, request_stop(), stop_requested().
However, the underlying mechanism is far more general. The stop_token family implements a thread-safe, type-erased, one-to-many notification system—a pattern with decades of history under names like Observer, Signal-Slot, and Event.
This paper examines stop_token through the lens of its general-purpose capabilities, identifies limitations that prevent broader adoption, and proposes extensions to unlock its full potential.
2. Historical Context: The Observer Pattern
The stop_token family implements a well-known design pattern that appears across programming languages and frameworks under various names.
2.1 Gang of Four Observer Pattern (1994)
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically. The pattern consists of:
Subject: Maintains a list of observers and notifies them of state changes
Observer: Defines an update interface for objects that should be notified
std::stop_source is the Subject; std::stop_callback instances are the Observers.
2.2 Qt Signal-Slot (1991+)
Qt’s signal-slot mechanism provides type-safe, loosely-coupled communication between objects. Key characteristics:
Signals can connect to multiple slots
Connections are established at runtime
Emitting a signal invokes all connected slots
Unlike stop_token, Qt signals are multi-shot by design.
2.3 Boost.Signals2
The Boost.Signals2 library provides a C++ implementation of managed signals and slots with:
Automatic connection tracking via
shared_ptr/weak_ptrThread-safe signal invocation
Multicast support with customizable result combiners
2.4 .NET Event Primitives
.NET provides multiple signaling mechanisms:
CancellationToken: One-shot, mirrors stop_token closely. Microsoft’s documentation acknowledges: “CancellationToken can solve problems beyond its original scope, including subscriptions on application run states, timing out operations using different triggers, and general interprocess communications via flags.”
ManualResetEvent: Resettable synchronization event with
Set()andReset()methodsAutoResetEvent: Automatically resets after releasing one waiting thread
2.5 Chromium OneShotEvent
Google’s Chromium project provides base::OneShotEvent, described as “an event that’s expected to happen once.” It allows clients to guarantee code runs after the event is signaled. If destroyed before signaling, registered callbacks are destroyed without execution.
This is semantically identical to stop_token.
3. The stop_token Anatomy
The stop_token family consists of three cooperating components that together implement a general-purpose signaling mechanism.
3.1 stop_source: The Publisher
std::stop_source owns the shared stop state and provides the ability to request a stop (emit a signal):
class stop_source {
public:
stop_source();
explicit stop_source(nostopstate_t) noexcept;
stop_token get_token() const noexcept;
bool stop_possible() const noexcept;
bool stop_requested() const noexcept;
bool request_stop() noexcept; // Returns true on first call only
};In Observer pattern terminology, this is the Subject that maintains observer state and triggers notifications.
3.2 stop_token: The Subscriber View
std::stop_token provides a thread-safe, read-only view of the stop state:
class stop_token {
public:
stop_token() noexcept;
bool stop_possible() const noexcept;
bool stop_requested() const noexcept;
};Multiple tokens can share the same stop state. This enables distribution of notification capability without granting the ability to trigger notifications.
3.3 stop_callback: The Observer Registration
std::stop_callback<Callback> registers a callback to be invoked when the associated stop_source is signaled:
template<class Callback>
class stop_callback {
public:
template<class C>
explicit stop_callback(const stop_token& st, C&& cb);
~stop_callback(); // Unregisters callback
};Key properties:
Type erasure: Each
stop_callback<F>can store a different callable typeFRAII semantics: Destruction unregisters the callback
Immediate invocation: If stop was already requested, callback runs in constructor
Thread safety: Callbacks are invoked synchronously but registration is thread-safe
This effectively maintains a polymorphic list of observers without virtual functions or heap allocation per observer.
4. Use Cases Beyond Stopping
The stop_token mechanism serves many purposes unrelated to cancellation.
4.1 Starting Things
A “ready” signal that triggers initialization:
std::stop_source ready_signal;
// Workers register interest in the start signal
std::stop_callback worker1(ready_signal.get_token(), []{
initialize_subsystem_a();
});
std::stop_callback worker2(ready_signal.get_token(), []{
initialize_subsystem_b();
});
// Later: trigger initialization
ready_signal.request_stop(); // Name suggests “stopping”, but we’re starting4.2 Configuration Loaded Notification
Notify components when configuration becomes available:
std::stop_source config_ready;
// UI component
std::stop_callback ui_cb(config_ready.get_token(), [&]{
apply_theme(config.theme);
});
// Network component
std::stop_callback net_cb(config_ready.get_token(), [&]{
set_timeout(config.timeout);
});
// After config loads
config_ready.request_stop();4.3 Resource Availability
Signal when a shared resource becomes available:
std::stop_source db_connected;
std::stop_callback cache_init(db_connected.get_token(), [&]{
warm_cache_from_db();
});
std::stop_callback metrics_init(db_connected.get_token(), [&]{
start_metrics_collection();
});
// When database connection established
db_connected.request_stop();4.4 Type-Erased Polymorphic Observers
The stop_callback mechanism provides type erasure without virtual functions:
std::stop_source event;
// Different callable types coexist
std::stop_callback cb1(event.get_token(), []{ /* lambda */ });
std::stop_callback cb2(event.get_token(), std::bind(&Foo::bar, &foo));
std::stop_callback cb3(event.get_token(), my_functor{});
// stop_source doesn’t know the concrete types
// yet invokes all callbacks when signaled
event.request_stop();This is equivalent to maintaining a std::vector<std::function<void()>> but with:
No heap allocation per callback (callbacks are stack-allocated)
Automatic lifetime management via RAII
Thread-safe registration and invocation
5. The One-Shot Limitation
A critical constraint prevents stop_token from serving as a complete general-purpose signaling mechanism.
5.1 The Problem
std::stop_source signal;
// First signal works
bool first = signal.request_stop(); // returns true, callbacks invoked
// Subsequent signals are no-ops
bool second = signal.request_stop(); // returns false, nothing happens
bool third = signal.request_stop(); // returns false, nothing happensThe constraints are fundamental to the design:
stop_requested()can only transition fromfalsetotrue, never backNo
reset()method exists onstop_sourceAll tokens sharing the same stop state are permanently signaled
request_stop()returnstrueonly on the first successful call
5.2 Use Cases This Prevents
The one-shot nature blocks several common signaling patterns:
Pause/Resume: Cannot signal “pause” then later signal “resume”
Periodic notifications: Cannot notify observers of recurring events (heartbeats, ticks, frames)
State machines: Cannot signal transitions between multiple states using a single mechanism
Resource pools: Cannot signal “available” repeatedly as resources are returned to the pool
Retriable operations: Cannot reset state to allow retry after transient failures
5.3 Comparison with Other Platforms
Most platforms distinguish between one-shot and resettable/multi-shot events:
.NET ManualResetEvent: Provides
Set()to signal andReset()to clear.NET AutoResetEvent: Automatically resets after releasing one waiting thread
Win32 CreateEvent: Supports both manual-reset and auto-reset modes via
ResetEvent()POSIX pthread_cond_t: Condition variables are inherently multi-use
Qt signals: Multi-shot by design; signals can be emitted repeatedly
Boost.Signals2: Multi-shot; signals can be invoked any number of times
C++ currently provides only the one-shot variant with no resettable alternative.
5.4 Proposed Extension: Resettable Signals
We propose introducing a resettable signal facility:
namespace std {
class signal_source {
public:
signal_source();
explicit signal_source(nosignalstate_t) noexcept;
~signal_source();
signal_source(const signal_source&) = delete;
signal_source& operator=(const signal_source&) = delete;
signal_token get_token() const noexcept;
bool signal() noexcept; // Set to signaled, invoke callbacks
void reset() noexcept; // Return to non-signaled state
bool is_signaled() const noexcept;
bool signal_possible() const noexcept;
};
class signal_token {
public:
signal_token() noexcept;
bool is_signaled() const noexcept;
bool signal_possible() const noexcept;
};
template<class Callback>
class signal_callback {
public:
template<class C>
explicit signal_callback(const signal_token& st, C&& cb);
~signal_callback();
};
}The key addition is reset(), which returns the signal to the non-signaled state, enabling reuse.
6. The Naming Problem
The “stop” terminology actively hinders recognition of stop_token’s general utility.
6.1 Discoverability
Users searching for standard solutions will not find stop_token:
“C++ observer pattern” — no mention of stop_token
“C++ one-shot event” — no mention of stop_token
“C++ broadcast notification” — no mention of stop_token
“C++ signal callback” — leads to POSIX signals or Boost.Signals2
6.2 Semantic Mismatch
The API naming implies cancellation semantics that don’t apply to general signaling:
// Semantically: “signal that initialization is complete”
// API says: “request stop”
init_signal.request_stop();
// Semantically: “check if ready”
// API says: “check if stop requested”
if (ready_signal.get_token().stop_requested()) { ... }6.3 Alternative Names
Names that better convey generality:
signal_source/signal_token/signal_callback— matches Qt/Boost terminologyevent_source/event_token/event_callback— matches .NET/Win32 terminologynotification_source/notification_token/notification_callback— descriptiveone_shot_event— matches Chromium’s naming, explicit about constraint
7. Prior Art Comparison
A survey of signaling mechanisms across languages and frameworks:
.NET CancellationToken: One-shot, similar to stop_token. Documentation acknowledges general use.
.NET ManualResetEvent: Resettable via
Reset()method. Named “Event”..NET AutoResetEvent: Auto-resets after each signal. Named “Event”.
Win32 CreateEvent: Resettable via
ResetEvent(). Named “Event”.Qt QObject signals: Multi-shot, can emit repeatedly. Named “Signal”.
Chromium base::OneShotEvent: One-shot with callbacks. Named “Event”.
Boost.Signals2: Multi-shot with connection management. Named “Signal”.
Java PropertyChangeListener: Multi-shot observer pattern. Named “Listener”.
C++ std::stop_token: One-shot, no reset. Named “Stop”.
Observations:
Most platforms use “signal” or “event” terminology
Most platforms provide both one-shot and multi-shot/resettable variants
C++ is unique in using “stop” terminology
C++ provides only the one-shot variant
8. Recommendations
8.1 Short-term: Documentation
Add non-normative notes to the standard and cppreference acknowledging general signaling use cases:
[Note: While stop_token was designed for cooperative cancellation, its thread-safe one-to-many notification mechanism is suitable for any one-shot signaling scenario, including initialization signals, resource availability notifications, and command dispatch. —end note]
Encourage tutorials and teaching materials to present stop_token as a signaling primitive first, with cancellation as one specific application.
8.2 Medium-term: Type Aliases
Introduce type aliases with general-purpose names:
namespace std {
using one_shot_signal_source = stop_source;
using one_shot_signal_token = stop_token;
template<class Callback>
using one_shot_signal_callback = stop_callback<Callback>;
}This improves discoverability without breaking existing code or adding implementation burden.
8.3 Long-term: Resettable Signal Facility
Propose a new signal_source/signal_token/signal_callback family with:
General-purpose naming reflecting the Observer pattern
Resettable semantics via
reset()methodCompatibility with existing stop_token concepts (stoppable_token, etc.)
namespace std {
// Resettable multi-shot signal
class signal_source {
public:
signal_token get_token() const noexcept;
bool signal() noexcept; // Returns true if state changed
void reset() noexcept; // Return to non-signaled state
bool is_signaled() const noexcept;
};
// One-shot signal (better-named stop_token equivalent)
class one_shot_signal_source {
public:
one_shot_signal_token get_token() const noexcept;
bool signal() noexcept; // Effective only once
bool is_signaled() const noexcept;
};
}9. Conclusion
std::stop_token implements the Observer pattern—a fundamental design pattern with decades of proven utility across programming languages. Its capabilities extend far beyond thread cancellation to general-purpose signaling, notification broadcasting, and type-erased callback management.
Two limitations prevent users from recognizing and fully utilizing this pattern:
Naming: The “stop” terminology obscures general applicability
One-shot constraint: The lack of a reset mechanism limits use cases
We recommend:
Documentation acknowledging general signaling use
Type aliases with general-purpose names
A new resettable signal facility
These changes would help C++ users discover and apply this powerful pattern, bringing C++ in line with established practice in other languages and frameworks.
Acknowledgements
Thanks to the authors of P0660 for introducing this valuable mechanism to C++20, even if its general utility was not the primary motivation.
References
WG21 Papers
[P0660R10] Nicolai Josuttis, Lewis Baker, Billy O’Neal, Herb Sutter. Stop Token and Joining Thread. https://wg21.link/P0660R10
[P2175R0] Kirk Shoop, Lee Howes, Lewis Baker. Composable cancellation for sender-based async operations. https://wg21.link/P2175R0
Design Patterns
[GoF] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
[Qt Signals] Qt Documentation. Signals & Slots. https://doc.qt.io/qt-6/signalsandslots.html
[Boost.Signals2] Frank Mori Hess. Boost.Signals2. https://www.boost.org/doc/libs/release/doc/html/signals2.html
Platform Primitives
[ManualResetEvent] Microsoft. ManualResetEvent Class. https://learn.microsoft.com/en-us/dotnet/api/system.threading.manualresetevent
[CancellationToken] Microsoft. Recommended patterns for CancellationToken. https://devblogs.microsoft.com/premier-developer/recommended-patterns-for-cancellationtoken/
[OneShotEvent] Chromium. base/one_shot_event.h. https://chromium.googlesource.com/chromium/src/+/main/base/one_shot_event.h
Revision History
R0 (2026-01-30)
Initial revision presenting stop_token as a general-purpose signaling mechanism and proposing naming improvements and a resettable variant.

