Scheduler & Watchdog¶
See also
For complete class and member documentation, see the API Reference.
Task scheduling, dependency tracking, and timeout monitoring.
All classes are in the dftracer::utils namespace.
For executor configuration and progress tracking, see Executor Classes. For task creation and DAG building, see Task System.
graph TB
subgraph Scheduling["Task Scheduling"]
Scheduler["Scheduler"]
Watchdog["Watchdog"]
Executor["Executor"]
end
subgraph Policies["Error Handling"]
ErrorPolicy["ErrorPolicy enum"]
ErrorHandler["ErrorHandler callback"]
end
subgraph Monitoring["Monitoring"]
TaskExec["TaskExecution"]
TimeoutCB["TimeoutCallback"]
WarningCB["WarningCallback"]
end
Scheduler --> Executor
Scheduler --> Watchdog
Scheduler --> ErrorPolicy
Scheduler --> ErrorHandler
Watchdog --> TaskExec
Watchdog --> TimeoutCB
Watchdog --> WarningCB
Scheduler¶
DAG task scheduler with dependency tracking and error handling.
The Scheduler manages the lifecycle of tasks in a directed acyclic graph (DAG):
Validates task dependencies and input types
Prepares inputs from parent task results (using combiners)
Submits ready tasks to the Executor
Tracks completion and propagates results to children
Handles errors according to the configured ErrorPolicy
Error policies:
FAIL_FAST- Stop immediately on first error (default)CONTINUE- Continue other branches, skip children of the failed taskCUSTOM- Delegate to a user-providedErrorHandlercallback
Defined in core/pipeline/pipeline_config.h. There is no RETRY policy at
this layer; retry is a utility-level behavior. ErrorHandler is
std::function<void(std::shared_ptr<Task>, std::exception_ptr)>.
Construction:
// Minimal: scheduler over an executor (no watchdog)
explicit Scheduler(Executor* executor);
// Full: bind a watchdog and take timeouts/policy from PipelineConfig
Scheduler(Executor* executor, Watchdog* watchdog, const PipelineConfig& config);
In practice Pipeline owns the Runtime (executor + watchdog) and the
Scheduler; you rarely construct one directly. Construct it manually only
when embedding the runtime or writing tests.
Key methods:
schedule(source, input)- begin execution from a source task; also a typed overloadschedule<T>(source, T&&)submit_dynamic_task(task, input)- submit a task discovered at runtime (intra-task parallelism)set_error_policy(policy)/set_error_handler(handler)set_progress_callback(cb)-cb(completed, total)set_global_timeout(ms)/set_default_task_timeout(ms)(both takestd::chrono::milliseconds;0= wait forever)request_shutdown()/is_shutdown_requested()- cooperative shutdownreset()- clear state for a fresh execution round
Timeout support:
Global timeout for entire pipeline execution
Per-task timeout with configurable defaults
Watchdog thread monitors for deadlocks and stalls
Usage example:
auto executor = std::make_unique<Executor>(ExecutorConfig{.num_threads = 4});
Scheduler scheduler(executor.get());
scheduler.set_error_policy(ErrorPolicy::FAIL_FAST);
scheduler.set_global_timeout(std::chrono::seconds(300));
scheduler.set_progress_callback([](size_t completed, size_t total) {
std::cout << completed << "/" << total << " tasks done\n";
});
// Schedule a root task
auto task = make_task([](CoroScope& ctx, const std::any& input) -> CoroTask<int> {
co_return 42;
});
scheduler.schedule(task, std::any{});
Watchdog¶
Independent monitoring thread for timeout detection and responsiveness checks.
The Watchdog runs on a separate thread and periodically checks:
Global timeout - Has the entire pipeline exceeded its time budget?
Per-task timeout - Has any individual task exceeded its timeout?
Responsiveness - Is the executor still making progress?
When a timeout is detected, the Watchdog invokes the configured callback and can request executor shutdown for graceful termination.
Responsiveness (whether the executor is still making progress) is judged
against the executor’s own ExecutorConfig::idle_timeout and
ExecutorConfig::deadlock_timeout (see Executor Classes), not
against the Watchdog’s per-task or global timeouts.
Constructor:
explicit Watchdog(
std::chrono::milliseconds check_interval = std::chrono::milliseconds(100),
std::chrono::milliseconds global_timeout = std::chrono::milliseconds(0),
std::chrono::milliseconds default_task_timeout = std::chrono::milliseconds(0),
std::chrono::milliseconds warning_threshold = std::chrono::milliseconds(10000));
All four parameters have defaults; 0 for a timeout means “no timeout”. The
warning_threshold (default 10s) controls the long-running-task warning and
has no default of zero, so warnings fire unless you disable the callback.
Timeout hierarchy:
Per-task timeout (from
Task::with_timeout()orset_default_task_timeout())Global timeout (set via
set_global_timeout())Long-task warning threshold (
warning_threshold, configured at construction)
Typically the Watchdog is owned by the Runtime and configured from
PipelineConfig (with_watchdog, with_watchdog_interval,
with_global_timeout, with_task_timeout, with_warning_threshold); you
seldom construct it directly.
Usage example:
using namespace std::chrono_literals;
Watchdog watchdog(
100ms, // check interval
30s, // global timeout
10s, // default task timeout
10s // warning threshold for long-running tasks
);
watchdog.set_timeout_callback([](const std::string& msg) {
std::cerr << "TIMEOUT: " << msg << "\n";
});
watchdog.set_warning_callback([](const std::string& task, int64_t ms) {
std::cerr << "WARNING: " << task << " running for " << ms << "ms\n";
});
watchdog.start();
// ... run pipeline ...
watchdog.stop();
TaskExecution¶
Active task execution metadata tracked by the Watchdog:
task- Shared pointer to the trackedTaskstart_time- When the task started executingtimeout- Task-specific timeout (0 = no timeout)warning_logged- Whether a slow-task warning has been logged
See Core Runtime for full struct definition.