1818#include " __system_context_replaceability_api.hpp"
1919#include " stdexec/execution.hpp"
2020#include " exec/static_thread_pool.hpp"
21+ #if STDEXEC_ENABLE_LIBDISPATCH
22+ # include " exec/libdispatch_queue.hpp"
23+ #endif
24+
25+ #include < thread>
26+ #include < atomic>
2127
2228namespace exec ::__system_context_default_impl {
2329 using namespace stdexec ::tags;
2430 using system_context_replaceability::receiver;
2531 using system_context_replaceability::bulk_item_receiver;
2632 using system_context_replaceability::storage;
2733 using system_context_replaceability::system_scheduler;
28- using system_context_replaceability::__system_context_replaceability;
29-
30- using __pool_scheduler_t = decltype (std::declval<exec::static_thread_pool>().get_scheduler());
34+ using system_context_replaceability::__system_context_backend_factory;
3135
3236 // / Receiver that calls the callback when the operation completes.
3337 template <class _Sender >
@@ -53,6 +57,8 @@ namespace exec::__system_context_default_impl {
5357 ---------------------
5458 Total: 152; extra 24 bytes compared to internal operation state.
5559
60+ Using libdispatch backend, the operation sizes are 48 (down from 80) and 128 (down from 160).
61+
5662 [*] sizes taken on an Apple M2 Pro arm64 arch. They may differ on other architectures, or with different implementations.
5763 */
5864
@@ -89,6 +95,12 @@ namespace exec::__system_context_default_impl {
8995 __op->__destruct (); // destroys the operation, including `this`.
9096 __r->set_stopped ();
9197 }
98+
99+ decltype (auto ) get_env() const noexcept {
100+ auto __o = __r_->try_query <stdexec::inplace_stop_token>();
101+ stdexec::inplace_stop_token __st = __o ? *__o : stdexec::inplace_stop_token{};
102+ return stdexec::prop{stdexec::get_stop_token, __st};
103+ }
92104 };
93105
94106 // / Ensure that `__storage` is aligned to `__alignment`. Shrinks the storage, if needed, to match desired alignment.
@@ -145,13 +157,16 @@ namespace exec::__system_context_default_impl {
145157 }
146158 };
147159
148- struct __system_scheduler_impl : system_scheduler {
149- __system_scheduler_impl ()
160+ template <typename _BaseSchedulerContext>
161+ struct __system_scheduler_generic_impl : system_scheduler {
162+ __system_scheduler_generic_impl ()
150163 : __pool_scheduler_(__pool_.get_scheduler()) {
151164 }
152165 private:
166+ using __pool_scheduler_t = decltype (std::declval<_BaseSchedulerContext>().get_scheduler());
167+
153168 // / The underlying thread pool.
154- exec::static_thread_pool __pool_;
169+ _BaseSchedulerContext __pool_;
155170 __pool_scheduler_t __pool_scheduler_;
156171
157172 // ! Functor called by the `bulk` operation; sends a `start` signal to the frontend.
@@ -184,7 +199,8 @@ namespace exec::__system_context_default_impl {
184199 }
185200
186201 void
187- bulk_schedule (uint32_t __size, storage __storage, bulk_item_receiver* __r) noexcept override {
202+ bulk_schedule (uint32_t __size, storage __storage, bulk_item_receiver* __r) noexcept
203+ override {
188204 try {
189205 auto __sndr =
190206 stdexec::bulk (stdexec::schedule (__pool_scheduler_), __size, __bulk_functor{__r});
@@ -197,51 +213,74 @@ namespace exec::__system_context_default_impl {
197213 }
198214 };
199215
200- // / Keeps track of the object implementing the system context interfaces.
201- struct __instance_holder {
216+ // / Keeps track of the backends for the system context interfaces.
217+ template <typename _Interface, typename _Impl>
218+ struct __instance_data {
219+ // / Gets the current instance; if there is no instance, uses the current factory to create one.
220+ std::shared_ptr<_Interface> __get_current_instance () {
221+ // If we have a valid instance, return it.
222+ __acquire_instance_lock ();
223+ auto __r = __instance_;
224+ __release_instance_lock ();
225+ if (__r) {
226+ return __r;
227+ }
202228
203- // / Get the only instance of this class.
204- static __instance_holder& __singleton () {
205- static __instance_holder __this_instance_;
206- return __this_instance_;
207- }
229+ // Otherwise, create a new instance using the factory.
230+ // Note: we are lazy-loading the instance to avoid creating it if it is not needed.
231+ auto __new_instance = __factory_.load (std::memory_order_relaxed)();
208232
209- // / Get the currently selected system context object.
210- system_scheduler* __get_current_instance () const noexcept {
211- return __current_instance_;
233+ // Store the newly created instance.
234+ __acquire_instance_lock ();
235+ __instance_ = __new_instance;
236+ __release_instance_lock ();
237+ return __new_instance;
212238 }
213239
214- // / Allows changing the currently selected system context object; used for testing.
215- void __set_current_instance (system_scheduler* __instance) noexcept {
216- __current_instance_ = __instance;
240+ // / Set `__new_factory` as the new factory for `_Interface` and return the old one.
241+ __system_context_backend_factory<_Interface>
242+ __set_backend_factory (__system_context_backend_factory<_Interface> __new_factory) {
243+ // Replace the factory, keeping track of the old one.
244+ auto __old_factory = __factory_.exchange (__new_factory);
245+ // Create a new instance with the new factory.
246+ auto __new_instance = __new_factory ();
247+ // Replace the current instance with the new one.
248+ __acquire_instance_lock ();
249+ auto __old_instance = std::exchange (__instance_, __new_instance);
250+ __release_instance_lock ();
251+ // Make sure to delete the old instance after releasing the lock.
252+ __old_instance.reset ();
253+ return __old_factory;
217254 }
218255
219256 private:
220- __instance_holder () {
221- static __system_scheduler_impl __default_instance_;
222- __current_instance_ = &__default_instance_;
223- }
257+ std::atomic<bool > __instance_locked_{false };
258+ std::shared_ptr<_Interface> __instance_{nullptr };
259+ std::atomic<__system_context_backend_factory<_Interface>> __factory_{__default_factory};
224260
225- system_scheduler* __current_instance_;
226- };
261+ // / The default factory returns an instance of `_Impl`.
262+ static std::shared_ptr<_Interface> __default_factory () {
263+ return std::make_shared<_Impl>();
264+ }
227265
228- struct __system_context_replaceability_impl : __system_context_replaceability {
229- // ! Globally replaces the system scheduler backend.
230- // ! This needs to be called within `main()` and before the system scheduler is accessed.
231- void __set_system_scheduler (system_scheduler* __backend) noexcept override {
232- __instance_holder::__singleton ().__set_current_instance (__backend);
266+ void __acquire_instance_lock () {
267+ while (__instance_locked_.exchange (true , std::memory_order_acquire)) {
268+ // Spin until we acquire the lock.
269+ }
270+ }
271+ void __release_instance_lock () {
272+ __instance_locked_.store (false , std::memory_order_release);
233273 }
234274 };
235275
236- inline void * __default_query_system_context_interface (const __uuid& __id) noexcept {
237- if (__id == system_scheduler::__interface_identifier) {
238- return __instance_holder::__singleton ().__get_current_instance ();
239- } else if (__id == __system_context_replaceability::__interface_identifier) {
240- static __system_context_replaceability_impl __impl;
241- return &__impl;
242- }
276+ #if STDEXEC_ENABLE_LIBDISPATCH
277+ using __system_scheduler_impl = __system_scheduler_generic_impl<exec::libdispatch_queue>;
278+ #else
279+ using __system_scheduler_impl = __system_scheduler_generic_impl<exec::static_thread_pool>;
280+ #endif
243281
244- return nullptr ;
245- }
282+ // / The singleton to hold the `system_scheduler` instance.
283+ inline constinit __instance_data<system_scheduler, __system_scheduler_impl>
284+ __system_scheduler_singleton{};
246285
247286} // namespace exec::__system_context_default_impl
0 commit comments