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
28namespace 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.
44class BaseDaemon : public Poco::Util::ServerApplication, public Loggers
45{
46 friend class SignalListener;
47
48public:
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
133protected:
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
213private:
214
215 /// Check SSE and others instructions availability
216 /// Calls exit on fail
217 void checkRequiredInstructions();
218};
219
220
221template <class Daemon>
222std::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