DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
failsafe.cpp
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.cpp
6 * @brief Software watchdog / deadlock detection + safe-state (see failsafe.h).
7 */
8
10
11#if DETWS_ENABLE_FAILSAFE
12
13#include "services/clock.h" // detws_millis() - the pluggable monotonic clock
14
15namespace
16{
17// All failsafe state, owned by one instance (internal linkage via the anon namespace):
18// grouped for auditability, unreachable from any other translation unit.
19struct FailsafeCtx
20{
21 DetwsLifeline lines[DETWS_FAILSAFE_MAX_LIFELINES];
22 DetwsFailsafeCb cb = nullptr;
23 void *cb_arg = nullptr;
24};
25FailsafeCtx s_fs;
26
27// Minimal unsigned -> decimal, no stdlib; returns chars written.
28size_t u32_dec(uint32_t v, char *out)
29{
30 char tmp[10];
31 size_t n = 0;
32 do
33 {
34 tmp[n++] = (char)('0' + v % 10);
35 v /= 10;
36 } while (v);
37 for (size_t i = 0; i < n; i++)
38 out[i] = tmp[n - 1 - i];
39 return n;
40}
41} // namespace
42
43void detws_failsafe_reset(void)
44{
45 for (int i = 0; i < DETWS_FAILSAFE_MAX_LIFELINES; i++)
46 s_fs.lines[i] = DetwsLifeline{};
47 s_fs.cb = nullptr;
48 s_fs.cb_arg = nullptr;
49}
50
51int detws_failsafe_register_at(const char *name, uint32_t deadline_ms, uint32_t now)
52{
53 for (int i = 0; i < DETWS_FAILSAFE_MAX_LIFELINES; i++)
54 {
55 if (!s_fs.lines[i].armed)
56 {
57 s_fs.lines[i].name = name;
58 s_fs.lines[i].deadline_ms = deadline_ms;
59 s_fs.lines[i].last_feed_ms = now; // starts fed, so it is not instantly overdue
60 s_fs.lines[i].armed = true;
61 s_fs.lines[i].breached = false;
62 return i;
63 }
64 }
65 return -1;
66}
67
68int detws_failsafe_register(const char *name, uint32_t deadline_ms)
69{
70 return detws_failsafe_register_at(name, deadline_ms, detws_millis());
71}
72
73bool detws_failsafe_feed_at(int id, uint32_t now)
74{
75 if (id < 0 || id >= DETWS_FAILSAFE_MAX_LIFELINES || !s_fs.lines[id].armed)
76 return false;
77 s_fs.lines[id].last_feed_ms = now;
78 s_fs.lines[id].breached = false; // a fresh check-in clears the breach so it can fire again next time
79 return true;
80}
81
82bool detws_failsafe_feed(int id)
83{
84 return detws_failsafe_feed_at(id, detws_millis());
85}
86
87void detws_failsafe_on_breach(DetwsFailsafeCb cb, void *arg)
88{
89 s_fs.cb = cb;
90 s_fs.cb_arg = arg;
91}
92
93uint32_t detws_failsafe_check_at(uint32_t now)
94{
95 uint32_t mask = 0;
96 for (int i = 0; i < DETWS_FAILSAFE_MAX_LIFELINES; i++)
97 {
98 DetwsLifeline &l = s_fs.lines[i];
99 if (!l.armed)
100 continue;
101 if (!detws_lifeline_overdue(now, l.last_feed_ms, l.deadline_ms))
102 continue;
103 mask |= (1u << i);
104 if (l.breached) // fire once per stuck episode
105 continue;
106 l.breached = true;
107 if (s_fs.cb)
108 s_fs.cb(i, l.name, s_fs.cb_arg);
109 }
110 return mask;
111}
112
113uint32_t detws_failsafe_check(void)
114{
115 return detws_failsafe_check_at(detws_millis());
116}
117
118// append a literal into out[*n], bounded by cap (leaving room for the NUL); truncates safely on overflow.
119static void fs_put(char *out, size_t cap, size_t *n, const char *s)
120{
121 while (*s && *n + 1 < cap)
122 out[(*n)++] = *s++;
123}
124// append @p v as decimal into out[*n], same bound.
125static void fs_put_u32(char *out, size_t cap, size_t *n, uint32_t v)
126{
127 char b[10];
128 size_t k = u32_dec(v, b);
129 for (size_t i = 0; i < k && *n + 1 < cap; i++)
130 out[(*n)++] = b[i];
131}
132
133int detws_failsafe_json_at(uint32_t now, char *out, size_t cap)
134{
135 // {"lifelines":[{"name":"...","overdue":false,"age_ms":N,"deadline_ms":N},...]}
136 if (!out || cap == 0)
137 return 0;
138 size_t n = 0;
139 fs_put(out, cap, &n, "{\"lifelines\":[");
140 bool first = true;
141 for (int i = 0; i < DETWS_FAILSAFE_MAX_LIFELINES; i++)
142 {
143 DetwsLifeline &l = s_fs.lines[i];
144 if (!l.armed)
145 continue;
146 if (!first)
147 fs_put(out, cap, &n, ",");
148 first = false;
149 fs_put(out, cap, &n, "{\"name\":\"");
150 fs_put(out, cap, &n, l.name ? l.name : "");
151 fs_put(out, cap, &n, "\",\"overdue\":");
152 fs_put(out, cap, &n, detws_lifeline_overdue(now, l.last_feed_ms, l.deadline_ms) ? "true" : "false");
153 fs_put(out, cap, &n, ",\"age_ms\":");
154 fs_put_u32(out, cap, &n, now - l.last_feed_ms);
155 fs_put(out, cap, &n, ",\"deadline_ms\":");
156 fs_put_u32(out, cap, &n, l.deadline_ms);
157 fs_put(out, cap, &n, "}");
158 }
159 fs_put(out, cap, &n, "]}");
160 out[n < cap ? n : cap - 1] = '\0';
161 return (int)n;
162}
163
164#endif // DETWS_ENABLE_FAILSAFE
#define DETWS_FAILSAFE_MAX_LIFELINES
Max monitored lifelines in the fail-safe registry (static, zero-heap).
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
Software watchdog: deadlock detection + fail-safe safe-state (DETWS_ENABLE_FAILSAFE).