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 | |