И так у нас сейчас есть сервис он же демон, который работает потенциально бесконечно.
Но что будет, если по какой-то причине в коде произойдет непредвиденная ситуация и произойдет исключение.
Давайте попробуем смоделировать такую ситуацию.
Генерим ошибку
Я добавлю в событие генерацию ошибки. Так как OnChanged на самом деле вызывается асинхронно то я не могу просто взять и сгенерить ошибку в нем. Типа так:
private static void OnChanged(object sender, FileSystemEventArgs e)
{
throw new Exception("Падаю =ООО"); // добавил ошибку
Console.WriteLine($"Изменился: {e.FullPath}");
}
мне придется немного смухлевать и добавить поле, которое указывает что ошибка произошла, и просто проверять ее в цикле:
class Program
{
static bool errorRaised = false; // добавил поле, которое будет уходить в true при возникновении ошибки
static int Main(string[] args)
{
// ...
while(true) {
Thread.Sleep(2000);
Console.WriteLine("Мониторю");
// проверяю errorRaised и если оно true, то кидаю ошибку
if (errorRaised) {
throw new Exception("Падаю =ООО");
}
}
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
errorRaised = true; // тут меняю значение на true чтобы сгенерить ошибку
Console.WriteLine($"Изменился: {e.FullPath}");
}
}
перебилдиваем и перезапускаем
dotnet build
sudo systemctl restart my_daemon.service
запускаем журнал
journalctl -u my_daemon.service -f
и в параллельной консольке пробуем чего-нибудь вписать в файл

как видим процесс умирает с концами и перестает мониторить.
Подключаем автоперезапуск
Одной из важнейших задач супервизора является оживление упавших процессов.
В общем, чтобы наш процесс после умирания сразу восстанавливался необходимо добавить в описание сервиса (файл my_daemon.service) строчку
[Unit]
Description=Заклинатель книг
[Service]
User=m
Restart=on-failure # вот эту
WorkingDirectory=/home/m/projects/daemon/bin/Debug/net5.0
ExecStart=/home/m/projects/daemon/bin/Debug/net5.0/daemon -f config.txt
[Install]
WantedBy=multi-user.target
полный список допустимых значений такой

вообще почитать про разные параметры для сервиса можно тут
после того как подправите файлик надо обновить кэш systemd и перезапустить сервис
sudo systemctl daemon-reload
sudo systemctl restart my_daemon.service
опять запускаем параллельно лог и процесс и наблюдаем как процесс перезапустился

красота! =)
Перехватываем HUP сигнал
Команда reload
В systemd помимо уже знакомых нам команд start, restart, stop
встроена еще одна полезная команда, которая называется reload
.
Это такой облегченный вариант restart, который предполагает, что сервис не будет останавливаться, а просто обновит свою внутреннюю конфигурацию, например перечитает файл конфига.
Правда если попробовать запустить эту команду, то увидим что-то такое

ну то есть наш сервис не поддерживает reload
Чтобы он стал его поддерживать надо добавить пункт ExecReload в описание сервиса
[Unit]
Description=Заклинатель книг
[Service]
User=m
Restart=on-failure
WorkingDirectory=/home/m/projects/daemon/bin/Debug/net7.0
ExecStart=/home/m/projects/daemon/bin/Debug/net7.0/daemon -f config.txt
ExecReload=/bin/kill -HUP $MAINPID # вот тут
[Install]
WantedBy=multi-user.target
теперь если попробовать выполнить reload сервиса мы увидим, что процесс останавливается.

Дело в том, что по умолчанию HUP сигнал обрабатывается как сигнал остановки. Причем он считается корректным сигналом остановки и супервизор не пытается перезапустить процесс.
В общем, чтобы поменять такое поведение надо подключить собственный обработчик к этому сигналу.
В .net 6 как раз добавили поддержку linux сигналов и можно легко сделать собственный обработчик, если запихать в Main что-то такое
using System;
using System.IO;
// ...
using System.Runtime.InteropServices; // не забывем добавить using
namespace daemon
{
class Program
{
static int Main(string[] args)
{
// подключаем обработчик
PosixSignalRegistration.Create(PosixSignal.SIGHUP, (context) => {
context.Cancel = true; // отключаем обработчик по умолчанию
Console.WriteLine("обновляюсь без перезагрузки");
});
// ...
}
// ...
}
}
билдимся и запускаем процесс по новой
sudo systemctl start my_daemon.service

вот теперь другое дело. Можно и задание пилить =)
Задание
Реализовать обработку какой-нибудь фразы в книге в результате которой программа будет падать с ошибкой.
[Необязательно] Реализовать обновление отслеживаемого файла через команду reload. То есть вы должны иметь возможность прописать путь к новому файлу в файл с конфигом, вызвать reload и убедится, что демон действительно стал мониторить новый файл.