18#include <fmt/format.h>
27template <
typename T>
class Async;
28template <
typename T>
class EpochContextReader;
29template <
typename T>
class EpochContextWriter;
167class ReverseEpochQueue;
205 explicit EpochContext(std::shared_ptr<EpochContext> next) : counter_(next->counter_ - 1), next_epoch_(next)
208 node_ = next ? next->node() :
nullptr;
214 static std::shared_ptr<EpochContext>
make_previous(std::shared_ptr<EpochContext> next)
216 return std::make_shared<EpochContext>(std::move(next));
222 std::lock_guard lock(mtx_);
232 std::unique_lock lock(mtx_);
233 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::set_next_epoch",
this, next.get(), counter_);
237 next->counter_ = counter_ + 1;
241 next_epoch_ = std::move(next);
242 if (phase_ ==
Phase::Reading && num_readers_ == 0) this->advance_finished_locked(std::move(lock));
248 std::unique_lock lock(mtx_);
250 if (phase_ ==
Phase::Pending) this->advance_start_locked(std::move(lock));
257 std::unique_lock lock(mtx_);
262 this->advance_start_locked(std::move(lock));
268 std::unique_lock lock(mtx_);
272 this->advance_start_locked(std::move(lock));
278 std::lock_guard lock(mtx_);
281 snapshot.
phase = phase_;
288 snapshot.node = node_;
292 for (
auto const& task_state : reader_tasks_)
294 if (
auto h = task_state.task.coroutine_handle()) snapshot.
reader_tasks.push_back(h);
298 for (
auto const& task_state : writer_tasks_)
300 if (
auto h = task_state.task.coroutine_handle()) snapshot.
writer_tasks.push_back(h);
311 if (!mtx_.try_lock())
return false;
312 std::lock_guard lock(mtx_, std::adopt_lock);
315 snapshot.
phase = phase_;
322 snapshot.node = node_;
326 for (
auto const& task_state : reader_tasks_)
328 if (
auto h = task_state.task.coroutine_handle()) snapshot.
reader_tasks.push_back(h);
332 for (
auto const& task_state : writer_tasks_)
334 if (
auto h = task_state.task.coroutine_handle()) snapshot.
writer_tasks.push_back(h);
344 return std::move(snapshot.reader_tasks);
351 return std::move(snapshot.writer_tasks);
359 std::lock_guard lock(mtx_);
365 void set_node(NodeInfo
const* node)
367 std::lock_guard lock(mtx_);
380 void writer_acquire() noexcept
382 std::unique_lock lock(mtx_);
389 if (phase_ ==
Phase::Started) this->advance_writing_locked(std::move(lock));
392 bool has_writer() const noexcept
394 std::lock_guard lock(mtx_);
395 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::has_writer",
this, counter_, phase_, total_writers_);
400 void writer_clear_errors() noexcept
402 std::lock_guard lock(mtx_);
413 void writer_bind(BasicTask&& task)
noexcept
415 std::unique_lock lock(mtx_);
416 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::writer_bind",
this, counter_, task.coroutine_handle(), phase_,
417 num_writers_, writer_active_);
424 this->execute_writer_locked(std::move(lock), TaskState{std::move(task)});
429 writer_tasks_.emplace_back(std::move(task));
434 bool writer_try_make_active() noexcept
436 std::unique_lock lock(mtx_);
437 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::writer_try_make_active",
this, counter_, phase_, writer_active_);
440 writer_active_ =
true;
446 void writer_notify_when_ready(std::function<
void()> notify)
448 std::unique_lock lock(mtx_);
456 writer_ready_waiters_.push_back(std::move(notify));
459 void writer_set_exception(std::exception_ptr e)
noexcept
461 std::lock_guard lock(mtx_);
464 if (!eptr_) eptr_ = e;
478 bool writer_cancel() noexcept
480 std::unique_lock lock(mtx_);
490 this->advance_reading_locked(std::move(lock));
500 bool writer_release_active() noexcept
502 std::unique_lock lock(mtx_);
508 writer_active_ =
false;
513 this->advance_reading_locked(std::move(lock));
517 if (!writer_tasks_.empty())
519 auto NextTask = std::move(writer_tasks_.back());
520 writer_tasks_.pop_back();
521 this->execute_writer_locked(std::move(lock), std::move(NextTask));
525 this->notify_writers_locked(std::move(lock));
540 void reader_acquire() noexcept
542 std::lock_guard lock(mtx_);
550 bool reader_ready() const noexcept
552 std::lock_guard lock(mtx_);
561 void reader_notify_when_ready(std::function<
void()> notify)
563 std::unique_lock lock(mtx_);
564 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::reader_notify_when_ready",
this, counter_, phase_);
573 reader_ready_waiters_.push_back(std::move(notify));
576 void reader_bind(BasicTask&& h)
578 std::unique_lock lock(mtx_);
584 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::reader_bind is scheduling the task immediately",
this, counter_);
585 std::exception_ptr my_eptr = eptr_;
587 if (my_eptr) h.exception_on_resume(my_eptr);
592 DEBUG_TRACE_MODULE(ASYNC,
"EpochContext::reader_bind is adding the reader task", h.coroutine_handle());
593 reader_tasks_.emplace_back(std::move(h));
598 std::exception_ptr reader_exception() const noexcept
614 bool reader_release() noexcept
616 std::lock_guard lock(mtx_);
623 if (phase_ ==
Phase::Reading && num_readers_ == 0 && next_epoch_ !=
nullptr)
626 next_epoch_->start(eptr_);
629 return num_readers_ == 0;
633 void advance_start_locked(std::unique_lock<std::mutex> lock)
639 if (total_writers_ > 0) this->advance_writing_locked(std::move(lock));
642 void advance_writing_locked(std::unique_lock<std::mutex> lock)
noexcept
648 if (!writer_tasks_.empty())
650 auto NextTask = std::move(writer_tasks_.back());
651 writer_tasks_.pop_back();
652 this->execute_writer_locked(std::move(lock), std::move(NextTask));
656 if (num_writers_ == 0)
657 this->advance_reading_locked(std::move(lock));
659 this->notify_writers_locked(std::move(lock));
662 void advance_reading_locked(std::unique_lock<std::mutex> lock)
noexcept
668 if (num_readers_ > 0)
670 this->execute_readers_locked(std::move(lock));
674 if (num_readers_ == 0 && next_epoch_ !=
nullptr) this->advance_finished_locked(std::move(lock));
677 void advance_finished_locked(std::unique_lock<std::mutex> lock)
noexcept
685 next_epoch_->start(eptr_);
689 void execute_writer_locked(std::unique_lock<std::mutex> lock, TaskState task)
695 if (eptr_) task.task.exception_on_resume(eptr_);
696 writer_active_ =
true;
701 void notify_writers_locked(std::unique_lock<std::mutex> lock)
noexcept
706 std::vector<std::function<void()>> waiters;
707 waiters.swap(writer_ready_waiters_);
709 for (
auto& notify : waiters)
713 void execute_readers_locked(std::unique_lock<std::mutex> lock)
721 std::exception_ptr my_eptr = eptr_;
722 std::vector<TaskState> my_reader_tasks;
723 std::swap(reader_tasks_, my_reader_tasks);
724 std::vector<std::function<void()>> my_ready_waiters;
725 std::swap(reader_ready_waiters_, my_ready_waiters);
728 for (
auto& notify : my_ready_waiters)
733 for (
auto&& task : my_reader_tasks)
735 if (my_eptr) task.task.exception_on_resume(my_eptr);
744 mutable std::mutex mtx_;
748 std::exception_ptr eptr_{
nullptr};
754 std::shared_ptr<EpochContext> next_epoch_;
759 int total_writers_{0};
761 std::vector<TaskState> writer_tasks_;
762 std::vector<std::function<void()>> writer_ready_waiters_;
763 bool writer_active_{
false};
767 std::vector<TaskState> reader_tasks_;
768 std::vector<std::function<void()>> reader_ready_waiters_;
771 NodeInfo
const* node_{
nullptr};
838 if (!epoch || !eptr)
return;
839 epoch->writer_set_exception(std::move(eptr));
855 case Phase::Finished:
882 epoch_->reader_acquire();
892 if (other.epoch_) other.epoch_->reader_acquire();
894 storage_ = other.storage_;
895 epoch_ = other.epoch_;
904 : storage_(std::move(storage)), epoch_(std::move(epoch))
906 epoch_->reader_acquire();
912 : storage_(std::move(other.storage_)), epoch_(std::move(other.epoch_))
923 storage_ = std::move(other.storage_);
924 epoch_ = std::move(other.epoch_);
934 NodeInfo const* node()
const {
return epoch_ ? epoch_->node() :
nullptr; }
941 TRACE_MODULE(ASYNC,
"EpochContextReader::suspend",
this, t.coroutine_handle(), epoch_.get(), epoch_->counter_);
942 epoch_->reader_bind(std::move(t));
947 [[nodiscard]]
bool ready() const noexcept {
return epoch_->reader_ready(); }
951 void notify_when_ready(std::function<
void()> notify)
const { epoch_->reader_notify_when_ready(std::move(notify)); }
957 [[nodiscard]] T
const&
data()
const
960 DEBUG_TRACE_MODULE(ASYNC,
"EpochContextReader::data", epoch_.get(), epoch_->counter_);
961 if (
auto e = epoch_->reader_exception(); e) std::rethrow_exception(e);
962 auto* ptr = storage_.get();
965#if UNI20_DEBUG_ASYNC_TASKS
966 constexpr char const* reason =
"EpochContextReader::data access on uninitialized storage";
970 auto const epoch_counter = epoch_ ? epoch_->counter_ : -1;
971 TRACE_STACK(reason,
this, epoch_.get(), epoch_counter);
991 if (
auto e = epoch_->reader_exception(); e) std::rethrow_exception(e);
992 return storage_.get();
1004 if (
auto e = epoch_->reader_exception(); e) std::rethrow_exception(e);
1005 auto* ptr = storage_.get();
1006 if (!ptr)
return std::nullopt;
1015 epoch_->reader_release();
1028 [[nodiscard]] T
const&
get_wait()
const;
1038 std::shared_ptr<EpochContext> epoch_{};
1056 : storage_(std::move(
storage)), epoch_(std::move(epoch))
1058 TRACE_MODULE(ASYNC,
"EpochContextWriter Constructor",
this, epoch_.get());
1059 epoch_->writer_acquire();
1068 explicit operator bool() const noexcept {
return epoch_ !=
nullptr; }
1075 : storage_(std::move(other.storage_)), epoch_(std::move(other.epoch_)), acquired_(std::move(other.acquired_))
1077 other.epoch_.reset();
1088 storage_ = std::move(other.storage_);
1089 epoch_ = std::move(other.epoch_);
1090 acquired_ = other.acquired_;
1091 other.epoch_.reset();
1101 NodeInfo const* node()
const {
return epoch_ ? epoch_->node() :
nullptr; }
1108 if (acquired_)
return true;
1109 acquired_ = epoch_->writer_try_make_active();
1118 epoch_->writer_bind(std::move(t));
1121 void resume() const noexcept { acquired_ =
true; }
1128 auto* ptr = storage_.get();
1131#if UNI20_DEBUG_ASYNC_TASKS
1132 constexpr char const* reason =
"EpochContextWriter::data access on uninitialized storage";
1136 auto const epoch_counter = epoch_ ? epoch_->counter_ : -1;
1137 TRACE_STACK(reason,
this, epoch_.get(), epoch_counter);
1149 template <
typename... Args>
1150 requires std::constructible_from<T, Args...>
1155 return storage_.emplace(std::forward<Args>(args)...);
1159#if UNI20_DEBUG_ASYNC_TASKS
1160 constexpr char const* reason =
"EpochContextWriter::emplace failed while constructing storage";
1164 auto const epoch_counter = epoch_ ? epoch_->counter_ : -1;
1165 TRACE_STACK(reason,
this, epoch_.get(), epoch_counter);
1178 void set_exception(std::exception_ptr e)
const noexcept { epoch_->writer_set_exception(e); }
1193 epoch_->writer_release_active();
1195 epoch_->writer_cancel();
1206 std::shared_ptr<EpochContext> epoch_;
1207 mutable bool acquired_{
false};
Exception hierarchy for async/dataflow operations.
Debug metadata nodes used for async DAG visualization.
Defines shared and concrete task coroutine promises.
static void dump()
Prints global task-registry diagnostics.
static void register_epoch_context(async::EpochContext const *epoch_context)
Registers an epoch context for debug tracking.
static void dump_epoch_context(async::EpochContext const *epoch_context, char const *reason=nullptr)
Prints diagnostics for a specific epoch context.
static DumpMode dump_mode() noexcept
Reports the current debug dump mode.
static void destroy_epoch_context(async::EpochContext const *epoch_context)
Removes an epoch context from debug tracking.
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
RAII handle representing the writer of a value in an EpochContext.
Definition epoch_context.hpp:874
std::shared_ptr< EpochContext > epoch_context_shared() const noexcept
Definition epoch_context.hpp:1034
~EpochContextReader()
Destructor. Signals epoch reader completion if still active.
Definition epoch_context.hpp:930
std::optional< T > data_option() const
Optionally retrieves the value stored in this buffer, if available.
Definition epoch_context.hpp:1001
EpochContextReader & operator=(EpochContextReader &&other) noexcept
Move assignment.
Definition epoch_context.hpp:918
EpochContextReader(EpochContextReader const &other)
Copy constructor retaining the reader reference count.
Definition epoch_context.hpp:880
bool ready() const noexcept
Check whether the reader is ready to resume.
Definition epoch_context.hpp:947
EpochContextReader(shared_storage< T > storage, std::shared_ptr< EpochContext > epoch) noexcept
Construct a new reader handle for a given parent and epoch.
Definition epoch_context.hpp:903
void release() noexcept
Release the reader.
Definition epoch_context.hpp:1011
void suspend(BasicTask &&t)
Suspend a coroutine task as a reader of this epoch.
Definition epoch_context.hpp:939
void wait() const
Wait for the epoch to become available on the global scheduler.
Definition debug_scheduler.hpp:274
T const * data_maybe() const
Definition epoch_context.hpp:988
T const & data() const
Access the stored value inside the parent Async<T>.
Definition epoch_context.hpp:957
EpochContextReader()=delete
Default-constructed inactive reader (no effect).
EpochContextReader & operator=(EpochContextReader const &other)
Copy assignment retaining the reader reference count.
Definition epoch_context.hpp:888
void notify_when_ready(std::function< void()> notify) const
Register a callback for this reader becoming ready.
Definition epoch_context.hpp:951
T const & get_wait() const
Wait for the epoch to become available on the global scheduler and return a reference to the value.
Definition debug_scheduler.hpp:308
EpochContextReader(EpochContextReader &&other) noexcept
Move constructor.
Definition epoch_context.hpp:911
RAII-scoped representation of a writer’s participation in an epoch.
Definition epoch_context.hpp:1048
void suspend(BasicTask &&t)
Suspend the writer task and submit to the epoch context.
Definition epoch_context.hpp:1115
EpochContextWriter()=delete
void resume() const noexcept
Definition epoch_context.hpp:1121
EpochContextWriter & operator=(EpochContextWriter &&other) noexcept
Move assignment transferring the writer handle.
Definition epoch_context.hpp:1083
std::shared_ptr< EpochContext > epoch_context_shared() const noexcept
Definition epoch_context.hpp:1180
~EpochContextWriter()
Destructor releasing any held writer epoch.
Definition epoch_context.hpp:1097
bool ready() const noexcept
Check whether the writer may proceed immediately.
Definition epoch_context.hpp:1106
T & emplace(Args &&... args)
Definition epoch_context.hpp:1151
shared_storage< T > & storage()
Definition epoch_context.hpp:1176
EpochContextWriter(shared_storage< T > storage, std::shared_ptr< EpochContext > epoch) noexcept
Construct an active writer.
Definition epoch_context.hpp:1055
T & data() const
Access the stored data while holding the writer gate.
Definition epoch_context.hpp:1126
void release() noexcept
Wait for the epoch to become available, and then return a prvalue-reference to the value.
Definition epoch_context.hpp:1188
void set_exception(std::exception_ptr e) const noexcept
Definition epoch_context.hpp:1178
bool value_is_initialized() const noexcept
Report whether the associated value is currently initialized.
Definition epoch_context.hpp:1184
EpochContextWriter(EpochContextWriter &&other) noexcept
Move constructor transferring the writer handle.
Definition epoch_context.hpp:1074
T && move_from_wait()
Wait for the epoch to become available, and then return a prvalue-reference to the value.
Definition debug_scheduler.hpp:327
EpochContextWriter & operator=(EpochContextWriter const &)=delete
Definition epoch_context.hpp:169
static std::shared_ptr< EpochContext > make_previous(std::shared_ptr< EpochContext > next)
Helper factory to construct the previous epoch in a reverse-mode chain.
Definition epoch_context.hpp:214
DebugSnapshot debug_snapshot() const
Return a point-in-time snapshot of epoch state and queued tasks.
Definition epoch_context.hpp:276
std::vector< std::coroutine_handle<> > writer_task_handles() const
Return coroutine handles currently queued as writers in this epoch context.
Definition epoch_context.hpp:348
std::vector< std::coroutine_handle<> > reader_task_handles() const
Return coroutine handles currently queued as readers in this epoch context.
Definition epoch_context.hpp:341
EpochContext()
Definition epoch_context.hpp:201
void start(std::exception_ptr eptr)
Definition epoch_context.hpp:266
bool try_debug_snapshot(DebugSnapshot &snapshot) const
Try to return a point-in-time snapshot without blocking.
Definition epoch_context.hpp:309
friend 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
EpochContext(std::shared_ptr< EpochContext > next)
Construct an EpochContext given an existing next epoch.
Definition epoch_context.hpp:205
void set_next_epoch(std::shared_ptr< EpochContext > next)
Set the next EpochContext in the chain.
Definition epoch_context.hpp:230
void start_reading()
Definition epoch_context.hpp:255
~EpochContext()
Definition epoch_context.hpp:219
void start()
Definition epoch_context.hpp:246
Phase
Definition epoch_context.hpp:172
@ Reading
The epoch is active and waiting for readers to finish.
@ Pending
The epoch is not yet active (i.e. it is in the future)
@ Started
The epoch has started but is waiting for a writer task.
@ Finished
The epoch is not active, and readers have finished.
@ Writing
The epoch is active and waiting for the writers to finish, with no writer currently running.
Coordinates multiple epochs of read/write gates.
Definition epoch_queue.hpp:22
Scheduler interface for initial AsyncTask submission and host-side progress.
Definition scheduler.hpp:89
Records node information for DAG/debugging visualization.
Definition async_node.hpp:171
Reverse-order epoch queue used by reverse-mode gradient accumulation.
Definition epoch_queue.hpp:96
Raised when a reader attempts to access an uninitialized buffer.
Definition async_errors.hpp:68
Raised when a writer requests a mutable reference before construction.
Definition async_errors.hpp:74
A lightweight, thread-safe, reference-counted storage for a single object.
Definition shared_storage.hpp:94
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
constexpr std::string_view format_as(uni20::async::EpochContext::Phase p)
Definition epoch_context.hpp:842
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
Definition data_table.cpp:8
Reference-counted optional in-place storage used by async buffers.
Definition epoch_context.hpp:186
int generation
Definition epoch_context.hpp:187
Phase phase
Definition epoch_context.hpp:188
EpochContext const * next_epoch
Definition epoch_context.hpp:189
int num_writers
Definition epoch_context.hpp:191
std::vector< std::coroutine_handle<> > writer_tasks
Definition epoch_context.hpp:195
int num_readers
Definition epoch_context.hpp:192
int total_writers
Definition epoch_context.hpp:190
std::vector< std::coroutine_handle<> > reader_tasks
Definition epoch_context.hpp:194
bool writer_active
Definition epoch_context.hpp:193
Definition epoch_context.hpp:181
BasicTask task
Definition epoch_context.hpp:182
Selects the task-registry implementation for the current build mode.
#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 TRACE_STACK(...)
Definition trace.hpp:192