DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
failsafe.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file failsafe.h
6 * @brief Software watchdog: deadlock detection + fail-safe safe-state (DETWS_ENABLE_FAILSAFE).
7 *
8 * A fixed registry of "lifelines" - a task, worker, or control loop that must check in
9 * (`detws_failsafe_feed`) at least every `deadline_ms`. If one stops checking in (a hang, a
10 * deadlock, a wedged loop), `detws_failsafe_check()` detects it and fires a breach callback exactly
11 * once per stuck episode, so the app can drive its outputs to a known-safe state (motors off, valves
12 * closed), log, and optionally reset. It complements the hardware task watchdog: this one is
13 * app-defined, per-lifeline, and knows *which* subsystem wedged.
14 *
15 * Zero heap (a static registry), no stdlib. The overdue test is a wrap-safe unsigned time delta, so it
16 * is correct across a `millis()` rollover. The evaluation core takes an explicit `now`, so it is fully
17 * host-testable with a synthetic clock; the no-`now` wrappers read the pluggable `detws_millis()`.
18 */
19
20#ifndef DETERMINISTICESPASYNCWEBSERVER_FAILSAFE_H
21#define DETERMINISTICESPASYNCWEBSERVER_FAILSAFE_H
22
23#include "ServerConfig.h"
24#include <stddef.h>
25#include <stdint.h>
26
27#if DETWS_ENABLE_FAILSAFE
28
29/** @brief One monitored lifeline. */
30struct DetwsLifeline
31{
32 const char *name; ///< label (for the breach callback + JSON); not copied.
33 uint32_t deadline_ms; ///< max interval between feeds before it is considered stuck.
34 uint32_t last_feed_ms; ///< time of the last check-in (detws_millis units).
35 bool armed; ///< slot in use.
36 bool breached; ///< currently in breach (so the callback fires once per episode).
37};
38
39/** @brief Breach callback: invoked once when @p id (named @p name) misses its deadline. */
40typedef void (*DetwsFailsafeCb)(int id, const char *name, void *arg);
41
42// ---------------------------------------------------------------------------
43// Host-testable core
44// ---------------------------------------------------------------------------
45
46/**
47 * @brief Is a lifeline overdue at @p now?
48 *
49 * Wrap-safe: the unsigned delta `now - last_feed` is correct across a millis() rollover as long as the
50 * true gap is under 2^32 ms (~49 days), which any real deadline is.
51 */
52static inline bool detws_lifeline_overdue(uint32_t now, uint32_t last_feed_ms, uint32_t deadline_ms)
53{
54 return (uint32_t)(now - last_feed_ms) > deadline_ms;
55}
56
57// ---------------------------------------------------------------------------
58// Registry API
59// ---------------------------------------------------------------------------
60
61/** @brief Clear the whole registry and the breach callback (mainly for tests / re-init). */
62void detws_failsafe_reset(void);
63
64/**
65 * @brief Register a lifeline that must check in at least every @p deadline_ms.
66 * @param name label used in the breach callback + JSON (borrowed, not copied).
67 * @param deadline_ms max allowed interval between feeds.
68 * @return the lifeline id (>= 0), or -1 if the registry (DETWS_FAILSAFE_MAX_LIFELINES) is full.
69 *
70 * The lifeline starts fed at the registration time, so it is not instantly overdue.
71 */
72int detws_failsafe_register(const char *name, uint32_t deadline_ms);
73int detws_failsafe_register_at(const char *name, uint32_t deadline_ms, uint32_t now);
74
75/** @brief Check in lifeline @p id (clears any breach). Reads detws_millis(). */
76bool detws_failsafe_feed(int id);
77bool detws_failsafe_feed_at(int id, uint32_t now);
78
79/** @brief Install the breach callback (fired once per stuck episode). */
80void detws_failsafe_on_breach(DetwsFailsafeCb cb, void *arg);
81
82/**
83 * @brief Evaluate every armed lifeline; fire the callback for each newly-overdue one.
84 * @return a bitmask of the lifeline ids currently in breach (bit i set = id i breached).
85 *
86 * A lifeline that is overdue and not already flagged fires the callback once and is marked breached
87 * until it is fed again; already-breached lifelines stay in the returned mask but do not re-fire.
88 */
89uint32_t detws_failsafe_check_at(uint32_t now);
90uint32_t detws_failsafe_check(void);
91
92/** @brief Serialize the registry as JSON for a /health-style endpoint. @return bytes written (excl NUL). */
93int detws_failsafe_json_at(uint32_t now, char *out, size_t cap);
94
95#endif // DETWS_ENABLE_FAILSAFE
96#endif // DETERMINISTICESPASYNCWEBSERVER_FAILSAFE_H
User-facing configuration for DeterministicESPAsyncWebServer.