Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
debug_scheduler.hpp
Go to the documentation of this file.
1
3
4#pragma once
5
6#include "async.hpp"
7#include "cuda_task.hpp"
8#include "scheduler.hpp"
9#include "task_registry.hpp"
10#include <algorithm>
11#include <cstdint>
12#include <numeric>
13#include <random>
15#include <utility>
16#include <vector>
17
18namespace uni20::async
19{
20
21namespace detail
22{
23inline void service_task_registry_debug_requests()
24{
25#if UNI20_DEBUG_ASYNC_TASKS
27#endif
28}
29
30inline void dump_deadlock_graphviz_snapshot()
31{
32#if UNI20_DEBUG_ASYNC_TASKS
35 {
36 display::info("Async Graphviz DAG snapshot written to {}", path);
37 }
38#endif
39}
40} // namespace detail
41
44{
46 fifo,
48 reverse,
50 random,
51};
52
61
64 public:
66 explicit DebugScheduler(DebugSchedulerOptions options = {}) : options_(options), random_engine_(options.random_seed)
67 {}
68
71 void schedule(AsyncTask&& task) override
72 {
73 TRACE_MODULE(ASYNC, "Scheduling a task", &task, task.coroutine_handle());
74 TaskRegistry::record_task_scheduled(task.coroutine_handle());
75 if (task.set_scheduler(this))
76 {
77 this->enqueue_task(std::move(task));
78 }
79 }
80
83 {
84 TRACE_MODULE(ASYNC, "~DebugScheduler", Handles_.size());
85
86 if (std::uncaught_exceptions() > 0)
87 {
88 while (!Handles_.empty())
89 {
90 Handles_.back().abandon_leak();
91 Handles_.pop_back();
92 }
93 }
94 }
95
98 [[nodiscard]] bool can_run() const noexcept { return !Blocked_ && !Handles_.empty(); }
99
101 void run();
102
104 void run_all();
105
108 void pause() override { Blocked_ = true; }
109
111 void resume() override { Blocked_ = false; }
112
115 void help_while_waiting(const WaitPredicate& is_ready) override
116 {
117 if (is_ready())
118 {
119 return;
120 }
121
122 if (Blocked_ || Handles_.empty())
123 {
124 detail::dump_deadlock_graphviz_snapshot();
126 PANIC("**DEADLOCK** get_wait object is not available but there are no runnable tasks!");
127 }
128 this->run();
129 }
130
133 [[nodiscard]] bool done() const noexcept { return Handles_.empty(); }
134
136 [[nodiscard]] DebugSchedulerOptions const& options() const noexcept { return options_; }
137
138 private:
139 void execute_batch_impl(LightweightTaskBatch const& batch) override
140 {
141 std::vector<std::size_t> indices(batch.size());
142 std::iota(indices.begin(), indices.end(), std::size_t{0});
143 this->order_batch(indices);
144 for (std::size_t const index : indices)
145 batch(index);
146 }
147
148 bool accepts_route(TaskRoute route) const noexcept override
149 {
150 return route.domain == TaskDomain::host && !route.cuda_device;
151 }
152
153 bool can_direct_transfer(TaskHandle, TaskHandle) const noexcept override { return true; }
154
155 // Internal resubmission
156 void reschedule(BasicTask&& task) override
157 {
158 TRACE_MODULE(ASYNC, "Rescheduling a task", &task, task.coroutine_handle());
159 // Assume sched_ is already set
160 this->enqueue_task(std::move(task));
161 }
162
163 bool Blocked_ = false;
164
165 std::vector<BasicTask> Handles_;
166 DebugSchedulerOptions options_;
167 std::mt19937_64 random_engine_;
168
169 template <class T> void order_batch(std::vector<T>& batch)
170 {
171 switch (options_.order)
172 {
174 return;
176 std::reverse(batch.begin(), batch.end());
177 return;
179 // Keep the permutation stable across standard-library implementations.
180 for (std::size_t remaining = batch.size(); remaining > 1; --remaining)
181 {
182 auto const selected = static_cast<std::size_t>(random_engine_() % remaining);
183 std::swap(batch[remaining - 1], batch[selected]);
184 }
185 return;
186 }
187 PANIC("invalid DebugScheduler ordering policy", static_cast<int>(options_.order));
188 }
189
190 protected:
192 void enqueue_task(BasicTask&& task) { Handles_.push_back(std::move(task)); }
193
195 virtual void resume_task(BasicTask& task) { task.resume(); }
196};
197
198namespace detail
199{
201inline DebugScheduler DefaultScheduler;
203inline IAsyncScheduler* global_scheduler = &DefaultScheduler;
204} // namespace detail
205
208inline void set_global_scheduler(IAsyncScheduler* sched) { detail::global_scheduler = sched; }
209
212inline IAsyncScheduler* get_global_scheduler() { return detail::global_scheduler; }
213
215inline void reset_global_scheduler() { detail::global_scheduler = &detail::DefaultScheduler; }
216
217// ScopedScheduler is useful for testing; set the scheduler for the lifetime of a block
220 public:
224 {
225 old_ = get_global_scheduler();
227 }
230
231 private:
232 IAsyncScheduler* old_;
233};
234
237inline void schedule(AsyncTask&& task) { get_global_scheduler()->schedule(std::move(task)); }
238
243template <class Function>
244 requires std::invocable<Function&, std::size_t>
245void execute_batch(std::size_t size, Function&& function)
246{
247 get_global_scheduler()->execute_batch(size, std::forward<Function>(function));
248}
249
253inline void schedule(CudaTask&& task)
254{
255 auto* scheduler = dynamic_cast<ICudaScheduler*>(get_global_scheduler());
256 CHECK(scheduler != nullptr, "the global scheduler does not accept CUDA tasks");
257 scheduler->schedule(std::move(task));
258}
259
265inline void schedule(CudaTask&& task, int device)
266{
267 auto* scheduler = dynamic_cast<ICudaScheduler*>(get_global_scheduler());
268 CHECK(scheduler != nullptr, "the global scheduler does not accept CUDA tasks");
269 scheduler->schedule(std::move(task), device);
270}
271
274template <typename T> void EpochContextReader<T>::wait() const
275{
276 auto* sched = get_global_scheduler();
277 if (!this->ready())
278 {
279 CHECK(sched);
280 auto epoch = epoch_;
281 sched->wait_for(IAsyncScheduler::WaitRequest{
282 .is_ready = [epoch] { return epoch->reader_ready(); },
283 .notify_when_ready =
284 [epoch](IAsyncScheduler::WaitWakeup notify) { epoch->reader_notify_when_ready(std::move(notify)); },
285 });
286 }
287}
288
292template <typename T> void EpochContextReader<T>::wait(IAsyncScheduler& sched) const
293{
294 if (!this->ready())
295 {
296 auto epoch = epoch_;
298 .is_ready = [epoch] { return epoch->reader_ready(); },
299 .notify_when_ready =
300 [epoch](IAsyncScheduler::WaitWakeup notify) { epoch->reader_notify_when_ready(std::move(notify)); },
301 });
302 }
303}
304
308template <typename T> T const& EpochContextReader<T>::get_wait() const
309{
310 this->wait();
311 return this->data();
312}
313
318template <typename T> T const& EpochContextReader<T>::get_wait(IAsyncScheduler& sched) const
319{
320 this->wait(sched);
321 return this->data();
322}
323
327template <typename T> T&& EpochContextWriter<T>::move_from_wait()
328{
329 auto* sched = get_global_scheduler();
330 if (!this->ready())
331 {
332 CHECK(sched);
333 auto epoch = this->epoch_context_shared();
334 sched->wait_for(IAsyncScheduler::WaitRequest{
335 .is_ready = [this] { return this->ready(); },
336 .notify_when_ready =
337 [epoch](IAsyncScheduler::WaitWakeup notify) { epoch->writer_notify_when_ready(std::move(notify)); },
338 });
339 }
340 return std::move(this->data());
341}
342
343//-----------------------------------------------------------------------------
344// Inline definitions
345//-----------------------------------------------------------------------------
346
348{
349 DEBUG_TRACE_MODULE(ASYNC, "DebugScheduler::run");
350 detail::service_task_registry_debug_requests();
351 if (Blocked_)
352 {
353 DEBUG_TRACE_MODULE(ASYNC, "run() on a blocked DebugQueue: doing nothing");
354 return;
355 }
356 TRACE_MODULE(ASYNC, "Got some coroutines to resume", Handles_.size());
357
358 std::vector<BasicTask> H;
359 std::swap(H, Handles_);
360 this->order_batch(H);
361 for (auto&& h : H)
362 {
363 TRACE_MODULE(ASYNC, "resuming coroutine...", &h, h.coroutine_handle());
364 this->resume_task(h);
365 CHECK(!h);
366 TRACE_MODULE(ASYNC, "here", &h, Handles_.size());
367 }
368 detail::service_task_registry_debug_requests();
369}
370
372{
373 DEBUG_TRACE_MODULE(ASYNC, "DebugScheduler::run_all");
374 detail::service_task_registry_debug_requests();
375 if (Blocked_)
376 {
377 DEBUG_TRACE_MODULE(ASYNC, "run() on a blocked DebugQueue: doing nothing");
378 return;
379 }
380 while (!done())
381 {
382 run();
383 }
384 detail::service_task_registry_debug_requests();
385}
386
387} // namespace uni20::async
static bool service_debug_requests()
Processes pending dump requests on the calling thread.
static bool dump_graphviz_file_best_effort(std::string const &path)
Writes a best-effort Graphviz DOT snapshot to a file.
static void dump()
Prints global task-registry diagnostics.
static std::string default_graphviz_dump_path()
Creates a default DOT output path for the current process.
static void record_task_scheduled(std::coroutine_handle<> h)
Records the call stack where a coroutine was submitted to a scheduler.
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
void resume()
Resume the coroutine and transfer this ownership claim.
Definition async_task_impl.hpp:77
CUDA-constrained public task type used for typed initial admission.
Definition cuda_task.hpp:14
Single-threaded scheduler with configurable runnable-batch ordering.
Definition debug_scheduler.hpp:63
~DebugScheduler()
Destructor.
Definition debug_scheduler.hpp:82
void enqueue_task(BasicTask &&task)
Add one already-bound task to the runnable queue.
Definition debug_scheduler.hpp:192
void resume() override
Unblock the scheduler.
Definition debug_scheduler.hpp:111
bool done() const noexcept
Check whether the runnable queue is empty.
Definition debug_scheduler.hpp:133
void run_all()
Run until no runnable tasks remain.
Definition debug_scheduler.hpp:371
bool can_run() const noexcept
Reports whether at least one runnable task exists.
Definition debug_scheduler.hpp:98
void pause() override
Block the scheduler from running.
Definition debug_scheduler.hpp:108
void run()
Run one batch of scheduled coroutines in the configured order.
Definition debug_scheduler.hpp:347
DebugSchedulerOptions const & options() const noexcept
Return the scheduler's immutable ordering configuration.
Definition debug_scheduler.hpp:136
void schedule(AsyncTask &&task) override
Enqueue a task for later run.
Definition debug_scheduler.hpp:71
virtual void resume_task(BasicTask &task)
Resume one queued task in the execution context selected by this scheduler.
Definition debug_scheduler.hpp:195
void help_while_waiting(const WaitPredicate &is_ready) override
Drives one runnable task while waiting for readiness.
Definition debug_scheduler.hpp:115
DebugScheduler(DebugSchedulerOptions options={})
Default-construct an empty scheduler.
Definition debug_scheduler.hpp:66
void wait() const
Wait for the epoch to become available on the global scheduler.
Definition debug_scheduler.hpp:274
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
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
Scheduler interface for initial AsyncTask submission and host-side progress.
Definition scheduler.hpp:89
std::function< void()> WaitWakeup
Definition scheduler.hpp:128
std::function< bool()> WaitPredicate
Definition scheduler.hpp:127
virtual void schedule(AsyncTask &&task)=0
Schedule a coroutine for its initial execution.
virtual void wait_for(const WaitPredicate &is_ready)
Block the calling thread until is_ready returns true.
Definition scheduler.hpp:166
void execute_batch(std::size_t size, Function &&function)
Execute a synchronous batch of independent lightweight work items.
Definition scheduler.hpp:113
Scheduler interface for initial CudaTask admission.
Definition cuda_task.hpp:49
Borrowed type-erased description of a synchronous lightweight task batch.
Definition scheduler.hpp:28
auto size() const noexcept -> std::size_t
Return the number of independent work items.
Definition scheduler.hpp:45
RAII helper that temporarily overrides the global scheduler.
Definition debug_scheduler.hpp:219
ScopedScheduler(IAsyncScheduler *sched)
Install a temporary global scheduler until destruction.
Definition debug_scheduler.hpp:223
~ScopedScheduler()
Restore the previous global scheduler.
Definition debug_scheduler.hpp:229
Defines the CUDA task admission type and scheduler interface.
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
void schedule(AsyncTask &&task)
Schedule a task on the currently configured global scheduler.
Definition debug_scheduler.hpp:237
void set_global_scheduler(IAsyncScheduler *sched)
Sets the process-wide scheduler used by async helpers.
Definition debug_scheduler.hpp:208
IAsyncScheduler * get_global_scheduler()
Returns the currently-installed process-wide scheduler.
Definition debug_scheduler.hpp:212
void reset_global_scheduler()
Restores the process-wide scheduler to the default debug scheduler.
Definition debug_scheduler.hpp:215
void execute_batch(std::size_t size, Function &&function)
Execute a synchronous lightweight batch on the global scheduler.
Definition debug_scheduler.hpp:245
DebugSchedulerOrder
Runnable-batch ordering policy for DebugScheduler.
Definition debug_scheduler.hpp:44
@ reverse
Execute each runnable batch in reverse submission order.
@ random
Execute each runnable batch in seeded pseudo-random order.
@ fifo
Execute each runnable batch in submission order.
void info(checked_format_string< Args... > format, Args &&... args)
Emit an informational display message.
Definition display.hpp:393
Defines internal task routing and the host-oriented scheduler interface.
Configuration for deterministic debug scheduling.
Definition debug_scheduler.hpp:55
std::uint64_t random_seed
Initial state for the reproducible random policy.
Definition debug_scheduler.hpp:59
DebugSchedulerOrder order
Runnable-batch ordering policy.
Definition debug_scheduler.hpp:57
Readiness predicate and optional targeted wakeup registration for a blocking wait.
Definition scheduler.hpp:133
WaitPredicate is_ready
Predicate that reports whether the requested value is available.
Definition scheduler.hpp:135
Selects the task-registry implementation for the current build mode.
Async aliases for lazy tensor-level views.
#define CHECK(cond,...)
Definition trace.hpp:299
#define TRACE_MODULE(m,...)
Definition trace.hpp:149
#define DEBUG_TRACE_MODULE(m,...)
Definition trace.hpp:511
#define PANIC(...)
Definition trace.hpp:382