Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 1 | // Copyright (c) FIRST and other WPILib contributors. |
| 2 | // Open Source Software; you can modify and/or share it under the terms of |
| 3 | // the WPILib BSD license file in the root directory of this project. |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 4 | |
| 5 | #include "frc/Watchdog.h" |
| 6 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 7 | #include <atomic> |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 8 | #include <thread> |
| 9 | #include <utility> |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 10 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 11 | #include <fmt/format.h> |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 12 | #include <hal/Notifier.h> |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 13 | #include <wpi/mutex.h> |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 14 | #include <wpi/priority_queue.h> |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 15 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 16 | #include "frc/Errors.h" |
| 17 | #include "frc/Timer.h" |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 18 | |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 19 | using namespace frc; |
| 20 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 21 | class Watchdog::Impl { |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 22 | public: |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 23 | Impl(); |
| 24 | ~Impl(); |
| 25 | |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 26 | template <typename T> |
| 27 | struct DerefGreater { |
| 28 | constexpr bool operator()(const T& lhs, const T& rhs) const { |
| 29 | return *lhs > *rhs; |
| 30 | } |
| 31 | }; |
| 32 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 33 | wpi::mutex m_mutex; |
| 34 | std::atomic<HAL_NotifierHandle> m_notifier; |
| 35 | wpi::priority_queue<Watchdog*, std::vector<Watchdog*>, |
| 36 | DerefGreater<Watchdog*>> |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 37 | m_watchdogs; |
| 38 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 39 | void UpdateAlarm(); |
| 40 | |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 41 | private: |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 42 | void Main(); |
| 43 | |
| 44 | std::thread m_thread; |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 45 | }; |
| 46 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 47 | Watchdog::Impl::Impl() { |
| 48 | int32_t status = 0; |
| 49 | m_notifier = HAL_InitializeNotifier(&status); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 50 | FRC_CheckErrorStatus(status, "{}", "starting watchdog notifier"); |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 51 | HAL_SetNotifierName(m_notifier, "Watchdog", &status); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 52 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 53 | m_thread = std::thread([=] { Main(); }); |
| 54 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 55 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 56 | Watchdog::Impl::~Impl() { |
| 57 | int32_t status = 0; |
| 58 | // atomically set handle to 0, then clean |
| 59 | HAL_NotifierHandle handle = m_notifier.exchange(0); |
| 60 | HAL_StopNotifier(handle, &status); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 61 | FRC_ReportError(status, "{}", "stopping watchdog notifier"); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 62 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 63 | // Join the thread to ensure the handler has exited. |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 64 | if (m_thread.joinable()) { |
| 65 | m_thread.join(); |
| 66 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 67 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 68 | HAL_CleanNotifier(handle, &status); |
| 69 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 70 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 71 | void Watchdog::Impl::UpdateAlarm() { |
| 72 | int32_t status = 0; |
| 73 | // Return if we are being destructed, or were not created successfully |
| 74 | auto notifier = m_notifier.load(); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 75 | if (notifier == 0) { |
| 76 | return; |
| 77 | } |
| 78 | if (m_watchdogs.empty()) { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 79 | HAL_CancelNotifierAlarm(notifier, &status); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 80 | } else { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 81 | HAL_UpdateNotifierAlarm( |
| 82 | notifier, |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 83 | static_cast<uint64_t>(m_watchdogs.top()->m_expirationTime.value() * |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 84 | 1e6), |
| 85 | &status); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 86 | } |
| 87 | FRC_CheckErrorStatus(status, "{}", "updating watchdog notifier alarm"); |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | void Watchdog::Impl::Main() { |
| 91 | for (;;) { |
| 92 | int32_t status = 0; |
| 93 | HAL_NotifierHandle notifier = m_notifier.load(); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 94 | if (notifier == 0) { |
| 95 | break; |
| 96 | } |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 97 | uint64_t curTime = HAL_WaitForNotifierAlarm(notifier, &status); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 98 | if (curTime == 0 || status != 0) { |
| 99 | break; |
| 100 | } |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 101 | |
| 102 | std::unique_lock lock(m_mutex); |
| 103 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 104 | if (m_watchdogs.empty()) { |
| 105 | continue; |
| 106 | } |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 107 | |
| 108 | // If the condition variable timed out, that means a Watchdog timeout |
| 109 | // has occurred, so call its timeout function. |
| 110 | auto watchdog = m_watchdogs.pop(); |
| 111 | |
| 112 | units::second_t now{curTime * 1e-6}; |
| 113 | if (now - watchdog->m_lastTimeoutPrintTime > kMinPrintPeriod) { |
| 114 | watchdog->m_lastTimeoutPrintTime = now; |
| 115 | if (!watchdog->m_suppressTimeoutMessage) { |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 116 | FRC_ReportError(warn::Warning, "Watchdog not fed within {:.6f}s", |
| 117 | watchdog->m_timeout.value()); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 118 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 119 | } |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 120 | |
| 121 | // Set expiration flag before calling the callback so any manipulation |
| 122 | // of the flag in the callback (e.g., calling Disable()) isn't |
| 123 | // clobbered. |
| 124 | watchdog->m_isExpired = true; |
| 125 | |
| 126 | lock.unlock(); |
| 127 | watchdog->m_callback(); |
| 128 | lock.lock(); |
| 129 | |
| 130 | UpdateAlarm(); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 131 | } |
| 132 | } |
| 133 | |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 134 | Watchdog::Watchdog(units::second_t timeout, std::function<void()> callback) |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 135 | : m_timeout(timeout), m_callback(std::move(callback)), m_impl(GetImpl()) {} |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 136 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 137 | Watchdog::~Watchdog() { |
| 138 | try { |
| 139 | Disable(); |
| 140 | } catch (const RuntimeError& e) { |
| 141 | e.Report(); |
| 142 | } |
| 143 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 144 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 145 | Watchdog::Watchdog(Watchdog&& rhs) { |
| 146 | *this = std::move(rhs); |
| 147 | } |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 148 | |
| 149 | Watchdog& Watchdog::operator=(Watchdog&& rhs) { |
| 150 | m_impl = rhs.m_impl; |
| 151 | std::scoped_lock lock(m_impl->m_mutex); |
| 152 | m_startTime = rhs.m_startTime; |
| 153 | m_timeout = rhs.m_timeout; |
| 154 | m_expirationTime = rhs.m_expirationTime; |
| 155 | m_callback = std::move(rhs.m_callback); |
| 156 | m_lastTimeoutPrintTime = rhs.m_lastTimeoutPrintTime; |
| 157 | m_suppressTimeoutMessage = rhs.m_suppressTimeoutMessage; |
| 158 | m_tracer = std::move(rhs.m_tracer); |
| 159 | m_isExpired = rhs.m_isExpired; |
| 160 | if (m_expirationTime != 0_s) { |
| 161 | m_impl->m_watchdogs.remove(&rhs); |
| 162 | m_impl->m_watchdogs.emplace(this); |
| 163 | } |
| 164 | return *this; |
| 165 | } |
| 166 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 167 | units::second_t Watchdog::GetTime() const { |
| 168 | return Timer::GetFPGATimestamp() - m_startTime; |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 169 | } |
| 170 | |
| 171 | void Watchdog::SetTimeout(units::second_t timeout) { |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 172 | m_startTime = Timer::GetFPGATimestamp(); |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 173 | m_tracer.ClearEpochs(); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 174 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 175 | std::scoped_lock lock(m_impl->m_mutex); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 176 | m_timeout = timeout; |
| 177 | m_isExpired = false; |
| 178 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 179 | m_impl->m_watchdogs.remove(this); |
| 180 | m_expirationTime = m_startTime + m_timeout; |
| 181 | m_impl->m_watchdogs.emplace(this); |
| 182 | m_impl->UpdateAlarm(); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 183 | } |
| 184 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 185 | units::second_t Watchdog::GetTimeout() const { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 186 | std::scoped_lock lock(m_impl->m_mutex); |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 187 | return m_timeout; |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 188 | } |
| 189 | |
| 190 | bool Watchdog::IsExpired() const { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 191 | std::scoped_lock lock(m_impl->m_mutex); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 192 | return m_isExpired; |
| 193 | } |
| 194 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 195 | void Watchdog::AddEpoch(std::string_view epochName) { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 196 | m_tracer.AddEpoch(epochName); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 197 | } |
| 198 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 199 | void Watchdog::PrintEpochs() { |
| 200 | m_tracer.PrintEpochs(); |
| 201 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 202 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 203 | void Watchdog::Reset() { |
| 204 | Enable(); |
| 205 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 206 | |
| 207 | void Watchdog::Enable() { |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 208 | m_startTime = Timer::GetFPGATimestamp(); |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 209 | m_tracer.ClearEpochs(); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 210 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 211 | std::scoped_lock lock(m_impl->m_mutex); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 212 | m_isExpired = false; |
| 213 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 214 | m_impl->m_watchdogs.remove(this); |
| 215 | m_expirationTime = m_startTime + m_timeout; |
| 216 | m_impl->m_watchdogs.emplace(this); |
| 217 | m_impl->UpdateAlarm(); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | void Watchdog::Disable() { |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 221 | std::scoped_lock lock(m_impl->m_mutex); |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 222 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 223 | if (m_expirationTime != 0_s) { |
| 224 | m_impl->m_watchdogs.remove(this); |
| 225 | m_expirationTime = 0_s; |
| 226 | m_impl->UpdateAlarm(); |
| 227 | } |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 228 | } |
| 229 | |
| 230 | void Watchdog::SuppressTimeoutMessage(bool suppress) { |
| 231 | m_suppressTimeoutMessage = suppress; |
| 232 | } |
| 233 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 234 | bool Watchdog::operator>(const Watchdog& rhs) const { |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 235 | return m_expirationTime > rhs.m_expirationTime; |
| 236 | } |
| 237 | |
Austin Schuh | 1e69f94 | 2020-11-14 15:06:14 -0800 | [diff] [blame] | 238 | Watchdog::Impl* Watchdog::GetImpl() { |
| 239 | static Impl inst; |
| 240 | return &inst; |
Brian Silverman | 8fce748 | 2020-01-05 13:18:21 -0800 | [diff] [blame] | 241 | } |