Custom Cycle Source
Goal
Implement ICycleManager and inject it into RealtimeThreadConfig only when built-in sleep_until timing cannot meet an external clock, hardware trigger, or existing control framework.
Interface contract
ICycleManager has four responsibilities: register_cycle(name, period_ns, callback) stores the definition; start_cycle(name) runs it; stop_cycle(name) requests stop; get_statistics(name) reports statistics. Executor does not own the object. It must stay valid for realtime registration, execution, and stopping. start_cycle() normally runs synchronously in the realtime thread start path, so it returns only after stop_cycle() and must not accidentally block the caller's shutdown path.
class ExternalClock final : public executor::ICycleManager {
public:
bool register_cycle(const std::string& name, int64_t period_ns,
std::function<void()> callback) override;
bool start_cycle(const std::string& name) override;
void stop_cycle(const std::string& name) override;
executor::CycleStatistics get_statistics(const std::string& name) const override;
};Borrow the object through RealtimeThreadConfig::cycle_manager but still register/stop via the Facade:
ExternalClock clock;
executor::RealtimeThreadConfig config;
config.cycle_manager = &clock;
config.cycle_callback = [] { run_control_cycle(); };
executor.register_realtime_task_ex("control", config);
executor.start_realtime_task_ex("control");
// Keep clock alive through stopping.
executor.stop_realtime_task("control");See examples/realtime_can.cpp for a minimal implementation.
Inputs, lifecycle, and failure
register_cycle() receives a pre-bound std::function<void()>. Save it by value and invoke it when the cycle triggers; never retain a reference to the registration argument. Bind business input by value or stable owner in config.cycle_callback.
cycle_manager is a borrowed raw pointer. Stop realtime work and let start_cycle() return before destroying business objects or the source. If the source has helper threads, join them before destruction so none calls a destroyed callback.
- Do not start the cycle source after registration failure; inspect
ExecutorResultand clean resources. start_cycle()reports startup failure without leaving a background thread.stop_cycle()wakes/ends waits sostop_realtime_task()is bounded.- Callback respects realtime budget: no unbounded waiting, allocation, or uncontrolled locking; statistics report cycles, timeout, and running state.
An external trigger does not remove deployment checks for priority, affinity, memory locking, or permissions. Query realtime status and handle fallback.
Use built-in timing for ordinary maintenance and normal realtime loops. An external manager adds stop ordering, exception isolation, thread ownership, and test burden merely for “more precision.”
Next: execution paths.