Skip to content

Delayed Retry and Health Checks

Goal

Schedule one device retry with submit_delayed(), run health checks with submit_periodic(), and stop periodic work through status queries and cancel_task().

cpp
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

#include <executor/executor.hpp>

using namespace std::chrono_literals;

int main() {
    executor::Executor executor;
    executor::ExecutorConfig config;
    config.min_threads = 1;
    config.max_threads = 1;
    if (!executor.initialize_ex(config)) {
        return 1;
    }

    auto retry = executor.submit_delayed(1, [] { return "retry complete"; });
    std::atomic<int> health_checks{0};
    const auto task_id = executor.submit_periodic(5, [&] { ++health_checks; });

    // 有界等待第一次健康检查完成:不依赖固定睡眠时长,调度慢的机器上也能等到。
    const auto deadline = std::chrono::steady_clock::now() + 2s;
    auto status = executor.get_periodic_task_status(task_id);
    while ((!status || status->execution_count == 0) &&
           std::chrono::steady_clock::now() < deadline) {
        std::this_thread::sleep_for(1ms);
        status = executor.get_periodic_task_status(task_id);
    }
    const bool cancelled = executor.cancel_task(task_id);

    std::cout << retry.get() << '\n';
    std::cout << "health checks=" << health_checks.load() << '\n';
    std::cout << "periodic status=" << (status && status->execution_count > 0 ? "running" : "missing")
              << ", cancelled=" << (cancelled ? "yes" : "no") << '\n';

    executor.shutdown();
    return status && status->execution_count > 0 && cancelled ? 0 : 1;
}
bash
./build/examples/tutorial/tutorial_03_delayed_periodic

Expected output:

text
retry complete
health checks=1
periodic status=running, cancelled=yes

The count depends on scheduling. The stable assertion is that the status exists before cancellation and execution_count > 0.

Status and input ownership

Keep the ID returned by submit_periodic() to call cancel_task(). get_periodic_task_status(id) returns a registered task's status; after cancellation it returns no result. get_all_periodic_task_status() is useful for monitoring and shutdown checks. A delayed task returns a future, so values, exceptions, and rejected submission remain observable through get().

submit_delayed(delay, fn, args...) accepts separate functions and arguments like submit(). submit_periodic(period, task) accepts a repeatable std::function<void()>; bind business inputs in a lambda:

cpp
auto device = std::make_shared<DeviceClient>(endpoint);
const auto id = executor.submit_periodic(1000, [device] { device->check_health(); });

The callback must be copyable. A unique_ptr capture generally cannot become std::function<void()>; use a stable owner or shared_ptr. A reference capture is valid only if its owner survives cancellation, timer stop, and every in-flight callback.

Each timer tick submits the same callback to the ordinary pool without waiting for the previous run. A callback longer than its period can overlap itself; protect mutable state with atomics, a mutex, or application-level single-flight logic.

Failure and shutdown

Periodic exceptions do not return to the submit_periodic() call. Check periodic status and configure failure status or a callback in a long-running service. Successful cancellation stops future scheduling only: work already queued or running can still complete.

Stop creating delayed/periodic work, cancel every retained ID, wait within a budget for submitted callbacks, then shut down Executor. Network or device I/O needs its own timeout.

Inject a retry exception, an overlong health check, repeated callback failures, and shutdown before delayed expiry. Do not use a larger queue to mask overlap. For exponential backoff, schedule the next delayed task after each failure with a capped attempt count; for strict phase or jitter requirements, use a dedicated real-time task instead.

Next: batch sensor frames.