| 1 | #pragma once |
| 2 | |
| 3 | #include <sys/types.h> |
| 4 | #include <port/unistd.h> |
| 5 | #include <iostream> |
| 6 | #include <memory> |
| 7 | #include <functional> |
| 8 | #include <optional> |
| 9 | #include <mutex> |
| 10 | #include <condition_variable> |
| 11 | #include <atomic> |
| 12 | #include <chrono> |
| 13 | #include <Poco/Process.h> |
| 14 | #include <Poco/ThreadPool.h> |
| 15 | #include <Poco/TaskNotification.h> |
| 16 | #include <Poco/Util/Application.h> |
| 17 | #include <Poco/Util/ServerApplication.h> |
| 18 | #include <Poco/Net/SocketAddress.h> |
| 19 | #include <Poco/Version.h> |
| 20 | #include <common/Types.h> |
| 21 | #include <common/logger_useful.h> |
| 22 | #include <common/getThreadNumber.h> |
| 23 | #include <daemon/GraphiteWriter.h> |
| 24 | #include <Common/Config/ConfigProcessor.h> |
| 25 | #include <loggers/Loggers.h> |
| 26 | |
| 27 | |
| 28 | namespace Poco { class TaskManager; } |
| 29 | |
| 30 | |
| 31 | /// \brief Base class for applications that can run as deamons. |
| 32 | /// |
| 33 | /// \code |
| 34 | /// # Some possible command line options: |
| 35 | /// # --config-file, -C or --config - path to configuration file. By default - config.xml in the current directory. |
| 36 | /// # --log-file |
| 37 | /// # --errorlog-file |
| 38 | /// # --daemon - run as daemon; without this option, the program will be attached to the terminal and also output logs to stderr. |
| 39 | /// <daemon_name> --daemon --config-file=localfile.xml --log-file=log.log --errorlog-file=error.log |
| 40 | /// \endcode |
| 41 | /// |
| 42 | /// You can configure different log options for different loggers used inside program |
| 43 | /// by providing subsections to "logger" in configuration file. |
| 44 | class BaseDaemon : public Poco::Util::ServerApplication, public Loggers |
| 45 | { |
| 46 | friend class SignalListener; |
| 47 | |
| 48 | public: |
| 49 | static inline constexpr char DEFAULT_GRAPHITE_CONFIG_NAME[] = "graphite" ; |
| 50 | |
| 51 | BaseDaemon(); |
| 52 | ~BaseDaemon() override; |
| 53 | |
| 54 | /// Загружает конфигурацию и "строит" логгеры на запись в файлы |
| 55 | void initialize(Poco::Util::Application &) override; |
| 56 | |
| 57 | /// Читает конфигурацию |
| 58 | void reloadConfiguration(); |
| 59 | |
| 60 | /// Определяет параметр командной строки |
| 61 | void defineOptions(Poco::Util::OptionSet & _options) override; |
| 62 | |
| 63 | /// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно |
| 64 | void exitOnTaskError(); |
| 65 | |
| 66 | /// Завершение демона ("мягкое") |
| 67 | void terminate(); |
| 68 | |
| 69 | /// Завершение демона ("жёсткое") |
| 70 | void kill(); |
| 71 | |
| 72 | /// Получен ли сигнал на завершение? |
| 73 | bool isCancelled() const |
| 74 | { |
| 75 | return is_cancelled; |
| 76 | } |
| 77 | |
| 78 | /// Получение ссылки на экземпляр демона |
| 79 | static BaseDaemon & instance() |
| 80 | { |
| 81 | return dynamic_cast<BaseDaemon &>(Poco::Util::Application::instance()); |
| 82 | } |
| 83 | |
| 84 | /// return none if daemon doesn't exist, reference to the daemon otherwise |
| 85 | static std::optional<std::reference_wrapper<BaseDaemon>> tryGetInstance() { return tryGetInstance<BaseDaemon>(); } |
| 86 | |
| 87 | /// Спит заданное количество секунд или до события wakeup |
| 88 | void sleep(double seconds); |
| 89 | |
| 90 | /// Разбудить |
| 91 | void wakeup(); |
| 92 | |
| 93 | /// В Graphite компоненты пути(папки) разделяются точкой. |
| 94 | /// У нас принят путь формата root_path.hostname_yandex_ru.key |
| 95 | /// root_path по умолчанию one_min |
| 96 | /// key - лучше группировать по смыслу. Например "meminfo.cached" или "meminfo.free", "meminfo.total" |
| 97 | template <class T> |
| 98 | void writeToGraphite(const std::string & key, const T & value, const std::string & config_name = DEFAULT_GRAPHITE_CONFIG_NAME, time_t timestamp = 0, const std::string & custom_root_path = "" ) |
| 99 | { |
| 100 | auto writer = getGraphiteWriter(config_name); |
| 101 | if (writer) |
| 102 | writer->write(key, value, timestamp, custom_root_path); |
| 103 | } |
| 104 | |
| 105 | template <class T> |
| 106 | void writeToGraphite(const GraphiteWriter::KeyValueVector<T> & key_vals, const std::string & config_name = DEFAULT_GRAPHITE_CONFIG_NAME, time_t timestamp = 0, const std::string & custom_root_path = "" ) |
| 107 | { |
| 108 | auto writer = getGraphiteWriter(config_name); |
| 109 | if (writer) |
| 110 | writer->write(key_vals, timestamp, custom_root_path); |
| 111 | } |
| 112 | |
| 113 | template <class T> |
| 114 | void writeToGraphite(const GraphiteWriter::KeyValueVector<T> & key_vals, const std::chrono::system_clock::time_point & current_time, const std::string & custom_root_path) |
| 115 | { |
| 116 | auto writer = getGraphiteWriter(); |
| 117 | if (writer) |
| 118 | writer->write(key_vals, std::chrono::system_clock::to_time_t(current_time), custom_root_path); |
| 119 | } |
| 120 | |
| 121 | GraphiteWriter * getGraphiteWriter(const std::string & config_name = DEFAULT_GRAPHITE_CONFIG_NAME) |
| 122 | { |
| 123 | if (graphite_writers.count(config_name)) |
| 124 | return graphite_writers[config_name].get(); |
| 125 | return nullptr; |
| 126 | } |
| 127 | |
| 128 | /// close all process FDs except |
| 129 | /// 0-2 -- stdin, stdout, stderr |
| 130 | /// also doesn't close global internal pipes for signal handling |
| 131 | void closeFDs(); |
| 132 | |
| 133 | protected: |
| 134 | /// Возвращает TaskManager приложения |
| 135 | /// все методы task_manager следует вызывать из одного потока |
| 136 | /// иначе возможен deadlock, т.к. joinAll выполняется под локом, а любой метод тоже берет лок |
| 137 | Poco::TaskManager & getTaskManager() { return *task_manager; } |
| 138 | |
| 139 | virtual void logRevision() const; |
| 140 | |
| 141 | /// Используется при exitOnTaskError() |
| 142 | void handleNotification(Poco::TaskFailedNotification *); |
| 143 | |
| 144 | /// thread safe |
| 145 | virtual void handleSignal(int signal_id); |
| 146 | |
| 147 | /// initialize termination process and signal handlers |
| 148 | virtual void initializeTerminationAndSignalProcessing(); |
| 149 | |
| 150 | /// реализация обработки сигналов завершения через pipe не требует блокировки сигнала с помощью sigprocmask во всех потоках |
| 151 | void waitForTerminationRequest() |
| 152 | #if defined(POCO_CLICKHOUSE_PATCH) || POCO_VERSION >= 0x02000000 // in old upstream poco not vitrual |
| 153 | override |
| 154 | #endif |
| 155 | ; |
| 156 | /// thread safe |
| 157 | virtual void onInterruptSignals(int signal_id); |
| 158 | |
| 159 | template <class Daemon> |
| 160 | static std::optional<std::reference_wrapper<Daemon>> tryGetInstance(); |
| 161 | |
| 162 | virtual std::string getDefaultCorePath() const; |
| 163 | |
| 164 | std::unique_ptr<Poco::TaskManager> task_manager; |
| 165 | |
| 166 | /// RAII wrapper for pid file. |
| 167 | struct PID |
| 168 | { |
| 169 | std::string file; |
| 170 | |
| 171 | /// Создать объект, не создавая PID файл |
| 172 | PID() {} |
| 173 | |
| 174 | /// Создать объект, создать PID файл |
| 175 | PID(const std::string & file_) { seed(file_); } |
| 176 | |
| 177 | /// Создать PID файл |
| 178 | void seed(const std::string & file_); |
| 179 | |
| 180 | /// Удалить PID файл |
| 181 | void clear(); |
| 182 | |
| 183 | ~PID() { clear(); } |
| 184 | }; |
| 185 | |
| 186 | PID pid; |
| 187 | |
| 188 | std::atomic_bool is_cancelled{false}; |
| 189 | |
| 190 | /// Флаг устанавливается по сообщению из Task (при аварийном завершении). |
| 191 | bool task_failed = false; |
| 192 | |
| 193 | bool log_to_console = false; |
| 194 | |
| 195 | /// Событие, чтобы проснуться во время ожидания |
| 196 | Poco::Event wakeup_event; |
| 197 | |
| 198 | /// Поток, в котором принимается сигнал HUP/USR1 для закрытия логов. |
| 199 | Poco::Thread signal_listener_thread; |
| 200 | std::unique_ptr<Poco::Runnable> signal_listener; |
| 201 | |
| 202 | std::map<std::string, std::unique_ptr<GraphiteWriter>> graphite_writers; |
| 203 | |
| 204 | std::mutex signal_handler_mutex; |
| 205 | std::condition_variable signal_event; |
| 206 | std::atomic_size_t terminate_signals_counter{0}; |
| 207 | std::atomic_size_t sigint_signals_counter{0}; |
| 208 | |
| 209 | std::string config_path; |
| 210 | DB::ConfigProcessor::LoadedConfig loaded_config; |
| 211 | Poco::Util::AbstractConfiguration * last_configuration = nullptr; |
| 212 | |
| 213 | private: |
| 214 | |
| 215 | /// Check SSE and others instructions availability |
| 216 | /// Calls exit on fail |
| 217 | void checkRequiredInstructions(); |
| 218 | }; |
| 219 | |
| 220 | |
| 221 | template <class Daemon> |
| 222 | std::optional<std::reference_wrapper<Daemon>> BaseDaemon::tryGetInstance() |
| 223 | { |
| 224 | Daemon * ptr = nullptr; |
| 225 | try |
| 226 | { |
| 227 | ptr = dynamic_cast<Daemon *>(&Poco::Util::Application::instance()); |
| 228 | } |
| 229 | catch (const Poco::NullPointerException &) |
| 230 | { |
| 231 | /// if daemon doesn't exist than instance() throw NullPointerException |
| 232 | } |
| 233 | |
| 234 | if (ptr) |
| 235 | return std::optional<std::reference_wrapper<Daemon>>(*ptr); |
| 236 | else |
| 237 | return {}; |
| 238 | } |
| 239 | |