// Copyright (c) 2015 Amanieu d'Antras // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #ifndef ASYNCXX_H_ # error "Do not include this header directly, include instead." #endif namespace async { // Exception thrown when an event_task is destroyed without setting a value struct LIBASYNC_EXPORT_EXCEPTION abandoned_event_task {}; namespace detail { // Common code for task and shared_task template class basic_task { // Reference counted internal task object detail::task_ptr internal_task; // Real result type, with void turned into fake_void typedef typename void_to_fake_void::type internal_result; // Type-specific task object typedef task_result internal_task_type; // Friend access friend async::task; friend async::shared_task; template friend typename T::internal_task_type* get_internal_task(const T& t); template friend void set_internal_task(T& t, task_ptr p); // Common code for get() void get_internal() const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); // If the task was canceled, throw the associated exception get_internal_task(*this)->wait_and_throw(); } // Common code for then() template typename continuation_traits::task_type then_internal(Sched& sched, Func&& f, Parent&& parent) const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); // Save a copy of internal_task because it might get moved into exec_func task_base* my_internal = internal_task.get(); // Create continuation typedef continuation_traits traits; typedef typename void_to_fake_void::type cont_internal_result; typedef continuation_exec_func::type, cont_internal_result, typename traits::decay_func, typename traits::is_value_cont, is_task::value> exec_func; typename traits::task_type cont; set_internal_task(cont, task_ptr(new task_func(std::forward(f), std::forward(parent)))); // Add the continuation to this task // Avoid an expensive ref-count modification since the task isn't shared yet get_internal_task(cont)->add_ref_unlocked(); get_internal_task(cont)->sched = std::addressof(sched); my_internal->add_continuation(sched, task_ptr(get_internal_task(cont))); return cont; } public: // Task result type typedef Result result_type; // Check if this task is not empty bool valid() const { return internal_task != nullptr; } // Query whether the task has finished executing bool ready() const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); return internal_task->ready(); } // Query whether the task has been canceled with an exception bool canceled() const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); return internal_task->state.load(std::memory_order_acquire) == task_state::canceled; } // Wait for the task to complete void wait() const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); internal_task->wait(); } // Get the exception associated with a canceled task std::exception_ptr get_exception() const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); if (internal_task->wait() == task_state::canceled) return get_internal_task(*this)->get_exception(); else return std::exception_ptr(); } }; // Common code for event_task specializations template class basic_event { // Reference counted internal task object detail::task_ptr internal_task; // Real result type, with void turned into fake_void typedef typename detail::void_to_fake_void::type internal_result; // Type-specific task object typedef detail::task_result internal_task_type; // Friend access friend async::event_task; template friend typename T::internal_task_type* get_internal_task(const T& t); // Common code for set() template bool set_internal(T&& result) const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); // Only allow setting the value once detail::task_state expected = detail::task_state::pending; if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) return false; LIBASYNC_TRY { // Store the result and finish get_internal_task(*this)->set_result(std::forward(result)); internal_task->finish(); } LIBASYNC_CATCH(...) { // At this point we have already committed to setting a value, so // we can't return the exception to the caller. If we did then it // could cause concurrent set() calls to fail, thinking a value has // already been set. Instead, we simply cancel the task with the // exception we just got. get_internal_task(*this)->cancel_base(std::current_exception()); } return true; } public: // Movable but not copyable basic_event(basic_event&& other) LIBASYNC_NOEXCEPT : internal_task(std::move(other.internal_task)) {} basic_event& operator=(basic_event&& other) LIBASYNC_NOEXCEPT { internal_task = std::move(other.internal_task); return *this; } // Main constructor basic_event() : internal_task(new internal_task_type) { internal_task->event_task_got_task = false; } // Cancel events if they are destroyed before they are set ~basic_event() { // This check isn't thread-safe but set_exception does a proper check if (internal_task && !internal_task->ready() && !internal_task->is_unique_ref(std::memory_order_relaxed)) { #ifdef LIBASYNC_NO_EXCEPTIONS // This will result in an abort if the task result is read set_exception(std::exception_ptr()); #else set_exception(std::make_exception_ptr(abandoned_event_task())); #endif } } // Get the task linked to this event. This can only be called once. task get_task() { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); LIBASYNC_ASSERT(!internal_task->event_task_got_task, std::logic_error, "get_task() called twice on event_task"); // Even if we didn't trigger an assert, don't return a task if one has // already been returned. task out; if (!internal_task->event_task_got_task) set_internal_task(out, internal_task); internal_task->event_task_got_task = true; return out; } // Cancel the event with an exception and cancel continuations bool set_exception(std::exception_ptr except) const { LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); // Only allow setting the value once detail::task_state expected = detail::task_state::pending; if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) return false; // Cancel the task get_internal_task(*this)->cancel_base(std::move(except)); return true; } }; } // namespace detail template class task: public detail::basic_task { public: // Movable but not copyable task() = default; task(task&& other) LIBASYNC_NOEXCEPT : detail::basic_task(std::move(other)) {} task& operator=(task&& other) LIBASYNC_NOEXCEPT { detail::basic_task::operator=(std::move(other)); return *this; } // Get the result of the task Result get() { this->get_internal(); // Move the internal state pointer so that the task becomes invalid, // even if an exception is thrown. detail::task_ptr my_internal = std::move(this->internal_task); return detail::fake_void_to_void(static_cast(my_internal.get())->get_result(*this)); } // Add a continuation to the task template typename detail::continuation_traits::task_type then(Sched& sched, Func&& f) { return this->then_internal(sched, std::forward(f), std::move(*this)); } template typename detail::continuation_traits::task_type then(Func&& f) { return then(::async::default_scheduler(), std::forward(f)); } // Create a shared_task from this task shared_task share() { LIBASYNC_ASSERT(this->internal_task, std::invalid_argument, "Use of empty task object"); shared_task out; detail::set_internal_task(out, std::move(this->internal_task)); return out; } }; template class shared_task: public detail::basic_task { // get() return value: const Result& -or- void typedef typename std::conditional< std::is_void::value, void, typename std::add_lvalue_reference< typename std::add_const::type >::type >::type get_result; public: // Movable and copyable shared_task() = default; // Get the result of the task get_result get() const { this->get_internal(); return detail::fake_void_to_void(detail::get_internal_task(*this)->get_result(*this)); } // Add a continuation to the task template typename detail::continuation_traits::task_type then(Sched& sched, Func&& f) const { return this->then_internal(sched, std::forward(f), *this); } template typename detail::continuation_traits::task_type then(Func&& f) const { return then(::async::default_scheduler(), std::forward(f)); } }; // Special task type which can be triggered manually rather than when a function executes. template class event_task: public detail::basic_event { public: // Movable but not copyable event_task() = default; event_task(event_task&& other) LIBASYNC_NOEXCEPT : detail::basic_event(std::move(other)) {} event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT { detail::basic_event::operator=(std::move(other)); return *this; } // Set the result of the task, mark it as completed and run its continuations bool set(const Result& result) const { return this->set_internal(result); } bool set(Result&& result) const { return this->set_internal(std::move(result)); } }; // Specialization for references template class event_task: public detail::basic_event { public: // Movable but not copyable event_task() = default; event_task(event_task&& other) LIBASYNC_NOEXCEPT : detail::basic_event(std::move(other)) {} event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT { detail::basic_event::operator=(std::move(other)); return *this; } // Set the result of the task, mark it as completed and run its continuations bool set(Result& result) const { return this->set_internal(result); } }; // Specialization for void template<> class event_task: public detail::basic_event { public: // Movable but not copyable event_task() = default; event_task(event_task&& other) LIBASYNC_NOEXCEPT : detail::basic_event(std::move(other)) {} event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT { detail::basic_event::operator=(std::move(other)); return *this; } // Set the result of the task, mark it as completed and run its continuations bool set() { return this->set_internal(detail::fake_void()); } }; // Task type returned by local_spawn() template class local_task { // Make sure the function type is callable typedef typename std::decay::type decay_func; static_assert(detail::is_callable::value, "Invalid function type passed to local_spawn()"); // Task result type typedef typename detail::remove_task()())>::type result_type; typedef typename detail::void_to_fake_void::type internal_result; // Task execution function type typedef detail::root_exec_func()())>::value> exec_func; // Task object embedded directly. The ref-count is initialized to 1 so it // will never be freed using delete, only when the local_task is destroyed. detail::task_func internal_task; // Friend access for local_spawn template friend local_task local_spawn(S& sched, F&& f); template friend local_task local_spawn(F&& f); // Constructor, used by local_spawn local_task(Sched& sched, Func&& f) : internal_task(std::forward(f)) { // Avoid an expensive ref-count modification since the task isn't shared yet internal_task.add_ref_unlocked(); detail::schedule_task(sched, detail::task_ptr(&internal_task)); } public: // Non-movable and non-copyable local_task(const local_task&) = delete; local_task& operator=(const local_task&) = delete; // Wait for the task to complete when destroying ~local_task() { wait(); // Now spin until the reference count drops to 1, since the scheduler // may still have a reference to the task. while (!internal_task.is_unique_ref(std::memory_order_acquire)) { #if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140612 // Some versions of libstdc++ (4.7 and below) don't include a // definition of std::this_thread::yield(). sched_yield(); #else std::this_thread::yield(); #endif } } // Query whether the task has finished executing bool ready() const { return internal_task.ready(); } // Query whether the task has been canceled with an exception bool canceled() const { return internal_task.state.load(std::memory_order_acquire) == detail::task_state::canceled; } // Wait for the task to complete void wait() { internal_task.wait(); } // Get the result of the task result_type get() { internal_task.wait_and_throw(); return detail::fake_void_to_void(internal_task.get_result(task())); } // Get the exception associated with a canceled task std::exception_ptr get_exception() const { if (internal_task.wait() == detail::task_state::canceled) return internal_task.get_exception(); else return std::exception_ptr(); } }; // Spawn a function asynchronously #if (__cplusplus >= 201703L) // Use std::invoke_result instead of std::result_of for C++17 or greater because std::result_of was deprecated in C++17 and removed in C++20 template task>>::type> spawn(Sched& sched, Func&& f) #else template task::type()>::type>::type> spawn(Sched& sched, Func&& f) #endif { // Using result_of in the function return type to work around bugs in the Intel // C++ compiler. // Make sure the function type is callable typedef typename std::decay::type decay_func; static_assert(detail::is_callable::value, "Invalid function type passed to spawn()"); // Create task typedef typename detail::void_to_fake_void()())>::type>::type internal_result; typedef detail::root_exec_func()())>::value> exec_func; task()())>::type> out; detail::set_internal_task(out, detail::task_ptr(new detail::task_func(std::forward(f)))); // Avoid an expensive ref-count modification since the task isn't shared yet detail::get_internal_task(out)->add_ref_unlocked(); detail::schedule_task(sched, detail::task_ptr(detail::get_internal_task(out))); return out; } template decltype(async::spawn(::async::default_scheduler(), std::declval())) spawn(Func&& f) { return async::spawn(::async::default_scheduler(), std::forward(f)); } // Create a completed task containing a value template task::type> make_task(T&& value) { task::type> out; detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); detail::get_internal_task(out)->set_result(std::forward(value)); detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); return out; } template task make_task(std::reference_wrapper value) { task out; detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); detail::get_internal_task(out)->set_result(value.get()); detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); return out; } inline task make_task() { task out; detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); return out; } // Create a canceled task containing an exception template task make_exception_task(std::exception_ptr except) { task out; detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); detail::get_internal_task(out)->set_exception(std::move(except)); detail::get_internal_task(out)->state.store(detail::task_state::canceled, std::memory_order_relaxed); return out; } // Spawn a very limited task which is restricted to the current function and // joins on destruction. Because local_task is not movable, the result must // be captured in a reference, like this: // auto&& x = local_spawn(...); template #ifdef __GNUC__ __attribute__((warn_unused_result)) #endif local_task local_spawn(Sched& sched, Func&& f) { // Since local_task is not movable, we construct it in-place and let the // caller extend the lifetime of the returned object using a reference. return {sched, std::forward(f)}; } template #ifdef __GNUC__ __attribute__((warn_unused_result)) #endif local_task local_spawn(Func&& f) { return {::async::default_scheduler(), std::forward(f)}; } } // namespace async