2016-01-15 03:55:07 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <memory>
|
2016-12-12 03:33:34 +00:00
|
|
|
|
#include <functional>
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
#include <Poco/Process.h>
|
|
|
|
|
#include <Poco/ThreadPool.h>
|
|
|
|
|
#include <Poco/TaskNotification.h>
|
|
|
|
|
#include <Poco/NumberFormatter.h>
|
|
|
|
|
#include <Poco/Util/Application.h>
|
|
|
|
|
#include <Poco/Util/ServerApplication.h>
|
|
|
|
|
#include <Poco/Net/SocketAddress.h>
|
|
|
|
|
#include <Poco/FileChannel.h>
|
|
|
|
|
#include <Poco/SyslogChannel.h>
|
|
|
|
|
|
|
|
|
|
#include <common/Common.h>
|
|
|
|
|
#include <common/logger_useful.h>
|
|
|
|
|
|
2016-01-15 04:13:00 +00:00
|
|
|
|
#include <daemon/GraphiteWriter.h>
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
2016-12-12 03:33:34 +00:00
|
|
|
|
#include <experimental/optional>
|
2016-01-15 03:55:07 +00:00
|
|
|
|
#include <zkutil/ZooKeeperHolder.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Poco { class TaskManager; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// \brief Базовый класс для демонов
|
|
|
|
|
///
|
|
|
|
|
/// \code
|
|
|
|
|
/// # Список возможных опций командной строки обрабатываемых демоном:
|
|
|
|
|
/// # --config-file или --config - имя файла конфигурации. По умолчанию - config.xml
|
|
|
|
|
/// # --pid-file - имя PID файла. По умолчанию - pid
|
|
|
|
|
/// # --log-file - имя лог файла
|
|
|
|
|
/// # --error-file - имя лог файла, в который будут помещаться только ошибки
|
|
|
|
|
/// # --daemon - запустить в режиме демона; если не указан - логгирование будет вестись на консоль
|
|
|
|
|
/// <daemon_name> --daemon --config-file=localfile.xml --pid-file=pid.pid --log-file=log.log --errorlog-file=error.log
|
|
|
|
|
/// \endcode
|
|
|
|
|
///
|
|
|
|
|
/// Если неперехваченное исключение выкинуто в других потоках (не Task-и), то по-умолчанию
|
|
|
|
|
/// используется KillingErrorHandler, который вызывает std::terminate.
|
|
|
|
|
///
|
|
|
|
|
/// Кроме того, класс позволяет достаточно гибко управлять журналированием. В методе initialize() вызывается метод
|
|
|
|
|
/// buildLoggers() который и строит нужные логгеры. Эта функция ожидает увидеть в конфигурации определённые теги
|
|
|
|
|
/// заключённые в секции "logger".
|
|
|
|
|
/// Если нужно журналирование на консоль, нужно просто не использовать тег "log" или использовать --console.
|
|
|
|
|
/// Теги уровней вывода использовать можно в любом случае
|
|
|
|
|
|
|
|
|
|
|
2016-02-09 17:06:50 +00:00
|
|
|
|
class BaseDaemon : public Poco::Util::ServerApplication
|
2016-01-15 03:55:07 +00:00
|
|
|
|
{
|
2016-06-08 14:39:19 +00:00
|
|
|
|
friend class SignalListener;
|
|
|
|
|
|
2016-01-15 03:55:07 +00:00
|
|
|
|
public:
|
2016-02-09 17:06:50 +00:00
|
|
|
|
BaseDaemon();
|
|
|
|
|
~BaseDaemon();
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
/// Загружает конфигурацию и "строит" логгеры на запись в файлы
|
2016-08-07 16:25:31 +00:00
|
|
|
|
void initialize(Poco::Util::Application &) override;
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
/// Читает конфигурацию
|
|
|
|
|
void reloadConfiguration();
|
|
|
|
|
|
|
|
|
|
/// Строит необходимые логгеры
|
|
|
|
|
void buildLoggers();
|
|
|
|
|
|
|
|
|
|
/// Определяет параметр командной строки
|
2016-08-07 16:25:31 +00:00
|
|
|
|
void defineOptions(Poco::Util::OptionSet& _options) override;
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
/// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно
|
|
|
|
|
void exitOnTaskError();
|
|
|
|
|
|
|
|
|
|
/// Завершение демона ("мягкое")
|
|
|
|
|
void terminate();
|
|
|
|
|
|
|
|
|
|
/// Завершение демона ("жёсткое")
|
|
|
|
|
void kill();
|
|
|
|
|
|
|
|
|
|
/// Получен ли сигнал на завершение?
|
|
|
|
|
bool isCancelled()
|
|
|
|
|
{
|
|
|
|
|
return is_cancelled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Получение ссылки на экземпляр демона
|
2016-02-09 17:06:50 +00:00
|
|
|
|
static BaseDaemon & instance()
|
2016-01-15 03:55:07 +00:00
|
|
|
|
{
|
2016-02-09 17:06:50 +00:00
|
|
|
|
return dynamic_cast<BaseDaemon &>(Poco::Util::Application::instance());
|
2016-01-15 03:55:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 13:30:27 +00:00
|
|
|
|
/// return none if daemon doesn't exist, reference to the daemon otherwise
|
2016-12-12 03:33:34 +00:00
|
|
|
|
static std::experimental::optional<std::reference_wrapper<BaseDaemon>> tryGetInstance() { return tryGetInstance<BaseDaemon>(); }
|
2016-09-05 16:29:21 +00:00
|
|
|
|
|
2016-01-15 03:55:07 +00:00
|
|
|
|
/// Спит заданное количество секунд или до события wakeup
|
|
|
|
|
void sleep(double seconds);
|
|
|
|
|
|
|
|
|
|
/// Разбудить
|
|
|
|
|
void wakeup();
|
|
|
|
|
|
|
|
|
|
/// Закрыть файлы с логами. При следующей записи, будут созданы новые файлы.
|
|
|
|
|
void closeLogs();
|
|
|
|
|
|
|
|
|
|
/// В Graphite компоненты пути(папки) разделяются точкой.
|
|
|
|
|
/// У нас принят путь формата root_path.hostname_yandex_ru.key
|
|
|
|
|
/// root_path по умолчанию one_min
|
|
|
|
|
/// key - лучше группировать по смыслу. Например "meminfo.cached" или "meminfo.free", "meminfo.total"
|
|
|
|
|
template <class T>
|
|
|
|
|
void writeToGraphite(const std::string & key, const T & value, time_t timestamp = 0, const std::string & custom_root_path = "")
|
|
|
|
|
{
|
|
|
|
|
graphite_writer->write(key, value, timestamp, custom_root_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
void writeToGraphite(const GraphiteWriter::KeyValueVector<T> & key_vals, time_t timestamp = 0, const std::string & custom_root_path = "")
|
|
|
|
|
{
|
|
|
|
|
graphite_writer->write(key_vals, timestamp, custom_root_path);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-19 12:41:14 +00:00
|
|
|
|
GraphiteWriter * getGraphiteWriter() { return graphite_writer.get(); }
|
|
|
|
|
|
2016-12-12 03:33:34 +00:00
|
|
|
|
std::experimental::optional<size_t> getLayer() const
|
2016-01-15 03:55:07 +00:00
|
|
|
|
{
|
|
|
|
|
return layer; /// layer выставляется в классе-наследнике BaseDaemonApplication.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
2016-02-19 11:07:11 +00:00
|
|
|
|
/// Возвращает TaskManager приложения
|
|
|
|
|
/// все методы task_manager следует вызывать из одного потока
|
|
|
|
|
/// иначе возможен deadlock, т.к. joinAll выполняется под локом, а любой метод тоже берет лок
|
|
|
|
|
Poco::TaskManager & getTaskManager() { return *task_manager; }
|
|
|
|
|
|
2016-02-10 07:54:22 +00:00
|
|
|
|
virtual void logRevision() const;
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
/// Используется при exitOnTaskError()
|
|
|
|
|
void handleNotification(Poco::TaskFailedNotification *);
|
|
|
|
|
|
2016-06-08 14:39:19 +00:00
|
|
|
|
/// thread safe
|
|
|
|
|
virtual void handleSignal(int signal_id);
|
|
|
|
|
|
|
|
|
|
/// реализация обработки сигналов завершения через pipe не требует блокировки сигнала с помощью sigprocmask во всех потоках
|
|
|
|
|
void waitForTerminationRequest() override;
|
|
|
|
|
/// thread safe
|
|
|
|
|
virtual void onInterruptSignals(int signal_id);
|
|
|
|
|
|
2016-09-05 16:29:21 +00:00
|
|
|
|
template <class Daemon>
|
2016-12-12 03:33:34 +00:00
|
|
|
|
static std::experimental::optional<std::reference_wrapper<Daemon>> tryGetInstance();
|
2016-09-05 16:29:21 +00:00
|
|
|
|
|
2016-01-15 03:55:07 +00:00
|
|
|
|
std::unique_ptr<Poco::TaskManager> task_manager;
|
|
|
|
|
|
|
|
|
|
/// Создание и автоматическое удаление pid файла.
|
|
|
|
|
struct PID
|
|
|
|
|
{
|
|
|
|
|
std::string file;
|
|
|
|
|
|
|
|
|
|
/// Создать объект, не создавая PID файл
|
|
|
|
|
PID() {}
|
|
|
|
|
|
|
|
|
|
/// Создать объект, создать PID файл
|
|
|
|
|
PID(const std::string & file_) { seed(file_); }
|
|
|
|
|
|
|
|
|
|
/// Создать PID файл
|
|
|
|
|
void seed(const std::string & file_);
|
|
|
|
|
|
|
|
|
|
/// Удалить PID файл
|
|
|
|
|
void clear();
|
|
|
|
|
|
|
|
|
|
~PID() { clear(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PID pid;
|
|
|
|
|
|
2016-06-08 14:39:19 +00:00
|
|
|
|
std::atomic_bool is_cancelled{false};
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
2016-05-12 19:40:23 +00:00
|
|
|
|
/// Флаг устанавливается по сообщению из Task (при аварийном завершении).
|
|
|
|
|
bool task_failed = false;
|
|
|
|
|
|
2016-01-15 03:55:07 +00:00
|
|
|
|
bool log_to_console = false;
|
|
|
|
|
|
|
|
|
|
/// Событие, чтобы проснуться во время ожидания
|
|
|
|
|
Poco::Event wakeup_event;
|
|
|
|
|
|
|
|
|
|
/// Поток, в котором принимается сигнал HUP/USR1 для закрытия логов.
|
2016-03-04 02:25:04 +00:00
|
|
|
|
Poco::Thread signal_listener_thread;
|
|
|
|
|
std::unique_ptr<Poco::Runnable> signal_listener;
|
2016-01-15 03:55:07 +00:00
|
|
|
|
|
|
|
|
|
/// Файлы с логами.
|
|
|
|
|
Poco::AutoPtr<Poco::FileChannel> log_file;
|
|
|
|
|
Poco::AutoPtr<Poco::FileChannel> error_log_file;
|
|
|
|
|
Poco::AutoPtr<Poco::SyslogChannel> syslog_channel;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GraphiteWriter> graphite_writer;
|
|
|
|
|
|
2016-12-12 03:33:34 +00:00
|
|
|
|
std::experimental::optional<size_t> layer;
|
2016-06-08 14:39:19 +00:00
|
|
|
|
|
|
|
|
|
std::mutex signal_handler_mutex;
|
|
|
|
|
std::condition_variable signal_event;
|
2016-06-10 12:01:03 +00:00
|
|
|
|
std::atomic_size_t terminate_signals_counter{0};
|
|
|
|
|
std::atomic_size_t sigint_signals_counter{0};
|
2016-01-15 03:55:07 +00:00
|
|
|
|
};
|
2016-09-05 16:29:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <class Daemon>
|
2016-12-12 03:33:34 +00:00
|
|
|
|
std::experimental::optional<std::reference_wrapper<Daemon>> BaseDaemon::tryGetInstance()
|
2016-09-05 16:29:21 +00:00
|
|
|
|
{
|
|
|
|
|
Daemon * ptr = nullptr;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
ptr = dynamic_cast<Daemon *>(&Poco::Util::Application::instance());
|
|
|
|
|
}
|
|
|
|
|
catch (const Poco::NullPointerException &)
|
|
|
|
|
{
|
2016-09-21 13:30:27 +00:00
|
|
|
|
/// if daemon doesn't exist than instance() throw NullPointerException
|
2016-09-05 16:29:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ptr)
|
2016-12-12 03:33:34 +00:00
|
|
|
|
return std::experimental::optional<std::reference_wrapper<Daemon>>(*ptr);
|
2016-09-05 16:29:21 +00:00
|
|
|
|
else
|
2016-12-12 03:33:34 +00:00
|
|
|
|
return {};
|
2016-09-05 16:29:21 +00:00
|
|
|
|
}
|