diff --git a/.github/workflows/stlab.yml b/.github/workflows/stlab.yml index 7f7712ca9..26d31c957 100644 --- a/.github/workflows/stlab.yml +++ b/.github/workflows/stlab.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - main + - main jobs: generate-matrix: @@ -67,7 +67,7 @@ jobs: ./emsdk install latest ./emsdk activate latest echo 'source "$HOME/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile - + # Override Emsdk's bundled node (14.18.2) to the GH Actions system installation (>= 16.16.0) sed -i "/^NODE_JS = .*/c\NODE_JS = '`which node`'" .emscripten echo "Overwrote .emscripten config file to:" @@ -122,7 +122,7 @@ jobs: run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 mkdir ..\build - cmake -S. -B../build -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake + cmake -S. -B../build -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake - name: Build // Unix if: ${{ startsWith(matrix.config.os, 'ubuntu') || startsWith(matrix.config.os, 'macos') }} diff --git a/stlab/concurrency/channel.hpp b/stlab/concurrency/channel.hpp index 2e7ce9d49..470b985dc 100755 --- a/stlab/concurrency/channel.hpp +++ b/stlab/concurrency/channel.hpp @@ -348,7 +348,7 @@ auto set_process_error(P& process, std::exception_ptr&& error) } template -auto set_process_error(P&, std::exception_ptr &&) +auto set_process_error(P&, std::exception_ptr&&) -> std::enable_if_t>, void> {} /**************************************************************************************************/ @@ -597,8 +597,8 @@ template { shared_process& _shared_process; - explicit - shared_process_sender_indexed(shared_process& sp) : _shared_process(sp) {} + explicit shared_process_sender_indexed(shared_process& sp) : + _shared_process(sp) {} void add_sender() override { ++_shared_process._sender_count; } @@ -652,9 +652,7 @@ struct shared_process_sender_helper; template struct shared_process_sender_helper, Args...> : shared_process_sender_indexed... { - - explicit - shared_process_sender_helper(shared_process& sp) : + explicit shared_process_sender_helper(shared_process& sp) : shared_process_sender_indexed(sp)... {} }; @@ -768,8 +766,6 @@ struct shared_process const std::tuple>...> _upstream; - - template shared_process(E&& e, F&& f) : shared_process_sender_helper, Args...>( @@ -954,12 +950,12 @@ struct shared_process std::chrono::nanoseconds::min()) broadcast(unwrap(*_process).yield()); else - execute_at(duration, - _executor)([_weak_this = make_weak_ptr(this->shared_from_this())] { - auto _this = _weak_this.lock(); - if (!_this) return; - _this->try_broadcast(); - }); + execute_at(duration, _executor)( + [_weak_this = make_weak_ptr(this->shared_from_this())]() noexcept { + auto _this = _weak_this.lock(); + if (!_this) return; + _this->try_broadcast(); + }); } /* @@ -978,25 +974,25 @@ struct shared_process } else { /* Schedule a timeout. */ _timeout_function_active = true; - execute_at(duration, - _executor)([_weak_this = make_weak_ptr(this->shared_from_this())] { - auto _this = _weak_this.lock(); - // It may be that the complete channel is gone in the meanwhile - if (!_this) return; - - // try_lock can fail spuriously - while (true) { - lock_t lock(_this->_timeout_function_control, std::try_to_lock); - if (!lock) continue; - - // we were cancelled - if (get_process_state(_this->_process).first != process_state::yield) { - _this->try_broadcast(); - _this->_timeout_function_active = false; + execute_at(duration, _executor)( + [_weak_this = make_weak_ptr(this->shared_from_this())]() noexcept { + auto _this = _weak_this.lock(); + // It may be that the complete channel is gone in the meanwhile + if (!_this) return; + + // try_lock can fail spuriously + while (true) { + lock_t lock(_this->_timeout_function_control, std::try_to_lock); + if (!lock) continue; + + // we were cancelled + if (get_process_state(_this->_process).first != process_state::yield) { + _this->try_broadcast(); + _this->_timeout_function_active = false; + } + return; } - return; - } - }); + }); } } catch (...) { // this catches exceptions during _process.await() and _process.yield() broadcast(std::move(std::current_exception())); @@ -1038,12 +1034,12 @@ struct shared_process std::chrono::nanoseconds::min()) broadcast(unwrap(*_process).yield()); else - execute_at(duration, - _executor)([_weak_this = make_weak_ptr(this->shared_from_this())] { - auto _this = _weak_this.lock(); - if (!_this) return; - _this->try_broadcast(); - }); + execute_at(duration, _executor)( + [_weak_this = make_weak_ptr(this->shared_from_this())]() noexcept { + auto _this = _weak_this.lock(); + if (!_this) return; + _this->try_broadcast(); + }); } /* @@ -1062,25 +1058,25 @@ struct shared_process } else { /* Schedule a timeout. */ _timeout_function_active = true; - execute_at(duration, - _executor)([_weak_this = make_weak_ptr(this->shared_from_this())] { - auto _this = _weak_this.lock(); - // It may be that the complete channel is gone in the meanwhile - if (!_this) return; - - // try_lock can fail spuriously - while (true) { - lock_t lock(_this->_timeout_function_control, std::try_to_lock); - if (!lock) continue; - - // we were cancelled - if (get_process_state(_this->_process).first != process_state::yield) { - _this->try_broadcast(); - _this->_timeout_function_active = false; + execute_at(duration, _executor)( + [_weak_this = make_weak_ptr(this->shared_from_this())]() noexcept { + auto _this = _weak_this.lock(); + // It may be that the complete channel is gone in the meanwhile + if (!_this) return; + + // try_lock can fail spuriously + while (true) { + lock_t lock(_this->_timeout_function_control, std::try_to_lock); + if (!lock) continue; + + // we were cancelled + if (get_process_state(_this->_process).first != process_state::yield) { + _this->try_broadcast(); + _this->_timeout_function_active = false; + } + return; } - return; - } - }); + }); } } catch (...) { // this catches exceptions during _process.await() and _process.yield() broadcast(std::move(std::current_exception())); @@ -1098,10 +1094,12 @@ struct shared_process REVISIT (sparent) : See above comments on step() and ensure consistency. What is this code doing, if we don't have a yield then it also assumes no await? + + This seems to be doing a lot for a (required) noexcept operation - are we sure? */ template - auto step() -> std::enable_if_t>> { + auto step() noexcept -> std::enable_if_t>> { using queue_t = typename Q::value_type; stlab::optional message; std::array do_cts; @@ -1135,7 +1133,7 @@ struct shared_process } void run() { - _executor([_p = make_weak_ptr(this->shared_from_this())] { + _executor([_p = make_weak_ptr(this->shared_from_this())]() noexcept { auto p = _p.lock(); if (p) p->template step(); }); @@ -1352,8 +1350,7 @@ auto zip(S s, R... r) { /**************************************************************************************************/ -struct buffer_size -{ +struct buffer_size { std::size_t _value; buffer_size(std::size_t b) : _value(b) {} }; @@ -1378,15 +1375,16 @@ struct annotated_process { F _f; annotations _annotations; - explicit annotated_process(executor_task_pair&& etp) : _f(std::move(etp._f)), _annotations(std::move(etp._executor)) {} + explicit annotated_process(executor_task_pair&& etp) : + _f(std::move(etp._f)), _annotations(std::move(etp._executor)) {} annotated_process(F f, const executor& e) : _f(std::move(f)), _annotations(e._executor) {} annotated_process(F f, buffer_size bs) : _f(std::move(f)), _annotations(bs._value) {} annotated_process(F f, executor&& e) : _f(std::move(f)), _annotations(std::move(e._executor)) {} annotated_process(F f, annotations&& a) : _f(std::move(f)), _annotations(std::move(a)) {} - annotated_process(executor_task_pair&& etp, buffer_size bs) : _f(std::move(etp._f)), _annotations(std::move(etp._executor), bs) {} - + annotated_process(executor_task_pair&& etp, buffer_size bs) : + _f(std::move(etp._f)), _annotations(std::move(etp._executor), bs) {} }; template @@ -1553,8 +1551,8 @@ class STLAB_NODISCARD() receiver { } auto operator|(sender send) { - return operator| - ([_send = std::move(send)](auto&& x) { _send(std::forward(x)); }); + return operator|( + [_send = std::move(send)](auto&& x) { _send(std::forward(x)); }); } }; diff --git a/stlab/concurrency/default_executor.hpp b/stlab/concurrency/default_executor.hpp index a4a966fd5..506aa4716 100644 --- a/stlab/concurrency/default_executor.hpp +++ b/stlab/concurrency/default_executor.hpp @@ -9,14 +9,17 @@ #ifndef STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP #define STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP -#include -#include #include + #include +#include +#include + #include #include #include +#include #if STLAB_TASK_SYSTEM(LIBDISPATCH) #include @@ -91,7 +94,7 @@ struct executor_type { using result_type = void; template - void operator()(F f) const { + auto operator()(F f) const -> std::enable_if_t> { using f_t = decltype(f); dispatch_group_async_f(detail::group()._group, @@ -224,7 +227,7 @@ class waiter { class notification_queue { struct element_t { std::size_t _priority; - task _task; + task _task; template element_t(F&& f, std::size_t priority) : _priority{priority}, _task{std::forward(f)} {} @@ -250,7 +253,7 @@ class notification_queue { } // Must be called under a lock with a non-empty _q, always returns a valid task - auto pop_not_empty() -> task { + auto pop_not_empty() -> task { auto result = std::move(_q.front()._task); std::pop_heap(begin(_q), end(_q), element_t::greater()); _q.pop_back(); @@ -258,7 +261,7 @@ class notification_queue { } public: - auto try_pop() -> task { + auto try_pop() -> task { lock_t lock{_mutex, std::try_to_lock}; if (!lock || _q.empty()) return nullptr; return pop_not_empty(); @@ -275,7 +278,7 @@ class notification_queue { return true; } - auto pop() -> std::pair> { + auto pop() -> std::pair> { lock_t lock{_mutex}; _waiting = true; while (_q.empty() && !_done && _waiting) @@ -341,7 +344,7 @@ class priority_task_system { void run(unsigned i) { stlab::set_current_thread_name("cc.stlab.default_executor"); while (true) { - task f; + task f; for (unsigned n = 0; n != _count && !f; ++n) { f = _q[(i + n) % _count].try_pop(); @@ -408,7 +411,7 @@ class priority_task_system { stlab::set_current_thread_name("cc.stlab.default_executor.expansion"); while (true) { - task f; + task f; for (unsigned n = 0; n != _count && !f; ++n) { f = _q[(i + n) % _count].try_pop(); @@ -458,7 +461,7 @@ template struct executor_type { using result_type = void; - void operator()(task f) const { + void operator()(task&& f) const { static task_system

only_task_system{[] { at_pre_exit([]() noexcept { only_task_system.join(); }); return task_system

{}; @@ -473,7 +476,7 @@ template struct executor_type { using result_type = void; - void operator()(task f) const { + void operator()(task&& f) const { pts().execute(P)>(std::move(f)); } }; diff --git a/stlab/concurrency/executor_base.hpp b/stlab/concurrency/executor_base.hpp index e49a73a11..693a9cf74 100644 --- a/stlab/concurrency/executor_base.hpp +++ b/stlab/concurrency/executor_base.hpp @@ -24,7 +24,7 @@ namespace stlab { inline namespace v1 { /**************************************************************************************************/ -using executor_t = std::function)>; +using executor_t = std::function)>; /* * returns an executor that will schedule any passed task to it to execute @@ -35,16 +35,17 @@ template > executor_t execute_at(std::chrono::duration duration, executor_t executor) { return [_duration = std::move(duration), _executor = std::move(executor)](auto f) mutable { if (_duration != std::chrono::duration{}) - system_timer(_duration, [_f = std::move(f), _executor = std::move(_executor)]() mutable { - _executor(std::move(_f)); - }); + system_timer(_duration, + [_f = std::move(f), _executor = std::move(_executor)]() mutable noexcept { + _executor(std::move(_f)); + }); else _executor(std::move(f)); }; } -[[deprecated("Use chrono::duration as parameter instead")]] -inline executor_t execute_at(std::chrono::steady_clock::time_point when, executor_t executor) { +[[deprecated("Use chrono::duration as parameter instead")]] inline executor_t execute_at( + std::chrono::steady_clock::time_point when, executor_t executor) { using namespace std::chrono; return execute_at(duration_cast(when - steady_clock::now()), std::move(executor)); } @@ -79,7 +80,6 @@ executor_task_pair operator&(F&& f, executor e) { return executor_task_pair{std::move(e._executor), std::forward(f)}; } - /**************************************************************************************************/ } // namespace v1 diff --git a/stlab/concurrency/future.hpp b/stlab/concurrency/future.hpp index 392e29302..ff814cd0a 100755 --- a/stlab/concurrency/future.hpp +++ b/stlab/concurrency/future.hpp @@ -256,7 +256,7 @@ struct shared_task { template struct shared_base> : std::enable_shared_from_this> { - using then_t = std::vector>>; + using then_t = std::vector>>; executor_t _executor; stlab::optional _result; @@ -294,7 +294,7 @@ struct shared_base> : std::enable_shared_from_this lock(_mutex); - if (!_ready) _then.emplace_back([](auto&&) {}, [_p = this->shared_from_this()] {}); + if (!_ready) _then.emplace_back([](auto&&) {}, [_p = this->shared_from_this()]() noexcept {}); } void set_exception(std::exception_ptr error) { @@ -361,7 +361,7 @@ struct shared_base> : std::enable_shared_from_this struct shared_base> : std::enable_shared_from_this> { - using then_t = std::pair>; + using then_t = std::pair>; executor_t _executor; stlab::optional _result; @@ -372,7 +372,7 @@ struct shared_base> : std::enable_shared_from_this< explicit shared_base(executor_t s) : _executor(std::move(s)) {} - void reset() { _then.second = task{}; } + void reset() { _then.second = task{}; } template auto recover(future&& p, F&& f) { @@ -399,7 +399,7 @@ struct shared_base> : std::enable_shared_from_this< void _detach() { std::unique_lock lock(_mutex); - if (!_ready) _then = then_t([](auto&&) {}, [_p = this->shared_from_this()] {}); + if (!_ready) _then = then_t([](auto&&) {}, [_p = this->shared_from_this()] () noexcept {}); } void set_exception(std::exception_ptr error) { @@ -438,7 +438,7 @@ struct shared_base> : std::enable_shared_from_this< template <> struct shared_base : std::enable_shared_from_this> { - using then_t = std::vector>>; + using then_t = std::vector>>; using result_type = void; executor_t _executor; @@ -461,7 +461,7 @@ struct shared_base : std::enable_shared_from_this> { void _detach() { std::unique_lock lock(_mutex); - if (!_ready) _then.emplace_back([](auto&&) {}, [_p = this->shared_from_this()] {}); + if (!_ready) _then.emplace_back([](auto&&) {}, [_p = this->shared_from_this()]() noexcept {}); } void set_exception(std::exception_ptr error) { @@ -518,7 +518,7 @@ struct shared : shared_base, shared_task { void add_promise() override { ++_promise_count; } - void operator()(Args... args) override { + void operator()(Args... args) noexcept override { if (!_f) return; try { @@ -582,7 +582,7 @@ class packaged_task { packaged_task& operator=(packaged_task&& x) noexcept = default; template - void operator()(A&&... args) const { + void operator()(A&&... args) const noexcept { auto p = _p.lock(); if (p) (*p)(std::forward(args)...); } diff --git a/stlab/concurrency/immediate_executor.hpp b/stlab/concurrency/immediate_executor.hpp index 96f15edc4..7c733dcc9 100644 --- a/stlab/concurrency/immediate_executor.hpp +++ b/stlab/concurrency/immediate_executor.hpp @@ -9,8 +9,7 @@ #ifndef STLAB_CONCURRENCY_IMMEDIATE_EXECUTOR_HPP #define STLAB_CONCURRENCY_IMMEDIATE_EXECUTOR_HPP -#include -#include +#include /**************************************************************************************************/ @@ -27,12 +26,7 @@ namespace detail { struct immediate_executor_type { template - void operator()(F&& f) const { - std::forward(f)(); - } - - template - void operator()(std::chrono::steady_clock::time_point, F&& f) const { + auto operator()(F&& f) const -> std::enable_if_t> { std::forward(f)(); } }; diff --git a/stlab/concurrency/main_executor.hpp b/stlab/concurrency/main_executor.hpp index 54d9ea9c8..adc0b7bef 100644 --- a/stlab/concurrency/main_executor.hpp +++ b/stlab/concurrency/main_executor.hpp @@ -9,20 +9,23 @@ #ifndef STLAB_CONCURRENCY_MAIN_EXECUTOR_HPP #define STLAB_CONCURRENCY_MAIN_EXECUTOR_HPP +#include #include #include #if STLAB_MAIN_EXECUTOR(QT5) || STLAB_MAIN_EXECUTOR(QT6) #include -#if (STLAB_MAIN_EXECUTOR(QT5) && (QT_VERSION < QT_VERSION_CHECK(5,0,0) || QT_VERSION >= QT_VERSION_CHECK(6,0,0)) || \ - STLAB_MAIN_EXECUTOR(QT6) && (QT_VERSION < QT_VERSION_CHECK(6,0,0) || QT_VERSION >= QT_VERSION_CHECK(7,0,0))) +#if (STLAB_MAIN_EXECUTOR(QT5) && \ + (QT_VERSION < QT_VERSION_CHECK(5, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) || \ + STLAB_MAIN_EXECUTOR(QT6) && \ + (QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(7, 0, 0))) #error "Mismatching Qt versions" #endif #include #include -#include #include +#include #elif STLAB_MAIN_EXECUTOR(LIBDISPATCH) #include #elif STLAB_MAIN_EXECUTOR(EMSCRIPTEN) @@ -82,7 +85,7 @@ class main_executor_type { public: template - void operator()(F f) const { + auto operator()(F f) const -> std::enable_if_t> { auto event = std::make_unique(); event->set_task(std::move(f)); auto receiver = event->receiver(); @@ -98,7 +101,7 @@ struct main_executor_type { using result_type = void; template - void operator()(F f) const { + auto operator()(F f) const -> std::enable_if_t> { using f_t = decltype(f); dispatch_async_f(dispatch_get_main_queue(), new f_t(std::move(f)), [](void* f_) { @@ -115,33 +118,32 @@ struct main_executor_type { using result_type = void; template - void operator()(F&& f) const { + auto operator()(F&& f) const -> std::enable_if_t> { using function_type = typename std::remove_reference::type; auto p = new function_type(std::forward(f)); - /* - `emscripten_async_run_in_main_runtime_thread()` schedules a function to run on the main - JS thread, however, the code can be executed at any POSIX thread cancelation point if - wasm code is executing on the JS main thread. - Executing the code from a POSIX thread cancelation point can cause problems, including - deadlocks and data corruption. Consider: - ``` - mutex.lock(); // <-- If reentered, would deadlock here - new T; // <-- POSIX cancelation point, could reenter - ``` - The call to `emscripten_async_call()` bounces the call to execute as part of the main - run-loop on the current (main) thread. This avoids nasty reentrancy issues if executed - from a POSIX thread cancelation point. - */ + /* + `emscripten_async_run_in_main_runtime_thread()` schedules a function to run on the main + JS thread, however, the code can be executed at any POSIX thread cancelation point if + wasm code is executing on the JS main thread. + Executing the code from a POSIX thread cancelation point can cause problems, including + deadlocks and data corruption. Consider: + ``` + mutex.lock(); // <-- If reentered, would deadlock here + new T; // <-- POSIX cancelation point, could reenter + ``` + The call to `emscripten_async_call()` bounces the call to execute as part of the main + run-loop on the current (main) thread. This avoids nasty reentrancy issues if executed + from a POSIX thread cancelation point. + */ emscripten_async_run_in_main_runtime_thread( - EM_FUNC_SIG_VI, - static_cast([](void* f_) { + EM_FUNC_SIG_VI, static_cast([](void* f_) { emscripten_async_call( [](void* f_) { auto f = static_cast(f_); // Note the absence of exception handling. - // Operations queued to the task system cannot throw as a precondition. + // Operations queued to the task system cannot throw as a precondition. // We use packaged tasks to marshal exceptions. (*f)(); delete f; @@ -159,7 +161,7 @@ struct main_executor_type { using result_type = void; template - void operator()(F f) const { } + void operator()(F f) const {} }; #endif diff --git a/stlab/concurrency/serial_queue.hpp b/stlab/concurrency/serial_queue.hpp index a8878bd54..97ed65775 100644 --- a/stlab/concurrency/serial_queue.hpp +++ b/stlab/concurrency/serial_queue.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -41,8 +42,8 @@ namespace detail { /**************************************************************************************************/ class serial_instance_t : public std::enable_shared_from_this { - using executor_t = std::function&&)>; - using queue_t = std::deque>; + using executor_t = std::function&&)>; + using queue_t = std::deque>; using lock_t = std::lock_guard; std::mutex _m; @@ -80,7 +81,7 @@ class serial_instance_t : public std::enable_shared_from_this pop_front_unsafe(local_queue)(); } - if (!empty()) _executor([_this(shared_from_this())]() { _this->all(); }); + if (!empty()) _executor([_this(shared_from_this())]() noexcept { _this->all(); }); } void single() { @@ -90,7 +91,7 @@ class serial_instance_t : public std::enable_shared_from_this f(); - if (!empty()) _executor([_this(shared_from_this())]() { _this->single(); }); + if (!empty()) _executor([_this(shared_from_this())]() noexcept { _this->single(); }); } // The kickstart allows us to grab a pointer to either the single or all @@ -125,7 +126,7 @@ class serial_instance_t : public std::enable_shared_from_this }); if (!running) { - _executor([_this(shared_from_this())]() { _this->kickstart(); }); + _executor([_this(shared_from_this())]() noexcept { _this->kickstart(); }); } } @@ -149,7 +150,10 @@ class serial_queue_t { [_e = std::move(e)](auto&& f) { _e(std::forward(f)); }, mode)) {} auto executor() const { - return [_impl = _impl](auto&& f) { _impl->enqueue(std::forward(f)); }; + return [_impl = + _impl](auto&& f) -> std::enable_if_t> { + _impl->enqueue(std::forward(f)); + }; } template diff --git a/stlab/concurrency/system_timer.hpp b/stlab/concurrency/system_timer.hpp index 6ac84f376..1c85da9cc 100755 --- a/stlab/concurrency/system_timer.hpp +++ b/stlab/concurrency/system_timer.hpp @@ -11,11 +11,11 @@ /**************************************************************************************************/ -#include #include #include #include +#include #if STLAB_TASK_SYSTEM(LIBDISPATCH) #include @@ -23,18 +23,13 @@ #include #include #elif STLAB_TASK_SYSTEM(PORTABLE) - #include #include #include #include +#endif #include -// REVISIT (sparent) : for testing only -#if 0 && __APPLE__ -#include -#endif -#endif /**************************************************************************************************/ @@ -55,28 +50,27 @@ namespace detail { struct system_timer_type { using result_type = void; - template - [[deprecated("Use chrono::duration as parameter instead")]] - void operator()(std::chrono::steady_clock::time_point when, F f) const { + [[deprecated("Use chrono::duration as parameter instead")]] void operator()( + std::chrono::steady_clock::time_point when, F f) const { using namespace std::chrono; operator()(when - steady_clock::now(), std::move(f)); } template > - void operator()(std::chrono::duration duration, F f) const { + auto operator()(std::chrono::duration duration, F f) const + -> std::enable_if_t> { using namespace std::chrono; using f_t = decltype(f); - dispatch_after_f( - dispatch_time(0, duration_cast(duration).count()), - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), new f_t(std::move(f)), - [](void* f_) { - auto f = static_cast(f_); - (*f)(); - delete f; - }); + dispatch_after_f(dispatch_time(0, duration_cast(duration).count()), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + new f_t(std::move(f)), [](void* f_) { + auto f = static_cast(f_); + (*f)(); + delete f; + }); } }; @@ -108,16 +102,16 @@ class system_timer { CloseThreadpool(_pool); } - template - [[deprecated("Use chrono::duration as parameter instead")]] - void operator()(std::chrono::steady_clock::time_point when, F&& f) { + [[deprecated("Use chrono::duration as parameter instead")]] void operator()( + std::chrono::steady_clock::time_point when, F&& f) { using namespace std::chrono; operator()(when - steady_clock::now(), std::forward(f)); } template > - void operator()(std::chrono::duration duration, F&& f) { + auto operator()(std::chrono::duration duration, F&& f) + -> std::enable_if_t> { using namespace std::chrono; auto timer = CreateThreadpoolTimer(&timer_callback_impl, new F(std::forward(f)), &_callBackEnvironment); @@ -140,7 +134,7 @@ class system_timer { (*f)(); } - template > + template > FILETIME duration_to_FILETIME(std::chrono::duration duration) const { using namespace std::chrono; FILETIME ft = {0, 0}; @@ -170,7 +164,7 @@ class system_timer { #elif STLAB_TASK_SYSTEM(PORTABLE) class system_timer { - using element_t = std::pair>; + using element_t = std::pair>; using queue_t = std::vector; using lock_t = std::unique_lock; @@ -191,7 +185,7 @@ class system_timer { void timed_queue_run() { while (true) { - task task; + task task; { lock_t lock(_timed_queue_mutex); @@ -227,14 +221,15 @@ class system_timer { } template - [[deprecated("Use chrono::duration as parameter instead")]] - void operator()(std::chrono::steady_clock::time_point when, F&& f) { + [[deprecated("Use chrono::duration as parameter instead")]] void operator()( + std::chrono::steady_clock::time_point when, F&& f) { using namespace std::chrono; operator()(when - steady_clock::now(), std::move(f)); } template > - void operator()(std::chrono::duration duration, F&& f) { + auto operator()(std::chrono::duration duration, F&& f) + -> std::enable_if_t> { lock_t lock(_timed_queue_mutex); _timed_queue.emplace_back(std::chrono::steady_clock::now() + duration, std::forward(f)); std::push_heap(std::begin(_timed_queue), std::end(_timed_queue), greater_first()); @@ -250,19 +245,19 @@ class system_timer { struct system_timer_type { using result_type = void; - + static system_timer& get_system_timer() { static system_timer only_system_timer; return only_system_timer; } - [[deprecated("Use chrono::duration as parameter instead")]] - void operator()(std::chrono::steady_clock::time_point when, task f) const { + [[deprecated("Use chrono::duration as parameter instead")]] void operator()( + std::chrono::steady_clock::time_point when, task&& f) const { operator()(when - std::chrono::steady_clock().now(), std::move(f)); } template > - void operator()(std::chrono::duration duration, task f) const { + void operator()(std::chrono::duration duration, task&& f) const { get_system_timer()(duration, std::move(f)); } }; diff --git a/stlab/concurrency/task.hpp b/stlab/concurrency/task.hpp index 074f6b8d7..b29b5c1ee 100644 --- a/stlab/concurrency/task.hpp +++ b/stlab/concurrency/task.hpp @@ -34,11 +34,9 @@ inline namespace v1 { tasks are functions with a mutable call operator to support moving items through for single invocations. */ -template -class task; -template -class task { +template +class task_ { template constexpr static bool maybe_empty = std::is_pointer>::value || std::is_member_pointer>::value || @@ -55,14 +53,14 @@ class task { } struct concept_t { - void (*dtor)(void*); + void (*dtor)(void*) noexcept; void (*move_ctor)(void*, void*) noexcept; const std::type_info& (*target_type)() noexcept; void* (*pointer)(void*) noexcept; const void* (*const_pointer)(const void*) noexcept; }; - using invoke_t = R (*)(void*, Args...); + using invoke_t = R (*)(void*, Args...) noexcept(NoExcept); template struct model; @@ -73,7 +71,7 @@ class task { model(G&& f) : _f(std::forward(f)) {} model(model&&) noexcept = delete; - static void dtor(void* self) { static_cast(self)->~model(); } + static void dtor(void* self) noexcept { static_cast(self)->~model(); } static void move_ctor(void* self, void* p) noexcept { new (p) model(std::move(static_cast(self)->_f)); } @@ -85,7 +83,7 @@ class task { signature to the actual captured model. */ - static auto invoke(void* self, Args... args) -> R { + static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R { return (static_cast(self)->_f)(std::forward(args)...); } @@ -110,7 +108,7 @@ class task { model(G&& f) : _p(std::make_unique(std::forward(f))) {} model(model&&) noexcept = default; - static void dtor(void* self) { static_cast(self)->~model(); } + static void dtor(void* self) noexcept { static_cast(self)->~model(); } static void move_ctor(void* self, void* p) noexcept { new (p) model(std::move(*static_cast(self))); } @@ -122,11 +120,10 @@ class task { signature to the actual captured model. */ - static auto invoke(void* self, Args... args) -> R { + static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R { return (*static_cast(self)->_p)(std::forward(args)...); } - static auto target_type() noexcept -> const std::type_info& { return typeid(F); } static auto pointer(void* self) noexcept -> void* { return static_cast(self)->_p.get(); @@ -147,9 +144,15 @@ class task { }; // empty (default) vtable - static void dtor(void*) {} + static void dtor(void*) noexcept {} static void move_ctor(void*, void*) noexcept {} - static auto invoke(void*, Args...) -> R { throw std::bad_function_call(); } + static auto invoke(void*, Args...) noexcept(NoExcept) -> R { + if constexpr (NoExcept) { + std::terminate(); + } else { + throw std::bad_function_call(); + } + } static auto target_type_() noexcept -> const std::type_info& { return typeid(void); } static auto pointer(void*) noexcept -> void* { return nullptr; } static auto const_pointer(const void*) noexcept -> const void* { return nullptr; } @@ -187,15 +190,16 @@ class task { public: using result_type = R; - constexpr task() noexcept = default; - constexpr task(std::nullptr_t) noexcept : task() {} - task(const task&) = delete; - task(task&& x) noexcept : _vtable_ptr(x._vtable_ptr), _invoke(x._invoke) { + constexpr task_() noexcept = default; + constexpr task_(std::nullptr_t) noexcept : task_() {} + task_(const task_&) = delete; + task_(task_&& x) noexcept : _vtable_ptr(x._vtable_ptr), _invoke(x._invoke) { _vtable_ptr->move_ctor(&x._model, &_model); } - template , task>::value, bool> = true> - task(F&& f) { + template , bool> = true> + task_(F&& f) { using small_t = model, true>; using large_t = model, false>; using model_t = std::conditional_t<(sizeof(small_t) <= small_size) && @@ -209,11 +213,11 @@ class task { _invoke = &model_t::invoke; } - ~task() { _vtable_ptr->dtor(&_model); }; + ~task_() { _vtable_ptr->dtor(&_model); }; - task& operator=(const task&) = delete; + task_& operator=(const task_&) = delete; - task& operator=(task&& x) noexcept { + task_& operator=(task_&& x) noexcept { _vtable_ptr->dtor(&_model); _vtable_ptr = x._vtable_ptr; _invoke = x._invoke; @@ -221,14 +225,16 @@ class task { return *this; } - task& operator=(std::nullptr_t) noexcept { return *this = task(); } + task_& operator=(std::nullptr_t) noexcept { return *this = task_(); } template - task& operator=(F&& f) { - return *this = task(std::forward(f)); + auto operator=(F&& f) + -> std::enable_if_t, + task_&> { + return *this = task_(std::forward(f)); } - void swap(task& x) noexcept { std::swap(*this, x); } + void swap(task_& x) noexcept { std::swap(*this, x); } explicit operator bool() const { return _vtable_ptr->const_pointer(&_model) != nullptr; } @@ -248,70 +254,34 @@ class task { } template - auto operator()(Brgs&&... brgs) { return _invoke(&_model, std::forward(brgs)...); } + auto operator()(Brgs&&... brgs) noexcept(NoExcept) { + return _invoke(&_model, std::forward(brgs)...); + } - friend inline void swap(task& x, task& y) { return x.swap(y); } - friend inline bool operator==(const task& x, std::nullptr_t) { return !static_cast(x); } - friend inline bool operator==(std::nullptr_t, const task& x) { return !static_cast(x); } - friend inline bool operator!=(const task& x, std::nullptr_t) { return static_cast(x); } - friend inline bool operator!=(std::nullptr_t, const task& x) { return static_cast(x); } + friend inline void swap(task_& x, task_& y) { return x.swap(y); } + friend inline bool operator==(const task_& x, std::nullptr_t) { return !static_cast(x); } + friend inline bool operator==(std::nullptr_t, const task_& x) { return !static_cast(x); } + friend inline bool operator!=(const task_& x, std::nullptr_t) { return static_cast(x); } + friend inline bool operator!=(std::nullptr_t, const task_& x) { return static_cast(x); } }; -#if STLAB_CPP_VERSION_LESS_THAN(17) - -// In C++17 constexpr implies inline and these definitions are deprecated - -#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__) -template -const typename task::concept_t task::_vtable = { - dtor, move_ctor, target_type_, pointer, const_pointer}; - -template -const typename task::invoke_t task::_invoke = _invoke; -#else -template -const typename task::concept_t task::_vtable; -#endif - -#ifdef _MSC_VER - -template -template -const typename task::concept_t task::model::_vtable; - -template -template -const typename task::concept_t task::model::_vtable; - -#else - -#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__) - -template -template -const typename task::concept_t task::template model::_vtable = { - dtor, move_ctor, target_type, pointer, const_pointer}; +/**************************************************************************************************/ -template -template -const typename task::concept_t task::template model::_vtable = { - dtor, move_ctor, target_type, pointer, const_pointer}; +template