eive-obsw/watchdog/Watchdog.cpp

278 lines
8.2 KiB
C++
Raw Normal View History

2021-07-29 11:35:20 +02:00
#include "Watchdog.h"
#include <errno.h>
2023-02-23 23:56:11 +01:00
#include <fcntl.h>
2021-07-29 11:35:20 +02:00
#include <poll.h>
2023-02-23 23:56:11 +01:00
#include <sys/stat.h>
#include <sys/types.h>
2021-07-29 11:35:20 +02:00
#include <unistd.h>
2023-02-23 23:56:11 +01:00
#include <cstdlib>
2021-07-29 11:35:20 +02:00
#include <cstring>
#include <filesystem>
2023-02-23 23:56:11 +01:00
#include <fstream>
#include <iostream>
#include <thread>
2021-07-29 11:35:20 +02:00
2023-02-23 23:56:11 +01:00
#include "definitions.h"
2021-07-29 11:35:20 +02:00
2023-02-23 23:56:11 +01:00
WatchdogTask::WatchdogTask() : fd(0) {
int result = 0;
// Only create the FIFO if it does not exist yet
if (not std::filesystem::exists(watchdog::FIFO_NAME)) {
// Permission 666 or rw-rw-rw-
mode_t mode = DEFFILEMODE;
result = mkfifo(watchdog::FIFO_NAME.c_str(), mode);
if (result != 0) {
2023-02-24 01:08:26 +01:00
std::cerr << "Could not created named pipe at " << watchdog::FIFO_NAME << ", error " << errno
<< ": " << strerror(errno) << std::endl;
2023-02-23 23:56:11 +01:00
throw std::runtime_error("eive-watchdog: FIFO creation failed");
}
2021-07-29 11:35:20 +02:00
#if WATCHDOG_VERBOSE_LEVEL >= 1
2023-02-24 01:08:26 +01:00
std::cout << "Pipe at " << watchdog::FIFO_NAME << " created successfully" << std::endl;
2021-07-29 11:35:20 +02:00
#endif
2023-02-23 23:56:11 +01:00
}
2021-07-29 11:59:32 +02:00
}
2023-02-23 23:56:11 +01:00
WatchdogTask::~WatchdogTask() {}
2021-07-29 11:59:32 +02:00
int WatchdogTask::performOperation() {
2023-02-23 23:56:11 +01:00
// Open FIFO read only and non-blocking
fd = open(watchdog::FIFO_NAME.c_str(), O_RDONLY | O_NONBLOCK);
if (fd < 0) {
2023-02-24 01:08:26 +01:00
std::cerr << "Opening pipe " << watchdog::FIFO_NAME << "read-only failed with " << errno << ": "
<< strerror(errno) << std::endl;
2023-02-23 23:56:11 +01:00
return -1;
}
state = States::NOT_STARTED;
2021-07-29 17:21:27 +02:00
2023-02-23 23:56:11 +01:00
while (true) {
WatchdogTask::LoopResult loopResult = watchdogLoop();
if (not stateMachine(loopResult)) {
break;
2021-07-29 11:35:20 +02:00
}
2023-02-23 23:56:11 +01:00
}
if (close(fd) < 0) {
2023-02-24 01:08:26 +01:00
std::cerr << "Closing named pipe at " << watchdog::FIFO_NAME << "failed, error " << errno
<< ": " << strerror(errno) << std::endl;
2023-02-23 23:56:11 +01:00
}
2023-02-24 01:08:26 +01:00
std::cout << "Closing" << std::endl;
2023-02-23 23:56:11 +01:00
return 0;
2021-07-29 11:35:20 +02:00
}
WatchdogTask::LoopResult WatchdogTask::watchdogLoop() {
2023-02-23 23:56:11 +01:00
using namespace std::chrono_literals;
struct pollfd waiter = {};
waiter.fd = fd;
waiter.events = POLLIN;
2021-07-29 11:35:20 +02:00
2023-02-23 23:56:11 +01:00
// Only poll one file descriptor with timeout
switch (poll(&waiter, 1, watchdog::TIMEOUT_MS)) {
case (0): {
return LoopResult::TIMEOUT;
2021-07-29 11:35:20 +02:00
}
2023-02-23 23:56:11 +01:00
case (1): {
return pollEvent(waiter);
2021-07-29 11:35:20 +02:00
}
default: {
2023-02-24 01:08:26 +01:00
std::cerr << "Unknown poll error at " << watchdog::FIFO_NAME << ", error " << errno << ": "
<< strerror(errno) << std::endl;
2023-02-23 23:56:11 +01:00
break;
2021-07-29 11:35:20 +02:00
}
2023-02-23 23:56:11 +01:00
}
return LoopResult::OK;
2021-07-29 11:35:20 +02:00
}
2021-07-29 16:31:04 +02:00
WatchdogTask::LoopResult WatchdogTask::pollEvent(struct pollfd& waiter) {
2023-02-23 23:56:11 +01:00
if (waiter.revents & POLLIN) {
ssize_t readLen = read(fd, buf.data(), buf.size());
if (readLen < 0) {
2023-02-24 01:08:26 +01:00
std::cerr << "Read error on pipe " << watchdog::FIFO_NAME << ", error " << errno << ": "
<< strerror(errno) << std::endl;
2023-02-23 23:56:11 +01:00
return LoopResult::OK;
}
2021-07-29 16:31:04 +02:00
#if WATCHDOG_VERBOSE_LEVEL == 2
2023-02-23 23:56:11 +01:00
std::cout << "Read " << readLen << " byte(s) on the pipe " << FIFO_NAME << std::endl;
2021-07-29 16:31:04 +02:00
#endif
2023-02-23 23:56:11 +01:00
else if (readLen >= 1) {
return parseCommand(readLen);
2021-07-29 16:31:04 +02:00
}
2023-02-23 23:56:11 +01:00
} else if (waiter.revents & POLLERR) {
2023-02-24 01:08:26 +01:00
std::cerr << "Poll error error on pipe " << watchdog::FIFO_NAME << std::endl;
2021-07-29 16:31:04 +02:00
return LoopResult::FAULT;
2023-02-23 23:56:11 +01:00
} else if (waiter.revents & POLLHUP) {
// Writer closed its end
return LoopResult::HUNG_UP;
}
return LoopResult::FAULT;
2021-07-29 16:31:04 +02:00
}
2023-02-23 23:56:11 +01:00
WatchdogTask::LoopResult WatchdogTask::parseCommand(ssize_t readLen) {
char readChar = buf[0];
// Cancel request
if (readChar == watchdog::first::CANCEL_CHAR) {
return LoopResult::CANCEL_REQ;
} else if (readChar == watchdog::first::SUSPEND_CHAR) {
// Suspend request
return LoopResult::SUSPEND_REQ;
} else if (readChar == watchdog::first::START_CHAR) {
if (readLen == 2 and static_cast<char>(buf[1]) == watchdog::second::WATCH_FLAG) {
return LoopResult::START_WITH_WATCH_REQ;
2021-07-29 16:31:04 +02:00
}
2023-02-23 23:56:11 +01:00
return LoopResult::START_REQ;
}
// Everything else: All working as expected
return LoopResult::OK;
2021-07-29 16:31:04 +02:00
}
int WatchdogTask::performRunningOperation() {
2023-02-23 23:56:11 +01:00
if (state != States::RUNNING) {
state = States::RUNNING;
}
if (notRunningStart.has_value()) {
notRunningStart = std::nullopt;
}
2021-07-29 17:21:27 +02:00
2023-02-23 23:56:11 +01:00
if (not obswRunning) {
if (printNotRunningLatch) {
// Reset latch so user can see timeouts
printNotRunningLatch = false;
}
2021-07-29 17:21:27 +02:00
2023-02-23 23:56:11 +01:00
obswRunning = true;
2023-02-24 01:08:26 +01:00
std::cout << "OBSW is running" << std::endl;
2021-07-29 16:31:04 +02:00
#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1
2023-02-24 01:08:26 +01:00
std::cout << "Creating " << watchdog::RUNNING_FILE_NAME << std::endl;
2023-02-23 23:56:11 +01:00
if (not std::filesystem::exists(watchdog::RUNNING_FILE_NAME)) {
std::ofstream obswRunningFile(watchdog::RUNNING_FILE_NAME);
if (not obswRunningFile.good()) {
std::cerr << "Creating file " << watchdog::RUNNING_FILE_NAME << " failed" << std::endl;
}
2021-07-29 16:31:04 +02:00
}
2023-02-23 23:56:11 +01:00
#endif
}
return 0;
2021-07-29 16:31:04 +02:00
}
2021-07-29 18:09:54 +02:00
int WatchdogTask::performNotRunningOperation(LoopResult type) {
2023-02-23 23:56:11 +01:00
// Latch prevents spam on console
if (not printNotRunningLatch) {
if (type == LoopResult::HUNG_UP) {
2023-02-24 01:08:26 +01:00
std::cout << "OBSW hung up" << std::endl;
2023-02-23 23:56:11 +01:00
} else {
2023-02-24 01:08:26 +01:00
std::cout << "The FIFO timed out, OBSW might not be running" << std::endl;
2021-07-29 17:21:27 +02:00
}
2023-02-23 23:56:11 +01:00
printNotRunningLatch = true;
}
2021-07-29 17:21:27 +02:00
2023-02-23 23:56:11 +01:00
if (not notRunningStart.has_value()) {
notRunningStart = std::chrono::system_clock::now();
}
if (obswRunning) {
2021-07-29 16:31:04 +02:00
#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1
2023-02-23 23:56:11 +01:00
if (std::filesystem::exists(watchdog::RUNNING_FILE_NAME)) {
int result = std::remove(watchdog::RUNNING_FILE_NAME.c_str());
if (result != 0) {
std::cerr << "Removing " << watchdog::RUNNING_FILE_NAME << " failed with code " << errno
<< ": " << strerror(errno) << std::endl;
}
2021-07-29 16:31:04 +02:00
}
2023-02-23 23:56:11 +01:00
#endif
obswRunning = false;
}
if (watchingObsw) {
auto timeNotRunning = std::chrono::system_clock::now() - notRunningStart.value();
if (std::chrono::duration_cast<std::chrono::milliseconds>(timeNotRunning).count() >
watchdog::MAX_NOT_RUNNING_MS) {
2023-02-24 01:08:26 +01:00
std::cout << "Restarting OBSW with systemctl" << std::endl;
2023-02-23 23:56:11 +01:00
std::system("systemctl restart obsw");
2021-07-29 18:09:54 +02:00
}
2023-02-23 23:56:11 +01:00
}
if (type == LoopResult::HUNG_UP) {
using namespace std::chrono_literals;
// Prevent spam
std::this_thread::sleep_for(2000ms);
}
return 0;
2021-07-29 16:31:04 +02:00
}
2021-07-29 17:21:27 +02:00
2023-02-23 23:56:11 +01:00
bool WatchdogTask::stateMachine(LoopResult loopResult) {
using namespace std::chrono_literals;
bool sleep = false;
switch (state) {
case (States::RUNNING): {
switch (loopResult) {
case (LoopResult::TIMEOUT):
case (LoopResult::HUNG_UP): {
performNotRunningOperation(loopResult);
break;
}
case (LoopResult::OK): {
performRunningOperation();
break;
}
case (LoopResult::SUSPEND_REQ): {
if (state == States::RUNNING or state == States::FAULTY) {
2023-02-24 01:08:26 +01:00
std::cout << "Received suspend request, suspending watchdog operations" << std::endl;
2023-02-23 23:56:11 +01:00
state = States::SUSPENDED;
}
performSuspendOperation();
sleep = true;
break;
}
case (LoopResult::CANCEL_REQ): {
2023-02-24 01:08:26 +01:00
std::cout << "Received cancel request, closing watchdog.." << std::endl;
2023-02-23 23:56:11 +01:00
return false;
}
}
2021-07-29 17:21:27 +02:00
}
2023-02-23 23:56:11 +01:00
case (States::FAULTY):
case (States::SUSPENDED):
case (States::NOT_STARTED): {
switch (loopResult) {
case (LoopResult::SUSPEND_REQ): {
// Ignore and also delay
sleep = true;
break;
}
case (LoopResult::START_REQ):
case (LoopResult::START_WITH_WATCH_REQ): {
if (state == States::NOT_STARTED or state == States::FAULTY) {
state = States::RUNNING;
}
if (loopResult == LoopResult::START_REQ) {
2023-02-24 01:08:26 +01:00
std::cout << "Start request without watch request received" << std::endl;
2023-02-23 23:56:11 +01:00
watchingObsw = false;
} else if (loopResult == LoopResult::START_WITH_WATCH_REQ) {
2023-02-24 01:08:26 +01:00
std::cout << "Start request with watch request received. Restarting OBSW if not "
"running for "
<< watchdog::MAX_NOT_RUNNING_MS / 1000 << " seconds" << std::endl;
2023-02-23 23:56:11 +01:00
watchingObsw = true;
}
performRunningOperation();
break;
}
default: {
sleep = true;
}
}
break;
}
}
if (loopResult == LoopResult::FAULT) {
// Configuration error
std::cerr << "Fault has occured in watchdog loop" << std::endl;
// Prevent spam
sleep = true;
}
if (sleep) {
std::this_thread::sleep_for(1000ms);
}
return true;
2021-07-29 17:21:27 +02:00
}
2023-02-23 23:56:11 +01:00
int WatchdogTask::performSuspendOperation() { return 0; }