Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
async_task_promise.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include "async_errors.hpp"
9#include "async_node.hpp"
10#include "async_task.hpp"
11#include "scheduler.hpp"
12#include <atomic>
13#include <coroutine>
14#include <exception>
15#include <limits>
16#include <memory>
17#include <optional>
18#include <type_traits>
19#include <vector>
20
21namespace uni20::async
22{
23
24class TaskFactory;
25class EpochContext;
26
29{};
30
32template <typename T>
33concept CudaTaskAwaitable = std::derived_from<std::remove_cvref_t<T>, CudaTaskAwaiterTag>;
34
38void propagate_unhandled_writer_exception(EpochContext* epoch, std::exception_ptr eptr) noexcept;
39
47template <typename Ret>
48concept AwaitSuspendResult = std::same_as<Ret, void> || std::same_as<Ret, BasicTask>;
49
60template <typename T>
61concept TaskAwaitable = requires(T a, BasicTask task) {
62 { a.await_suspend(std::move(task)) } noexcept -> AwaitSuspendResult;
63};
64
70template <typename T>
71concept TaskFactoryChildAwaitable = requires(T a, BasicTask task) {
72 { a.await_suspend(std::move(task)) } noexcept -> std::same_as<void>;
73};
74
86template <typename T>
87concept TaskFactoryAwaitable = requires(T a, TaskFactory factory) {
88 { a.await_suspend(std::move(factory)) } noexcept -> std::same_as<void>;
89 { a.num_awaiters() } -> std::convertible_to<int>;
90};
91
93template <TaskAwaitable A> struct TaskAwaiter;
94
96template <TaskFactoryAwaitable A> struct TaskFactoryAwaiter;
97
100 public:
103 {
107 std::shared_ptr<EpochContext> epoch{};
108 bool explicit_sink{false};
109 };
110
112 std::atomic<bool> started_{false};
113
118
122 std::exception_ptr* continuation_exception_ = nullptr;
123
127 std::atomic<int> awaiter_count_ = 0;
128
133 std::atomic<bool> exception_{false};
134 std::exception_ptr eptr_ = nullptr;
135
138 std::atomic<bool> cancel_on_resume_{false};
139
140 static constexpr int kNoPreferredNumaNode = std::numeric_limits<int>::min();
141
144
145 // debugging / DAG info
146 std::string Name; // function name of the coroutine
147 uint64_t Instance; // instance number, global
149
150#if UNI20_DEBUG_DAG
151 // For debug tracking the DAG, we store the nodes of the incoming (ReadBuffer) and outgoing (WriteBuffer) objects.
152 std::vector<NodeInfo const*> ReadDependencies;
153 std::vector<NodeInfo const*> WriteDependencies;
154#endif
155
156 // /// \brief To propogate exceptions and cancellations to the appropriate awaiter, whenever we suspend
157 // /// we stash the awaiter here, so we can pass on set_cancel() and set_exception()
158 // AsyncAwaiter* current_awaiter_ = nullptr;
159 //
160 // void set_current_awaiter(AsyncAwaiter* a) { current_awaiter_ = a; }
161 //
162 // void set_cancel() noexcept { cancel_.store(true, std::memory_order_release); }
163 //
164 // void set_exception(std::exception_ptr e) noexcept
165 // {
166 // eptr_ = e;
167 // // write to the atomic, mostly for the memory barrier
168 // awaiter_has_error_.store(false, std::memory_order_release);
169 // }
170 //
171 // bool is_cancel_on_resume() const noexcept { return awaiter_has_error_.load(std::memory_order_acquire); }
172 //
173 // std::exception_ptr is_exception_on_resume() const noexcept
174 // {
175 // return awaiter_has_error_.load(std::memory_order_acquire) ? nullptr : eptr_;
176 // }
177
181 template <typename... Args> TaskPromiseBase(Args&&... args)
182 {
183 // For each parameter, detect ReadBuffer / WriteBuffer
184 (ProcessCoroutineArgument(this, args), ...);
185 }
186
189 {
190 auto c = continuation_;
191 continuation_ = {};
192 continuation_exception_ = nullptr;
193 auto handle = this->self();
195 return c;
196 }
197
199 {
202 }
203
206 bool release_awaiter() noexcept { return awaiter_count_.fetch_sub(1, std::memory_order_acq_rel) == 1; }
207
211 bool release_awaiter(int count) noexcept
212 {
213 return awaiter_count_.fetch_sub(count, std::memory_order_acq_rel) == count;
214 }
215
218 int add_awaiter() noexcept { return awaiter_count_.fetch_add(1, std::memory_order_relaxed); }
219
223 int add_awaiter(int count) noexcept { return awaiter_count_.fetch_add(count, std::memory_order_relaxed); }
224
227 void set_exception(std::exception_ptr e) noexcept
228 {
229 if (!exception_.exchange(true, std::memory_order_acq_rel))
230 {
231 eptr_ = e;
232 }
233 }
234
239 void register_exception_sink(ExceptionSinkNode& node, std::shared_ptr<EpochContext> epoch, bool explicit_sink)
240 {
241 if (node.owner) node.owner->unregister_exception_sink(node, false);
242 if (!epoch) return;
243
244 node.owner = this;
245 node.epoch = std::move(epoch);
246 node.explicit_sink = explicit_sink;
247 node.prev = nullptr;
250 exception_sinks_head_ = &node;
251 }
252
256 void unregister_exception_sink(ExceptionSinkNode& node, bool from_destructor) noexcept
257 {
258 if (node.owner != this) return;
259 if (from_destructor && node.explicit_sink && std::uncaught_exceptions() > 0)
260 {
261 CHECK(
262 false,
263 "propagate_exceptions_to sink destroyed during exception unwinding before coroutine unhandled_exception()");
264 }
265
266 if (node.prev)
267 node.prev->next = node.next;
268 else
270
271 if (node.next) node.next->prev = node.prev;
272
273 node.owner = nullptr;
274 node.prev = nullptr;
275 node.next = nullptr;
276 node.epoch.reset();
277 node.explicit_sink = false;
278 }
279
282 std::exception_ptr get_exception() noexcept
283 {
284 if (exception_.load(std::memory_order_acquire))
285 return eptr_;
286 else
287 return nullptr;
288 }
289
293 {
294 if (exception_.load(std::memory_order_acquire)) std::rethrow_exception(eptr_);
295 }
296
298 void set_cancel_on_resume() noexcept { cancel_on_resume_.store(true, std::memory_order_release); }
299
302 [[nodiscard]] bool is_cancel_on_resume() const noexcept
303 {
304 return cancel_on_resume_.load(std::memory_order_acquire);
305 }
306
308 template <TaskAwaitable A> auto await_transform(A& a);
309 template <TaskAwaitable A> auto await_transform(A&& a);
310
312 template <TaskFactoryAwaitable A> auto await_transform(A& a);
313 template <TaskFactoryAwaitable A> auto await_transform(A&& a);
314
319 template <typename Task>
320 requires std::derived_from<std::remove_cvref_t<Task>, BasicTask>
321 Task&& await_transform(Task&& task) noexcept
322 {
323 return std::forward<Task>(task);
324 }
325
328 template <typename T> auto await_transform(T&&)
329 {
330 static_assert(sizeof(T) == 0,
331 "co_await expression does not match any known TaskAwaitable or TaskFactoryAwaitable type.");
332 }
333
336 void set_preferred_numa_node(std::optional<int> node) noexcept
337 {
338 preferred_numa_node_.store(node ? *node : kNoPreferredNumaNode, std::memory_order_release);
339 }
340
343 [[nodiscard]] std::optional<int> preferred_numa_node() const noexcept
344 {
345 int node = preferred_numa_node_.load(std::memory_order_acquire);
346 if (node == kNoPreferredNumaNode) return std::nullopt;
347 return node;
348 }
349
351 void mark_started() noexcept { started_.store(true, std::memory_order_release); }
352
354 [[nodiscard]] bool has_started() const noexcept { return started_.load(std::memory_order_acquire); }
355
357 [[nodiscard]] IScheduler* scheduler() const noexcept { return sched_; }
358
360 [[nodiscard]] TaskHandle self() noexcept
361 {
362 DEBUG_PRECONDITION(self_);
363 return self_;
364 }
365
367 static void note_running(TaskHandle handle) noexcept { TaskRegistry::mark_running(handle.coroutine()); }
368
371
372#if UNI20_DEBUG_DAG
377 template <typename A> static void note_await_dependency(TaskHandle handle, A const& awaitable)
378 {
379 auto record = [coroutine = handle.coroutine()](NodeInfo const* node, TaskRegistry::EpochTaskRole role) {
380 TaskRegistry::record_await_dependency(coroutine, node, role);
381 };
382 if constexpr (requires { awaitable.debug_each_dependency(record); })
383 {
384 awaitable.debug_each_dependency(record);
385 }
386 else if constexpr (requires {
387 awaitable.node();
388 awaitable.debug_task_role();
389 })
390 {
391 TaskRegistry::record_await_dependency(handle.coroutine(), awaitable.node(), awaitable.debug_task_role());
392 }
393 }
394#endif
395
397 static void note_leaked(TaskHandle handle) noexcept { TaskRegistry::leak_task(handle.coroutine()); }
398
401 {
403 handle.coroutine().resume();
404 }
405
407 static void destroy_and_track(TaskHandle handle) noexcept
408 {
410 handle.coroutine().destroy();
411 }
412
418 static void prepare_nested_route(TaskHandle parent, TaskHandle child);
419
423 [[nodiscard]] static bool can_transfer_directly(TaskHandle from, TaskHandle to) noexcept;
424
429 static std::coroutine_handle<> resolve_await_suspend_result(TaskHandle current, BasicTask&& task)
430 {
431 if (!task)
432 {
433 note_suspended(current);
434 return std::noop_coroutine();
435 }
436
437 auto nested = task.release_ownership();
438 CHECK(nested, "coroutine handle was not exclusively owned!");
439
440 if (nested == current)
441 {
442 note_running(current);
443 return current.coroutine();
444 }
445
446 prepare_nested_route(current, nested);
447 nested.promise().continuation_ = current;
448 note_suspended(current);
449
450 if (!can_transfer_directly(current, nested))
451 {
452 auto nested_task = nested.promise().take_ownership();
453 BasicTask::reschedule(std::move(nested_task));
454 return std::noop_coroutine();
455 }
456
457 nested.promise().mark_started();
458 note_running(nested);
459 return nested.coroutine();
460 }
461
463 template <TaskAwaitable A> static auto suspend_task_awaitable(TaskHandle current, A& awaitable);
464
466 template <TaskFactoryAwaitable A> static void suspend_factory_awaitable(TaskHandle current, A& awaitable);
467
473 {
474 [[maybe_unused]] int prior_count = this->add_awaiter();
475 DEBUG_CHECK_EQUAL(prior_count, 0, "expected handle to be previously unowned!");
476 return BasicTask(this->self(), BasicTask::construction_key{});
477 }
478
484
488 TaskHandle release_ownership() { return this->release_awaiter() ? this->self() : TaskHandle{}; }
489
491 constexpr TaskPromiseBase() noexcept = default;
492
494 template <TaskPromise Promise>
495 [[nodiscard]] TaskHandle initialize_task(std::coroutine_handle<Promise> handle) noexcept
496 {
497 DEBUG_PRECONDITION(!self_);
498 self_ = TaskHandle::from(handle);
499 this->add_awaiter();
501#if UNI20_DEBUG_DAG
502 TaskRegistry::record_task_dependencies(self_.coroutine(), ReadDependencies, WriteDependencies);
503#endif
504 return self_;
505 }
506
508 auto initial_suspend() noexcept
509 {
510 struct InitialAwaiter
511 {
512 TaskPromiseBase* promise;
513
514 [[nodiscard]] constexpr bool await_ready() noexcept { return false; }
515 void await_suspend(std::coroutine_handle<>) noexcept { TaskPromiseBase::note_suspended(promise->self()); }
516 constexpr void await_resume() noexcept {}
517 };
518 return InitialAwaiter{this};
519 }
520
524
525 auto final_suspend() noexcept
526 {
527 struct FinalAwaiter
528 {
529 TaskPromiseBase* promise;
530
531 [[nodiscard]] constexpr bool await_ready() noexcept { return false; }
532
533 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept
534 {
535 auto completed = promise->self();
536 auto continuation = std::exchange(promise->continuation_, {});
537 auto* continuation_exception = std::exchange(promise->continuation_exception_, nullptr);
538 bool cancelled = promise->is_cancel_on_resume();
539 auto eptr = promise->get_exception();
540 bool const direct_transfer =
541 continuation && TaskPromiseBase::can_transfer_directly(completed, continuation);
542 TRACE_MODULE(ASYNC, "Final suspend of coroutine", completed.coroutine(), continuation.coroutine(),
543 cancelled);
544
545 TaskPromiseBase::destroy_and_track(completed);
546
547 TRACE_MODULE(ASYNC, "Destroy is done");
548
549 if (cancelled)
550 {
551 while (continuation)
552 continuation = continuation.promise().destroy_with_continuation();
553 }
554
555 if (continuation)
556 {
557 if (eptr)
558 {
559 if (continuation_exception)
560 *continuation_exception = eptr;
561 else
562 continuation.promise().set_exception(eptr);
563 }
564
565 if (!direct_transfer)
566 {
567 auto continuation_task = continuation.promise().take_ownership();
568 BasicTask::reschedule(std::move(continuation_task));
569 return std::noop_coroutine();
570 }
571
572 TaskPromiseBase::note_running(continuation);
573 return continuation.coroutine();
574 }
575 else
576 return std::noop_coroutine();
577 }
578
579 void await_resume() noexcept {}
580 };
581 return FinalAwaiter{this};
582 }
583
585 constexpr void return_void() noexcept {}
586
589 {
590 try
591 {
592 throw;
593 }
594 catch (task_cancelled const&)
595 {
596 this->set_cancel_on_resume();
597 }
598 catch (...)
599 {
600 bool const originating_failure = !exception_.load(std::memory_order_acquire);
601 auto eptr = std::current_exception();
602 TaskRegistry::record_unhandled_exception(this->self().coroutine(), eptr, originating_failure);
603 this->set_exception(eptr);
604 for (auto* node = exception_sinks_head_; node; node = node->next)
605 propagate_unhandled_writer_exception(node->epoch.get(), eptr);
606 }
607 }
608
609 // /// \brief Called when the task is ready to run again.
610 // /// \param h The coroutine handle to schedule.
611 // void notify_ready(TaskHandle handle) { sched_->schedule(handle); }
612
613 protected:
616 {
617 CHECK(scheduler, "task scheduler route cannot be null");
618 CHECK(scheduler->accepts_route(route), "scheduler does not accept task route", static_cast<int>(route.domain),
619 route.cuda_device.value_or(-1));
620 }
621
622 private:
625 IScheduler* sched_ = nullptr;
626 TaskHandle self_{};
627
628 friend class BasicTask;
629};
630
632class AsyncTaskPromise final : public TaskPromiseBase {
633 public:
635
637
638 [[nodiscard]] AsyncTask get_return_object() noexcept
639 {
640 auto handle = std::coroutine_handle<AsyncTaskPromise>::from_promise(*this);
641 return AsyncTask(this->initialize_task(handle));
642 }
643};
644
646class CudaTaskPromise final : public TaskPromiseBase {
647 public:
649
652
653 [[nodiscard]] CudaTask get_return_object() noexcept;
654
659 [[nodiscard]] std::optional<int> device() const noexcept { return device_; }
660
665 template <CudaTaskAwaitable Awaiter> Awaiter&& await_transform(Awaiter&& awaiter) noexcept
666 {
667 return std::forward<Awaiter>(awaiter);
668 }
669
674 {
675 CHECK(device >= 0, "CUDA device ordinal must be non-negative", device);
676 CHECK(!this->has_started(), "cannot bind a CUDA task device after the task has started");
677 if (device_)
678 {
679 CHECK_EQUAL(*device_, device, "cannot rebind a CUDA task to another device");
680 return;
681 }
682 if (auto* scheduler = this->scheduler())
683 {
685 }
686 device_ = device;
687 }
688
694 {
695 CHECK(device >= 0, "CUDA device ordinal must be non-negative", device);
696 this->validate_scheduler_route(this->scheduler(), TaskRoute{.domain = TaskDomain::cuda, .cuda_device = device});
697 device_ = device;
698 }
699
700 private:
701 std::optional<int> device_{};
702};
703
708{
709 CHECK(handle);
710 CHECK(handle.domain() == TaskDomain::cuda);
711 return static_cast<CudaTaskPromise&>(handle.promise());
712}
713
716{
717 CHECK(handle);
718 TaskRoute route{.domain = handle.domain()};
719 if (handle.domain() == TaskDomain::cuda) route.cuda_device = cuda_promise(handle).device();
720 return route;
721}
722
724{
725 CHECK(parent);
726 CHECK(child);
727 CHECK(parent.promise().scheduler(), "nested task parent has no scheduler route");
728
729 if (!child.promise().scheduler())
730 {
731 auto* scheduler = parent.promise().scheduler();
733 child.promise().sched_ = scheduler;
734 }
735
736 if (child.domain() == TaskDomain::cuda)
737 {
738 auto& child_promise = cuda_promise(child);
739 if (!child_promise.device() && parent.domain() == TaskDomain::cuda)
740 {
741 if (auto const parent_device = cuda_promise(parent).device()) child_promise.bind_device(*parent_device);
742 }
743 }
744}
745
747{
748 CHECK(from);
749 CHECK(to);
750 auto* scheduler = from.promise().scheduler();
751 CHECK(scheduler, "running task has no scheduler route");
752
753 if (scheduler != to.promise().scheduler()) return false;
754 if (from.domain() != to.domain()) return false;
755 return scheduler->can_direct_transfer(from, to);
756}
757
767 public:
773 {
774 DEBUG_PRECONDITION(count_ > 0);
775 --count_;
776 return BasicTask(handle_, BasicTask::construction_key{});
777 }
778
779 TaskFactory(TaskFactory&& other) noexcept
780 : handle_(std::exchange(other.handle_, {})), count_(std::exchange(other.count_, 0))
781 {}
782
787 {
788 if (this != &other)
789 {
790 this->release_outstanding();
791 handle_ = std::exchange(other.handle_, {});
792 count_ = std::exchange(other.count_, 0);
793 }
794 return *this;
795 }
796
797 TaskFactory(TaskFactory const&) = delete;
799
804 ~TaskFactory() noexcept
805 {
806 DEBUG_TRACE_MODULE(ASYNC, this, handle_.coroutine(), count_);
807 this->release_outstanding();
808 }
809
810 private:
811 friend class TaskPromiseBase;
812
817 TaskFactory(TaskHandle handle, int count) : handle_(handle), count_(count)
818 {
819 [[maybe_unused]] int prior_count = handle_.promise().add_awaiter(count);
820 DEBUG_CHECK_EQUAL(prior_count, 0, "expected handle to be previously unowned!");
821 // if we requested zero references, then we can destroy the handle immediately
822 if (count_ == 0)
823 {
825 handle_ = {};
826 }
827 }
828
830 void release_outstanding() noexcept
831 {
832 // Return the outstanding references. If this reaches zero, destroy the coroutine.
833 if (count_ > 0 && handle_.promise().release_awaiter(count_))
834 {
836 }
837 count_ = 0;
838 handle_ = {};
839 }
840
841 TaskHandle handle_;
842 size_t count_;
843};
844
845inline TaskFactory TaskPromiseBase::take_shared_ownership(int count) { return TaskFactory(this->self(), count); }
846
847template <TaskAwaitable A> auto TaskPromiseBase::suspend_task_awaitable(TaskHandle current, A& awaitable)
848{
849#if UNI20_DEBUG_DAG
850 TaskPromiseBase::note_await_dependency(current, awaitable);
851#endif
852 using await_return_type = decltype(awaitable.await_suspend(std::declval<BasicTask>()));
853 if constexpr (std::is_void_v<await_return_type>)
854 {
855 auto task = current.promise().take_ownership();
856 awaitable.await_suspend(std::move(task));
858 return;
859 }
860 else if constexpr (std::is_same_v<await_return_type, BasicTask>)
861 {
862 auto owning_task = current.promise().take_ownership();
863 auto task = awaitable.await_suspend(std::move(owning_task));
864 return TaskPromiseBase::resolve_await_suspend_result(current, std::move(task));
865 }
866 else
867 {
868 static_assert(std::is_same_v<await_return_type, void>,
869 "Unsupported await_suspend() return type: must be void or BasicTask");
870 }
871}
872
873template <TaskFactoryAwaitable A> void TaskPromiseBase::suspend_factory_awaitable(TaskHandle current, A& awaitable)
874{
875#if UNI20_DEBUG_DAG
876 TaskPromiseBase::note_await_dependency(current, awaitable);
877#endif
878 auto factory = current.promise().take_shared_ownership(awaitable.num_awaiters());
879 awaitable.await_suspend(std::move(factory));
881}
882
884template <TaskAwaitable A> struct TaskAwaiter //: public AsyncAwaiter
885{
888
891 [[nodiscard]] bool await_ready() { return awaitable.await_ready(); }
892
897 template <TaskPromise Promise> auto await_suspend(std::coroutine_handle<Promise> handle)
898 {
900 }
901
904 [[nodiscard]] decltype(auto) await_resume()
905 {
906#if UNI20_DEBUG_DAG
907 TaskPromiseBase::note_await_dependency(promise.self(), awaitable);
908#endif
909 if constexpr (requires { awaitable.register_exception_sinks(promise); })
910 {
911 awaitable.register_exception_sinks(promise);
912 }
913 if constexpr (std::is_lvalue_reference_v<A>)
914 {
915 return awaitable.await_resume();
916 }
917 else
918 {
919 return std::move(awaitable).await_resume();
920 }
921 }
922
923 // decltype(auto) await_resume() &
924 // {
925 // // we can call await_resume on a moved awaitable here, because this is the last time
926 // // that we refer to awaitable
927 // return awaitable.await_resume();
928 // }
929 //
930 // decltype(auto) await_resume() &&
931 // {
932 // // we can call await_resume on a moved awaitable here, because this is the last time
933 // // that we refer to awaitable
934 // return std::move(awaitable).await_resume();
935 // }
936
937 // void set_cancel() override final { awaitable.set_cancel(); }
938 //
939 // void set_exception(std::exception_ptr e) override final { awaitable.set_exception(e); }
940};
941
946template <typename T> void ProcessCoroutineArgument(TaskPromiseBase* promise, T const& value)
947{
948 static_cast<void>(promise);
949 static_cast<void>(value);
950}
951
952template <TaskFactoryAwaitable A> struct TaskFactoryAwaiter //: public AsyncAwaiter
953{
956
959 [[nodiscard]] bool await_ready() { return awaitable.await_ready(); }
960
964 template <TaskPromise Promise> void await_suspend(std::coroutine_handle<Promise> handle)
965 {
967 }
968
971 [[nodiscard]] decltype(auto) await_resume()
972 {
973#if UNI20_DEBUG_DAG
974 TaskPromiseBase::note_await_dependency(promise.self(), awaitable);
975#endif
976 if constexpr (std::is_lvalue_reference_v<A>)
977 {
978 return awaitable.await_resume();
979 }
980 else
981 {
982 return std::move(awaitable).await_resume();
983 }
984 }
985
986 // void set_cancel() override final { awaitable.set_cancel(); }
987 //
988 // void set_exception(std::exception_ptr e) override final { awaitable.set_exception(e); }
989};
990
991template <TaskPromise Promise> TaskHandle TaskHandle::from(std::coroutine_handle<Promise> handle) noexcept
992{
993 if (!handle) return {};
994 return TaskHandle(std::coroutine_handle<>::from_address(handle.address()), std::addressof(handle.promise()),
995 Promise::task_domain);
996}
997
998inline TaskPromiseBase& TaskHandle::promise() const noexcept
999{
1000 DEBUG_PRECONDITION(promise_ != nullptr);
1001 return *promise_;
1002}
1003
1004} // namespace uni20::async
1005
1006#include "async_task_impl.hpp"
Exception hierarchy for async/dataflow operations.
Debug metadata nodes used for async DAG visualization.
Promise-neutral coroutine task ownership and the public AsyncTask type.
Inline implementation of promise-neutral task ownership and nesting.
static void register_task(std::coroutine_handle<> h)
Registers a newly-created coroutine handle.
static void leak_task(std::coroutine_handle<> h)
Marks a coroutine handle as leaked.
static void record_task_dependencies(std::coroutine_handle<> h, std::vector< async::NodeInfo const * > const &read_dependencies, std::vector< async::NodeInfo const * > const &write_dependencies)
Records constructor-discovered coarse DAG dependencies for a coroutine.
static void destroy_task(std::coroutine_handle<> h)
Marks a coroutine handle as destroyed.
static void mark_suspended(std::coroutine_handle<> h)
Marks a coroutine handle as suspended.
static void mark_running(std::coroutine_handle<> h)
Marks a coroutine handle as currently running.
static void record_unhandled_exception(std::coroutine_handle<> h, std::exception_ptr exception, bool originating_failure) noexcept
Records an exception escaping a coroutine and optionally captures its live DAG.
EpochTaskRole
Role of a coroutine relative to an epoch context.
Definition task_registry_debug.hpp:56
static void record_await_dependency(std::coroutine_handle<> h, async::NodeInfo const *node, EpochTaskRole role)
Records a buffer dependency discovered at a concrete co_await site.
Promise type for ordinary AsyncTask coroutines.
Definition async_task_promise.hpp:632
AsyncTask get_return_object() noexcept
Definition async_task_promise.hpp:638
static constexpr TaskDomain task_domain
Definition async_task_promise.hpp:634
Canonical host-oriented coroutine task type.
Definition async_task.hpp:196
Move-only promise-neutral ownership claim for a Uni20 coroutine.
Definition async_task.hpp:103
static void reschedule(BasicTask task)
Reschedule a task when it becomes the sole ownership claimant.
Definition async_task_impl.hpp:11
std::coroutine_handle await_suspend(std::coroutine_handle< ParentPromise > parent)
Await a nested task from any Uni20 task promise type.
Definition async_task_impl.hpp:162
Promise type for CUDA-constrained CudaTask coroutines.
Definition async_task_promise.hpp:646
static constexpr TaskDomain task_domain
Definition async_task_promise.hpp:648
std::optional< int > device() const noexcept
Return the task's established CUDA device affinity, if any.
Definition async_task_promise.hpp:659
void select_device(int device)
Select the device used for subsequent CUDA task activations.
Definition async_task_promise.hpp:693
CudaTask get_return_object() noexcept
Definition cuda_task.hpp:40
void bind_device(int device)
Bind the CUDA device before the coroutine starts.
Definition async_task_promise.hpp:673
Awaiter && await_transform(Awaiter &&awaiter) noexcept
Pass through an explicitly opted-in CUDA task awaiter.
Definition async_task_promise.hpp:665
CUDA-constrained public task type used for typed initial admission.
Definition cuda_task.hpp:14
Internal route for resuming an already-bound coroutine task.
Definition scheduler.hpp:64
Records node information for DAG/debugging visualization.
Definition async_node.hpp:171
Factory for producing multiple BasicTask ownership claims for one coroutine.
Definition async_task_promise.hpp:766
TaskFactory(TaskFactory &&other) noexcept
Definition async_task_promise.hpp:779
TaskFactory(TaskFactory const &)=delete
TaskFactory & operator=(TaskFactory &&other) noexcept
Move assignment.
Definition async_task_promise.hpp:786
BasicTask take_next()
Dispense the next BasicTask from the pool of shared ownership handles.
Definition async_task_promise.hpp:772
~TaskFactory() noexcept
Destructor returns any unused ownership claims and may destroy the coroutine.
Definition async_task_promise.hpp:804
TaskFactory & operator=(TaskFactory const &)=delete
Non-owning erased identity for one Uni20 coroutine frame.
Definition async_task.hpp:59
TaskDomain domain() const noexcept
Return the execution domain declared by the concrete promise.
Definition async_task.hpp:72
constexpr std::coroutine_handle coroutine() const noexcept
Definition async_task.hpp:67
TaskPromiseBase & promise() const noexcept
Definition async_task_promise.hpp:998
static TaskHandle from(std::coroutine_handle< Promise > handle) noexcept
Definition async_task_promise.hpp:991
Shared non-polymorphic implementation for Uni20 task promises.
Definition async_task_promise.hpp:99
IScheduler * scheduler() const noexcept
Return the scheduler currently recorded for this task.
Definition async_task_promise.hpp:357
static auto suspend_task_awaitable(TaskHandle current, A &awaitable)
Execute await_suspend for TaskAwaitable and apply TaskRegistry tracking.
Definition async_task_promise.hpp:847
static constexpr int kNoPreferredNumaNode
Definition async_task_promise.hpp:140
bool release_awaiter(int count) noexcept
Decrease the number of active awaiters by a specified count.
Definition async_task_promise.hpp:211
std::atomic< bool > cancel_on_resume_
If true, then when the coroutine is next resumed (i.e.
Definition async_task_promise.hpp:138
static void prepare_nested_route(TaskHandle parent, TaskHandle child)
Complete an unbound child's route before nested execution.
Definition async_task_promise.hpp:723
constexpr void return_void() noexcept
Called when the coroutine returns normally.
Definition async_task_promise.hpp:585
uint64_t Instance
Definition async_task_promise.hpp:147
TaskHandle initialize_task(std::coroutine_handle< Promise > handle) noexcept
Cache erased self-identity and register the initial ownership claim.
Definition async_task_promise.hpp:495
auto await_transform(T &&)
Fallback await_transform overload that rejects unsupported awaitables.
Definition async_task_promise.hpp:328
friend class BasicTask
Definition async_task_promise.hpp:628
bool is_cancel_on_resume() const noexcept
Reports whether cancellation-on-resume is currently set.
Definition async_task_promise.hpp:302
auto await_transform(A &a)
Transform an awaiter to transfer one task ownership claim.
Definition async_impl.hpp:74
static std::coroutine_handle resolve_await_suspend_result(TaskHandle current, BasicTask &&task)
Resolve an await_suspend task return value into a coroutine transfer handle.
Definition async_task_promise.hpp:429
std::string Name
Definition async_task_promise.hpp:146
auto initial_suspend() noexcept
Suspend immediately on coroutine entry.
Definition async_task_promise.hpp:508
void register_exception_sink(ExceptionSinkNode &node, std::shared_ptr< EpochContext > epoch, bool explicit_sink)
Register one exception propagation sink with this promise.
Definition async_task_promise.hpp:239
std::atomic< int > awaiter_count_
Number of active ownership claims on this coroutine.
Definition async_task_promise.hpp:127
void set_exception(std::exception_ptr e) noexcept
Record an exception for deferred rethrow on resume.
Definition async_task_promise.hpp:227
BasicTask take_ownership() noexcept
Acquire exclusive ownership of the coroutine.
Definition async_task_promise.hpp:472
constexpr TaskPromiseBase() noexcept=default
Default-construct the promise.
int add_awaiter() noexcept
Increase the number of active awaiters by one.
Definition async_task_promise.hpp:218
TaskFactory take_shared_ownership(int count)
Acquire shared ownership of the coroutine for use with multi-await constructs.
Definition async_task_promise.hpp:845
int add_awaiter(int count) noexcept
Increase the number of active awaiters by a specified count.
Definition async_task_promise.hpp:223
static void note_running(TaskHandle handle) noexcept
Record that a coroutine has transitioned into a runnable/running state.
Definition async_task_promise.hpp:367
bool has_started() const noexcept
Query whether the coroutine has begun executing.
Definition async_task_promise.hpp:354
ExceptionSinkNode * exception_sinks_head_
Definition async_task_promise.hpp:148
bool release_awaiter() noexcept
Decrease the number of active awaiters by one.
Definition async_task_promise.hpp:206
TaskPromiseBase(Args &&... args)
Construct the promise and process coroutine arguments for debug metadata.
Definition async_task_promise.hpp:181
void unregister_exception_sink(ExceptionSinkNode &node, bool from_destructor) noexcept
Unregister one exception propagation sink from this promise.
Definition async_task_promise.hpp:256
void set_preferred_numa_node(std::optional< int > node) noexcept
Set the preferred NUMA node recorded for this coroutine.
Definition async_task_promise.hpp:336
static bool can_transfer_directly(TaskHandle from, TaskHandle to) noexcept
Report whether symmetric transfer is valid for two prepared task routes.
Definition async_task_promise.hpp:746
void unhandled_exception()
Called on unhandled exception escaping the coroutine.
Definition async_task_promise.hpp:588
std::atomic< bool > exception_
EpochContext can inject an exception into a coroutine, which will be thrown when resuming from an awa...
Definition async_task_promise.hpp:133
void set_cancel_on_resume() noexcept
Mark the coroutine for cancellation at next resume.
Definition async_task_promise.hpp:298
void mark_started() noexcept
Mark the coroutine as having been scheduled to start executing.
Definition async_task_promise.hpp:351
std::exception_ptr get_exception() noexcept
Get the current exception pointer, or nullptr if there is no current exception.
Definition async_task_promise.hpp:282
TaskHandle destroy_with_continuation() noexcept
safely destroy this coroutine, returning the continuation_ (which also must now be destroyed)
Definition async_task_promise.hpp:188
std::exception_ptr * continuation_exception_
Destination for an exception returned by a directly awaited task.
Definition async_task_promise.hpp:122
auto final_suspend() noexcept
Definition async_task_promise.hpp:525
static void destroy_and_track(TaskHandle handle) noexcept
Destroy a coroutine handle while recording destruction in the registry.
Definition async_task_promise.hpp:407
static void validate_scheduler_route(IScheduler *scheduler, TaskRoute route)
Validate a prospective task route without changing promise state.
Definition async_task_promise.hpp:615
static void note_leaked(TaskHandle handle) noexcept
Record that a coroutine has been intentionally leaked.
Definition async_task_promise.hpp:397
static void note_suspended(TaskHandle handle) noexcept
Record that a coroutine has suspended and is waiting for resumption.
Definition async_task_promise.hpp:370
static void suspend_factory_awaitable(TaskHandle current, A &awaitable)
Execute await_suspend for TaskFactoryAwaitable and apply TaskRegistry tracking.
Definition async_task_promise.hpp:873
TaskHandle release_ownership()
Release ownership of the coroutine and return it, if it was exclusively owned.
Definition async_task_promise.hpp:488
TaskHandle self() noexcept
Return this promise's erased coroutine identity.
Definition async_task_promise.hpp:360
std::atomic< bool > started_
Tracks whether the coroutine has been scheduled or otherwise started.
Definition async_task_promise.hpp:112
std::optional< int > preferred_numa_node() const noexcept
Retrieve the preferred NUMA node for this coroutine.
Definition async_task_promise.hpp:343
void rethrow_exception()
Throw the current exception, if there is one.
Definition async_task_promise.hpp:292
static void resume_and_track(TaskHandle handle)
Resume a coroutine handle while recording the running transition.
Definition async_task_promise.hpp:400
std::exception_ptr eptr_
Definition async_task_promise.hpp:134
Task && await_transform(Task &&task) noexcept
Pass through a concrete task that uses the shared basic task representation.
Definition async_task_promise.hpp:321
TaskHandle continuation_
Uni20 task coroutines are nestable.
Definition async_task_promise.hpp:117
std::atomic< int > preferred_numa_node_
Preferred NUMA node recorded for the coroutine.
Definition async_task_promise.hpp:143
~TaskPromiseBase() noexcept
Definition async_task_promise.hpp:198
Raised when an AsyncTask is cancelled before completion.
Definition async_errors.hpp:44
Concept for the valid return types of await_suspend.
Definition async_task_promise.hpp:48
Identifies an explicitly opted-in CUDA task awaiter.
Definition async_task_promise.hpp:33
Concept for awaitables that accept promise-neutral task ownership.
Definition async_task_promise.hpp:61
Concept for awaitables that support shared promise-neutral ownership.
Definition async_task_promise.hpp:87
Awaitable that can hold one ownership claim in a multi-input join.
Definition async_task_promise.hpp:71
Promise concept accepted by Uni20 task machinery.
Definition async_task.hpp:50
cuda::ResourceLease< HandleSlot > handle
Definition cublas/execution.cpp:33
locale_t value
Definition data_table.cpp:49
bool continuation
Definition display.cpp:148
Definition qnum.hpp:403
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
TaskRoute task_route(TaskHandle handle) noexcept
Return the current scheduling requirements recorded by a task.
Definition async_task_promise.hpp:715
CudaTaskPromise & cuda_promise(TaskHandle handle) noexcept
Narrow an erased task promise after verifying that it is CUDA-specific.
Definition async_task_promise.hpp:707
void propagate_unhandled_writer_exception(EpochContext *epoch, std::exception_ptr eptr) noexcept
Inject an unhandled writer-side coroutine exception into an epoch.
Definition epoch_context.hpp:836
void ProcessCoroutineArgument(TaskPromiseBase *promise, T const &value)
Process a coroutine argument for debug metadata (default no-op).
Definition async_task_promise.hpp:946
TaskDomain
Execution domain declared by a concrete Uni20 task promise.
Definition async_task.hpp:32
Definition cli.cpp:6
Defines internal task routing and the host-oriented scheduler interface.
Definition async_task.hpp:177
Marker base for awaiters that require a CudaTaskPromise handle.
Definition async_task_promise.hpp:29
Forwarding awaiter that transfers one task ownership claim to an awaitable.
Definition async_task_promise.hpp:885
bool await_ready()
Checks whether the wrapped awaitable is ready.
Definition async_task_promise.hpp:891
TaskPromiseBase & promise
Definition async_task_promise.hpp:887
A awaitable
Definition async_task_promise.hpp:886
auto await_suspend(std::coroutine_handle< Promise > handle)
Suspend using promise-neutral ownership-transfer semantics.
Definition async_task_promise.hpp:897
decltype(auto) await_resume()
Resume wrapped awaitable and register explicit exception sinks if provided.
Definition async_task_promise.hpp:904
Forwarding awaiter that transfers shared task ownership through a TaskFactory.
Definition async_task_promise.hpp:953
void await_suspend(std::coroutine_handle< Promise > handle)
Suspend using shared-ownership await-suspend semantics.
Definition async_task_promise.hpp:964
decltype(auto) await_resume()
Resume wrapped awaitable and return its await result.
Definition async_task_promise.hpp:971
bool await_ready()
Checks whether the wrapped awaitable is ready.
Definition async_task_promise.hpp:959
TaskPromiseBase & promise
Definition async_task_promise.hpp:955
A awaitable
Definition async_task_promise.hpp:954
Intrusive node describing one exception propagation sink.
Definition async_task_promise.hpp:103
TaskPromiseBase * owner
Definition async_task_promise.hpp:104
bool explicit_sink
Definition async_task_promise.hpp:108
ExceptionSinkNode * prev
Definition async_task_promise.hpp:105
ExceptionSinkNode * next
Definition async_task_promise.hpp:106
std::shared_ptr< EpochContext > epoch
Definition async_task_promise.hpp:107
Execution requirements used to validate a scheduler route.
Definition async_task.hpp:43
std::optional< int > cuda_device
Definition async_task.hpp:45
TaskDomain domain
Definition async_task.hpp:44
#define CHECK(cond,...)
Definition trace.hpp:299
#define DEBUG_CHECK(cond,...)
Definition trace.hpp:608
#define TRACE_MODULE(m,...)
Definition trace.hpp:149
#define DEBUG_PRECONDITION(cond,...)
Definition trace.hpp:648
#define DEBUG_TRACE_MODULE(m,...)
Definition trace.hpp:511
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309
#define DEBUG_CHECK_EQUAL(a, b,...)
Definition trace.hpp:618