be/src/service/doris_main.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include <arrow/flight/client.h> |
19 | | #include <arrow/flight/sql/client.h> |
20 | | #include <arrow/scalar.h> |
21 | | #include <arrow/status.h> |
22 | | #include <arrow/table.h> |
23 | | #include <butil/macros.h> |
24 | | // IWYU pragma: no_include <bthread/errno.h> |
25 | | #include <errno.h> // IWYU pragma: keep |
26 | | #include <fcntl.h> |
27 | | #include <fmt/core.h> |
28 | | #if !defined(__SANITIZE_ADDRESS__) && !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \ |
29 | | !defined(THREAD_SANITIZER) && !defined(USE_JEMALLOC) |
30 | | #include <gperftools/malloc_extension.h> // IWYU pragma: keep |
31 | | #endif |
32 | | #include <libgen.h> |
33 | | #include <setjmp.h> |
34 | | #include <signal.h> |
35 | | #include <stdint.h> |
36 | | #include <stdio.h> |
37 | | #include <stdlib.h> |
38 | | #include <unistd.h> |
39 | | |
40 | | #include <cstring> |
41 | | #include <functional> |
42 | | #include <memory> |
43 | | #include <ostream> |
44 | | #include <string> |
45 | | #include <string_view> |
46 | | #include <thread> |
47 | | #include <tuple> |
48 | | #include <vector> |
49 | | |
50 | | #include "cloud/cloud_backend_service.h" |
51 | | #include "cloud/config.h" |
52 | | #include "common/phdr_cache.h" |
53 | | #include "common/stack_trace.h" |
54 | | #if defined(__ELF__) && !defined(__FreeBSD__) |
55 | | #include "common/symbol_index.h" |
56 | | #endif |
57 | | #include "runtime/memory/mem_tracker_limiter.h" |
58 | | #include "storage/tablet/tablet_schema_cache.h" |
59 | | #include "storage/utils.h" |
60 | | #include "util/concurrency_stats.h" |
61 | | |
62 | | #if defined(LEAK_SANITIZER) |
63 | | #include <sanitizer/lsan_interface.h> |
64 | | #endif |
65 | | |
66 | | #include <curl/curl.h> |
67 | | #include <thrift/TOutput.h> |
68 | | |
69 | | #include "agent/heartbeat_server.h" |
70 | | #include "common/config.h" |
71 | | #include "common/daemon.h" |
72 | | #include "common/logging.h" |
73 | | #include "common/signal_handler.h" |
74 | | #include "common/status.h" |
75 | | #include "io/cache/block_file_cache_factory.h" |
76 | | #include "load/stream_load/stream_load_recorder_manager.h" |
77 | | #include "runtime/exec_env.h" |
78 | | #include "runtime/user_function_cache.h" |
79 | | #include "runtime/workload_management/resource_context.h" |
80 | | #include "service/arrow_flight/flight_sql_service.h" |
81 | | #include "service/backend_options.h" |
82 | | #include "service/backend_service.h" |
83 | | #include "service/http_service.h" |
84 | | #include "service/server/be_server_starter_factory.h" |
85 | | #include "storage/options.h" |
86 | | #include "storage/storage_engine.h" |
87 | | #include "udf/python/python_env.h" |
88 | | #include "util/debug_util.h" |
89 | | #include "util/disk_info.h" |
90 | | #include "util/jni_plugin_registry.h" |
91 | | #include "util/mem_info.h" |
92 | | #include "util/string_util.h" |
93 | | #include "util/thread.h" |
94 | | #include "util/thrift_rpc_helper.h" |
95 | | #include "util/thrift_server.h" |
96 | | #include "util/uid_util.h" |
97 | | |
98 | | namespace doris {} // namespace doris |
99 | | |
100 | | static void help(const char*); |
101 | | |
102 | | extern "C" { |
103 | | void __lsan_do_leak_check(); |
104 | | int __llvm_profile_write_file(); |
105 | | } |
106 | | |
107 | | namespace doris { |
108 | | |
109 | 7 | void signal_handler(int signal) { |
110 | 7 | if (signal == SIGINT || signal == SIGTERM) { |
111 | 7 | k_doris_exit = true; |
112 | 7 | } |
113 | | // SIGQUIT deliberately does nothing here; see init_signals(). |
114 | 7 | } |
115 | | |
116 | 21 | int install_signal(int signo, void (*handler)(int)) { |
117 | 21 | struct sigaction sa; |
118 | 21 | memset(&sa, 0, sizeof(struct sigaction)); |
119 | 21 | sa.sa_handler = handler; |
120 | | // Restartable syscalls stay restartable. It matters most for SIGQUIT: unlike the two |
121 | | // shutdown signals, that one is sent to a HEALTHY BE - `kill -3` is the operator's habit |
122 | | // for asking a running process for a thread dump, and since the handler now produces |
123 | | // nothing they send it again. Without SA_RESTART each of those turns whatever syscall the |
124 | | // receiving thread happened to be in into EINTR, in the middle of normal serving. |
125 | 21 | sa.sa_flags = SA_RESTART; |
126 | 21 | sigemptyset(&sa.sa_mask); |
127 | 21 | auto ret = sigaction(signo, &sa, nullptr); |
128 | 21 | if (ret != 0) { |
129 | 0 | char buf[64]; |
130 | 0 | LOG(ERROR) << "install signal failed, signo=" << signo << ", errno=" << errno |
131 | 0 | << ", errmsg=" << strerror_r(errno, buf, sizeof(buf)); |
132 | 0 | } |
133 | 21 | return ret; |
134 | 21 | } |
135 | | |
136 | 7 | void init_signals() { |
137 | 7 | auto ret = install_signal(SIGINT, signal_handler); |
138 | 7 | if (ret < 0) { |
139 | 0 | exit(-1); |
140 | 0 | } |
141 | 7 | ret = install_signal(SIGTERM, signal_handler); |
142 | 7 | if (ret < 0) { |
143 | 0 | exit(-1); |
144 | 0 | } |
145 | | // SIGQUIT is taken over even though the BE does nothing with it, because its default |
146 | | // action is not "nothing": it terminates the process and dumps core. `kill -3 <pid>` is |
147 | | // what an operator reaches for to get a thread dump out of a process that looks stuck, |
148 | | // and until the JVM started running with -Xrs it got one - the JVM installed a handler |
149 | | // for this signal along with the shutdown ones. -Xrs stops it from doing that (see |
150 | | // JvmLauncher::_build_options), which would leave SIGQUIT at SIG_DFL and turn that |
151 | | // habitual command into a crash. Handling it and ignoring it is the pre-change |
152 | | // behaviour minus the thread dump; jcmd and jstack, which attach rather than signal, |
153 | | // are how to get one now. |
154 | | // |
155 | | // Installed with a handler rather than SIG_IGN so that the disposition is inheritable |
156 | | // by nothing and visible to JvmLauncher's BeOwnedSignalGuard, which saves and restores |
157 | | // it around any VM creation it does not control. |
158 | 7 | ret = install_signal(SIGQUIT, signal_handler); |
159 | 7 | if (ret < 0) { |
160 | 0 | exit(-1); |
161 | 0 | } |
162 | 7 | } |
163 | | |
164 | 8 | static void thrift_output(const char* x) { |
165 | 8 | LOG(WARNING) << "thrift internal message: " << x; |
166 | 8 | } |
167 | | |
168 | | } // namespace doris |
169 | | |
170 | | // These code is referenced from clickhouse |
171 | | // It is used to check the SIMD instructions |
172 | | enum class InstructionFail { |
173 | | NONE = 0, |
174 | | SSE3 = 1, |
175 | | SSSE3 = 2, |
176 | | SSE4_1 = 3, |
177 | | SSE4_2 = 4, |
178 | | POPCNT = 5, |
179 | | AVX = 6, |
180 | | AVX2 = 7, |
181 | | AVX512 = 8, |
182 | | ARM_NEON = 9 |
183 | | }; |
184 | | |
185 | 0 | auto instruction_fail_to_string(InstructionFail fail) { |
186 | 0 | switch (fail) { |
187 | 0 | #define ret(x) return std::make_tuple(STDERR_FILENO, x, ARRAY_SIZE(x) - 1) |
188 | 0 | case InstructionFail::NONE: |
189 | 0 | ret("NONE"); |
190 | 0 | case InstructionFail::SSE3: |
191 | 0 | ret("SSE3"); |
192 | 0 | case InstructionFail::SSSE3: |
193 | 0 | ret("SSSE3"); |
194 | 0 | case InstructionFail::SSE4_1: |
195 | 0 | ret("SSE4.1"); |
196 | 0 | case InstructionFail::SSE4_2: |
197 | 0 | ret("SSE4.2"); |
198 | 0 | case InstructionFail::POPCNT: |
199 | 0 | ret("POPCNT"); |
200 | 0 | case InstructionFail::AVX: |
201 | 0 | ret("AVX"); |
202 | 0 | case InstructionFail::AVX2: |
203 | 0 | ret("AVX2"); |
204 | 0 | case InstructionFail::AVX512: |
205 | 0 | ret("AVX512"); |
206 | 0 | case InstructionFail::ARM_NEON: |
207 | 0 | ret("ARM_NEON"); |
208 | 0 | } |
209 | | |
210 | 0 | LOG(ERROR) << "Unrecognized instruction fail value." << std::endl; |
211 | 0 | exit(-1); |
212 | 0 | } |
213 | | |
214 | | sigjmp_buf jmpbuf; |
215 | | |
216 | 0 | void sig_ill_check_handler(int, siginfo_t*, void*) { |
217 | 0 | siglongjmp(jmpbuf, 1); |
218 | 0 | } |
219 | | |
220 | | /// Check if necessary SSE extensions are available by trying to execute some sse instructions. |
221 | | /// If instruction is unavailable, SIGILL will be sent by kernel. |
222 | 8 | void check_required_instructions_impl(volatile InstructionFail& fail) { |
223 | 8 | #if defined(__SSE3__) |
224 | 8 | fail = InstructionFail::SSE3; |
225 | 8 | __asm__ volatile("addsubpd %%xmm0, %%xmm0" : : : "xmm0"); |
226 | 8 | #endif |
227 | | |
228 | 8 | #if defined(__SSSE3__) |
229 | 8 | fail = InstructionFail::SSSE3; |
230 | 8 | __asm__ volatile("pabsw %%xmm0, %%xmm0" : : : "xmm0"); |
231 | | |
232 | 8 | #endif |
233 | | |
234 | 8 | #if defined(__SSE4_1__) |
235 | 8 | fail = InstructionFail::SSE4_1; |
236 | 8 | __asm__ volatile("pmaxud %%xmm0, %%xmm0" : : : "xmm0"); |
237 | 8 | #endif |
238 | | |
239 | 8 | #if defined(__SSE4_2__) |
240 | 8 | fail = InstructionFail::SSE4_2; |
241 | 8 | __asm__ volatile("pcmpgtq %%xmm0, %%xmm0" : : : "xmm0"); |
242 | 8 | #endif |
243 | | |
244 | | /// Defined by -msse4.2 |
245 | 8 | #if defined(__POPCNT__) |
246 | 8 | fail = InstructionFail::POPCNT; |
247 | 8 | { |
248 | 8 | uint64_t a = 0; |
249 | 8 | uint64_t b = 0; |
250 | 8 | __asm__ volatile("popcnt %1, %0" : "=r"(a) : "r"(b) :); |
251 | 8 | } |
252 | 8 | #endif |
253 | | |
254 | 8 | #if defined(__AVX__) |
255 | 8 | fail = InstructionFail::AVX; |
256 | 8 | __asm__ volatile("vaddpd %%ymm0, %%ymm0, %%ymm0" : : : "ymm0"); |
257 | 8 | #endif |
258 | | |
259 | 8 | #if defined(__AVX2__) |
260 | 8 | fail = InstructionFail::AVX2; |
261 | 8 | __asm__ volatile("vpabsw %%ymm0, %%ymm0" : : : "ymm0"); |
262 | 8 | #endif |
263 | | |
264 | | #if defined(__AVX512__) |
265 | | fail = InstructionFail::AVX512; |
266 | | __asm__ volatile("vpabsw %%zmm0, %%zmm0" : : : "zmm0"); |
267 | | #endif |
268 | | |
269 | | #if defined(__ARM_NEON__) |
270 | | fail = InstructionFail::ARM_NEON; |
271 | | #ifndef __APPLE__ |
272 | | __asm__ volatile("vadd.i32 q8, q8, q8" : : : "q8"); |
273 | | #endif |
274 | | #endif |
275 | | |
276 | 8 | fail = InstructionFail::NONE; |
277 | 8 | } |
278 | | |
279 | 0 | bool write_retry(int fd, const char* data, size_t size) { |
280 | 0 | if (!size) size = strlen(data); |
281 | |
|
282 | 0 | while (size != 0) { |
283 | 0 | ssize_t res = ::write(fd, data, size); |
284 | |
|
285 | 0 | if ((-1 == res || 0 == res) && errno != EINTR) return false; |
286 | | |
287 | 0 | if (res > 0) { |
288 | 0 | data += res; |
289 | 0 | size -= res; |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | 0 | return true; |
294 | 0 | } |
295 | | |
296 | | /// Macros to avoid using strlen(), since it may fail if SSE is not supported. |
297 | | #define WRITE_ERROR(data) \ |
298 | 0 | do { \ |
299 | 0 | static_assert(__builtin_constant_p(data)); \ |
300 | 0 | if (!write_retry(STDERR_FILENO, data, ARRAY_SIZE(data) - 1)) _Exit(1); \ |
301 | 0 | } while (false) |
302 | | |
303 | | /// Check SSE and others instructions availability. Calls exit on fail. |
304 | | /// This function must be called as early as possible, even before main, because static initializers may use unavailable instructions. |
305 | 8 | void check_required_instructions() { |
306 | 8 | struct sigaction sa {}; |
307 | 8 | struct sigaction sa_old {}; |
308 | 8 | sa.sa_sigaction = sig_ill_check_handler; |
309 | 8 | sa.sa_flags = SA_SIGINFO; |
310 | 8 | auto signal = SIGILL; |
311 | 8 | if (sigemptyset(&sa.sa_mask) != 0 || sigaddset(&sa.sa_mask, signal) != 0 || |
312 | 8 | sigaction(signal, &sa, &sa_old) != 0) { |
313 | | /// You may wonder about strlen. |
314 | | /// Typical implementation of strlen is using SSE4.2 or AVX2. |
315 | | /// But this is not the case because it's compiler builtin and is executed at compile time. |
316 | |
|
317 | 0 | WRITE_ERROR("Can not set signal handler\n"); |
318 | 0 | _Exit(1); |
319 | 0 | } |
320 | | |
321 | 8 | volatile InstructionFail fail = InstructionFail::NONE; |
322 | | |
323 | 8 | if (sigsetjmp(jmpbuf, 1)) { |
324 | 0 | WRITE_ERROR("Instruction check fail. The CPU does not support "); |
325 | 0 | if (!std::apply(write_retry, instruction_fail_to_string(fail))) _Exit(1); |
326 | 0 | WRITE_ERROR(" instruction set.\n"); |
327 | 0 | WRITE_ERROR( |
328 | 0 | "For example, if your CPU does not support AVX2, you need to rebuild the Doris BE " |
329 | 0 | "with: USE_AVX2=0 sh build.sh --be"); |
330 | 0 | _Exit(1); |
331 | 0 | } |
332 | | |
333 | 8 | check_required_instructions_impl(fail); |
334 | | |
335 | 8 | if (sigaction(signal, &sa_old, nullptr)) { |
336 | 0 | WRITE_ERROR("Can not set signal handler\n"); |
337 | 0 | _Exit(1); |
338 | 0 | } |
339 | 8 | } |
340 | | |
341 | | struct Checker { |
342 | 8 | Checker() { check_required_instructions(); } |
343 | | } checker |
344 | | #ifndef __APPLE__ |
345 | | __attribute__((init_priority(101))) /// Run before other static initializers. |
346 | | #endif |
347 | | ; |
348 | | |
349 | | // A startup failure that happens after ExecEnv::init() has run must terminate the |
350 | | // process the same way normal shutdown does (see the _exit(0) at the end of main): |
351 | | // in the default mode we _exit() immediately, skipping global destructors and the |
352 | | // LeakSanitizer atexit check. Init-time singletons (e.g. the internal workload |
353 | | // group's task scheduler) intentionally live for the whole process lifetime, so |
354 | | // running the leak check on this abnormal-exit path reports them as false-positive |
355 | | // leaks. enable_graceful_exit_check is honored so memleak-check mode still runs LSAN. |
356 | 0 | [[noreturn]] static void exit_on_startup_failure() { |
357 | 0 | if (!doris::config::enable_graceful_exit_check) { |
358 | 0 | google::FlushLogFiles(google::GLOG_INFO); |
359 | 0 | _exit(1); |
360 | 0 | } |
361 | | |
362 | | // exit() starts destroying function-local statics while background threads are still |
363 | | // running. Tear down ExecEnv first so StorageEngine workers are stopped and joined before |
364 | | // they can race with those destructors. |
365 | 0 | if (doris::ExecEnv::ready()) { |
366 | 0 | doris::ExecEnv::GetInstance()->destroy(); |
367 | 0 | } |
368 | 0 | google::FlushLogFiles(google::GLOG_INFO); |
369 | 0 | exit(1); |
370 | 0 | } |
371 | | |
372 | 8 | int main(int argc, char** argv) { |
373 | 8 | doris::signal::InstallFailureSignalHandler(); |
374 | | // create StackTraceCache Instance, at the beginning, other static destructors may use. |
375 | 8 | StackTrace::createCache(); |
376 | | // extern doris::ErrorCode::ErrorCodeInitializer error_code_init; |
377 | | // Some developers will modify status.h and we use a very ticky logic to init error_states |
378 | | // and it maybe not inited. So add a check here. |
379 | 8 | doris::ErrorCode::error_code_init.check_init(); |
380 | | // check if print version or help |
381 | 8 | if (argc > 1) { |
382 | 1 | if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0) { |
383 | 1 | puts(doris::get_build_version(false).c_str()); |
384 | 1 | exit(0); |
385 | 1 | } else if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { |
386 | 0 | help(basename(argv[0])); |
387 | 0 | exit(0); |
388 | 0 | } |
389 | 1 | } |
390 | | |
391 | 7 | if (getenv("DORIS_HOME") == nullptr) { |
392 | 0 | fprintf(stderr, "you need set DORIS_HOME environment variable.\n"); |
393 | 0 | exit(-1); |
394 | 0 | } |
395 | 7 | if (getenv("PID_DIR") == nullptr) { |
396 | 0 | fprintf(stderr, "you need set PID_DIR environment variable.\n"); |
397 | 0 | exit(-1); |
398 | 0 | } |
399 | | |
400 | 7 | SCOPED_INIT_THREAD_CONTEXT(); |
401 | | |
402 | 7 | using doris::Status; |
403 | 7 | using std::string; |
404 | | |
405 | | // open pid file, obtain file lock and save pid |
406 | 7 | string pid_file = string(getenv("PID_DIR")) + "/be.pid"; |
407 | 7 | int fd = open(pid_file.c_str(), O_RDWR | O_CREAT, |
408 | 7 | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); |
409 | 7 | if (fd < 0) { |
410 | 0 | fprintf(stderr, "fail to create pid file."); |
411 | 0 | exit(-1); |
412 | 0 | } |
413 | | |
414 | 7 | string pid = std::to_string((long)getpid()); |
415 | 7 | pid += "\n"; |
416 | 7 | size_t length = write(fd, pid.c_str(), pid.size()); |
417 | 7 | if (length != pid.size()) { |
418 | 0 | fprintf(stderr, "fail to save pid into pid file."); |
419 | 0 | exit(-1); |
420 | 0 | } |
421 | | |
422 | | // descriptor will be leaked when failing to close fd |
423 | 7 | if (::close(fd) < 0) { |
424 | 0 | fprintf(stderr, "failed to close fd of pidfile."); |
425 | 0 | exit(-1); |
426 | 0 | } |
427 | | |
428 | | // init config. |
429 | | // the config in be_custom.conf will overwrite the config in be.conf |
430 | | // Must init custom config after init config, separately. |
431 | | // Because the path of custom config file is defined in be.conf |
432 | 7 | string conffile = string(getenv("DORIS_HOME")) + "/conf/be.conf"; |
433 | 7 | if (!doris::config::init(conffile.c_str(), true, true, true)) { |
434 | 0 | fprintf(stderr, "error read config file. \n"); |
435 | 0 | return -1; |
436 | 0 | } |
437 | | |
438 | 7 | string custom_conffile = doris::config::custom_config_dir + "/be_custom.conf"; |
439 | 7 | if (!doris::config::init(custom_conffile.c_str(), true, false, false)) { |
440 | 0 | fprintf(stderr, "error read custom config file. \n"); |
441 | 0 | return -1; |
442 | 0 | } |
443 | | |
444 | 7 | doris::init_be_version_metrics(); |
445 | | |
446 | | // ATTN: Callers that want to override default gflags variables should do so before calling this method |
447 | 7 | google::ParseCommandLineFlags(&argc, &argv, true); |
448 | | // ATTN: MUST init before LOG |
449 | 7 | doris::init_glog("be"); |
450 | | |
451 | 7 | LOG(INFO) << doris::get_version_string(false); |
452 | | |
453 | 7 | doris::init_thrift_logging(); |
454 | | |
455 | 7 | if (doris::config::enable_fuzzy_mode) { |
456 | 7 | Status status = doris::config::set_fuzzy_configs(); |
457 | 7 | if (!status.ok()) { |
458 | 0 | LOG(WARNING) << "Failed to initialize fuzzy config: " << status; |
459 | 0 | exit(1); |
460 | 0 | } |
461 | 7 | } |
462 | | |
463 | | #if !defined(__SANITIZE_ADDRESS__) && !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \ |
464 | | !defined(THREAD_SANITIZER) && !defined(USE_JEMALLOC) |
465 | | // Change the total TCMalloc thread cache size if necessary. |
466 | | const size_t kDefaultTotalThreadCacheBytes = 1024 * 1024 * 1024; |
467 | | if (!MallocExtension::instance()->SetNumericProperty("tcmalloc.max_total_thread_cache_bytes", |
468 | | kDefaultTotalThreadCacheBytes)) { |
469 | | fprintf(stderr, "Failed to change TCMalloc total thread cache size.\n"); |
470 | | return -1; |
471 | | } |
472 | | #endif |
473 | | |
474 | 7 | std::vector<doris::StorePath> paths; |
475 | 7 | auto olap_res = doris::parse_conf_store_paths(doris::config::storage_root_path, &paths); |
476 | 7 | if (!olap_res) { |
477 | 0 | LOG(ERROR) << "parse config storage path failed, path=" << doris::config::storage_root_path; |
478 | 0 | exit(-1); |
479 | 0 | } |
480 | | |
481 | 7 | std::vector<doris::StorePath> spill_paths; |
482 | 7 | if (doris::config::spill_storage_root_path.empty()) { |
483 | 7 | doris::config::spill_storage_root_path = doris::config::storage_root_path; |
484 | 7 | } |
485 | 7 | olap_res = doris::parse_conf_store_paths(doris::config::spill_storage_root_path, &spill_paths); |
486 | 7 | if (!olap_res) { |
487 | 0 | LOG(ERROR) << "parse config spill storage path failed, path=" |
488 | 0 | << doris::config::spill_storage_root_path; |
489 | 0 | exit(-1); |
490 | 0 | } |
491 | 7 | std::set<std::string> broken_paths; |
492 | 7 | doris::parse_conf_broken_store_paths(doris::config::broken_storage_path, &broken_paths); |
493 | | |
494 | 7 | auto it = paths.begin(); |
495 | 18 | for (; it != paths.end();) { |
496 | 11 | if (broken_paths.count(it->path) > 0) { |
497 | 0 | if (doris::config::ignore_broken_disk) { |
498 | 0 | LOG(WARNING) << "ignore broken disk, path = " << it->path; |
499 | 0 | it = paths.erase(it); |
500 | 0 | } else { |
501 | 0 | LOG(ERROR) << "a broken disk is found " << it->path; |
502 | 0 | exit(-1); |
503 | 0 | } |
504 | 11 | } else if (!doris::check_datapath_rw(it->path)) { |
505 | 0 | if (doris::config::ignore_broken_disk) { |
506 | 0 | LOG(WARNING) << "read write test file failed, path=" << it->path; |
507 | 0 | it = paths.erase(it); |
508 | 0 | } else { |
509 | 0 | LOG(ERROR) << "read write test file failed, path=" << it->path; |
510 | | // if only one disk and the disk is full, also need exit because rocksdb will open failed |
511 | 0 | exit(-1); |
512 | 0 | } |
513 | 11 | } else { |
514 | 11 | ++it; |
515 | 11 | } |
516 | 11 | } |
517 | | |
518 | 7 | if (paths.empty()) { |
519 | 0 | LOG(ERROR) << "All disks are broken, exit."; |
520 | 0 | exit(-1); |
521 | 0 | } |
522 | | |
523 | 7 | it = spill_paths.begin(); |
524 | 18 | for (; it != spill_paths.end();) { |
525 | 11 | if (!doris::check_datapath_rw(it->path)) { |
526 | 0 | if (doris::config::ignore_broken_disk) { |
527 | 0 | LOG(WARNING) << "read write test file failed, path=" << it->path; |
528 | 0 | it = spill_paths.erase(it); |
529 | 0 | } else { |
530 | 0 | LOG(ERROR) << "read write test file failed, path=" << it->path; |
531 | 0 | exit(-1); |
532 | 0 | } |
533 | 11 | } else { |
534 | 11 | ++it; |
535 | 11 | } |
536 | 11 | } |
537 | 7 | if (spill_paths.empty()) { |
538 | 0 | LOG(ERROR) << "All spill disks are broken, exit."; |
539 | 0 | exit(-1); |
540 | 0 | } |
541 | | |
542 | | // initialize libcurl here to avoid concurrent initialization |
543 | 7 | auto curl_ret = curl_global_init(CURL_GLOBAL_ALL); |
544 | 7 | if (curl_ret != 0) { |
545 | 0 | LOG(ERROR) << "fail to initialize libcurl, curl_ret=" << curl_ret; |
546 | 0 | exit(-1); |
547 | 0 | } |
548 | | // add logger for thrift internal |
549 | 7 | apache::thrift::TOutput::instance().setOutputFunction(doris::thrift_output); |
550 | | |
551 | 7 | Status status = Status::OK(); |
552 | | // No JVM is started here on purpose. It is created by the first Java feature that asks |
553 | | // for it - a JNI table format, a Java UDF, an hdfs access - and a BE that uses none of |
554 | | // them runs without one. See Jni::JvmLauncher. |
555 | | |
556 | 7 | if (doris::config::enable_python_udf_support) { |
557 | 7 | if (std::string python_udf_root_path = |
558 | 7 | fmt::format("{}/lib/udf/python", std::getenv("DORIS_HOME")); |
559 | 7 | !std::filesystem::exists(python_udf_root_path)) { |
560 | 1 | std::filesystem::create_directories(python_udf_root_path); |
561 | 1 | } |
562 | | |
563 | | // Normalize and trim all Python-related config parameters |
564 | 7 | std::string python_env_mode = |
565 | 7 | std::string(doris::trim(doris::to_lower(doris::config::python_env_mode))); |
566 | 7 | std::string python_conda_root_path = |
567 | 7 | std::string(doris::trim(doris::config::python_conda_root_path)); |
568 | 7 | std::string python_venv_root_path = |
569 | 7 | std::string(doris::trim(doris::config::python_venv_root_path)); |
570 | 7 | std::string python_venv_interpreter_paths = |
571 | 7 | std::string(doris::trim(doris::config::python_venv_interpreter_paths)); |
572 | | |
573 | 7 | if (python_env_mode == "conda") { |
574 | 1 | if (python_conda_root_path.empty()) { |
575 | 0 | LOG(ERROR) |
576 | 0 | << "Python conda root path is empty, please set `python_conda_root_path` " |
577 | 0 | "or set `enable_python_udf_support` to `false`"; |
578 | 0 | exit(1); |
579 | 0 | } |
580 | 1 | LOG(INFO) << "Doris backend python version manager is initialized. Python conda " |
581 | 1 | "root path: " |
582 | 1 | << python_conda_root_path; |
583 | 1 | status = doris::PythonVersionManager::instance().init(doris::PythonEnvType::CONDA, |
584 | 1 | python_conda_root_path, ""); |
585 | 6 | } else if (python_env_mode == "venv") { |
586 | 6 | if (python_venv_root_path.empty()) { |
587 | 0 | LOG(ERROR) |
588 | 0 | << "Python venv root path is empty, please set `python_venv_root_path` or " |
589 | 0 | "set `enable_python_udf_support` to `false`"; |
590 | 0 | exit(1); |
591 | 0 | } |
592 | 6 | if (python_venv_interpreter_paths.empty()) { |
593 | 0 | LOG(ERROR) |
594 | 0 | << "Python interpreter paths is empty, please set " |
595 | 0 | "`python_venv_interpreter_paths` or set `enable_python_udf_support` to " |
596 | 0 | "`false`"; |
597 | 0 | exit(1); |
598 | 0 | } |
599 | 6 | LOG(INFO) << "Doris backend python version manager is initialized. Python venv " |
600 | 6 | "root path: " |
601 | 6 | << python_venv_root_path |
602 | 6 | << ", python interpreter paths: " << python_venv_interpreter_paths; |
603 | 6 | status = doris::PythonVersionManager::instance().init(doris::PythonEnvType::VENV, |
604 | 6 | python_venv_root_path, |
605 | 6 | python_venv_interpreter_paths); |
606 | 6 | } else { |
607 | 0 | status = Status::InvalidArgument( |
608 | 0 | "Python env mode is invalid, should be `conda` or `venv`. If you don't want to " |
609 | 0 | "enable the Python UDF function, please set `enable_python_udf_support` to " |
610 | 0 | "`false`"); |
611 | 0 | } |
612 | | |
613 | 7 | if (!status.ok()) { |
614 | 0 | LOG(ERROR) << "Failed to initialize python version manager: " << status; |
615 | 0 | exit(1); |
616 | 0 | } |
617 | 7 | LOG(INFO) << doris::PythonVersionManager::instance().to_string(); |
618 | 7 | } |
619 | | |
620 | | // SIGINT and SIGTERM are how the BE is asked to shut down, and the handler installed |
621 | | // here does nothing but raise the flag the loop at the end of main() waits on, so the |
622 | | // shutdown stays orderly. SIGQUIT is claimed here as well, so that it does nothing at |
623 | | // all rather than killing the BE with a core dump. A JVM would rather turn the first |
624 | | // two into a Java Shutdown.exit() and answer the third with a thread dump, and it |
625 | | // installs handlers of its own for all three when it starts. The JVM used to be created |
626 | | // a few lines above this call, which is what left these handlers on top; now that it is |
627 | | // created on demand, Jni::JvmLauncher::_bootstrap() is what puts them back once the JVM |
628 | | // has had its way with them. |
629 | | // https://www.oracle.com/java/technologies/javase/signals.html |
630 | 7 | doris::init_signals(); |
631 | | // ATTN: MUST init before `ExecEnv`, `StorageEngine` and other daemon services |
632 | | // |
633 | | // Daemon ───┬──► StorageEngine ──► ExecEnv ──► Disk/Mem/CpuInfo |
634 | | // │ |
635 | | // │ |
636 | | // BackendService ─┘ |
637 | 7 | doris::CpuInfo::init(); |
638 | 7 | doris::DiskInfo::init(); |
639 | 7 | doris::MemInfo::init(); |
640 | | |
641 | 7 | LOG(INFO) << doris::CpuInfo::debug_string(); |
642 | 7 | LOG(INFO) << doris::DiskInfo::debug_string(); |
643 | 7 | LOG(INFO) << doris::MemInfo::debug_string(); |
644 | | |
645 | | // Doris-patched GNU libunwind reads PHDR metadata from our lock-free snapshot instead of |
646 | | // entering glibc dl_iterate_phdr while jemalloc profiling or signal-context unwinding may |
647 | | // already be involved in loader-lock-sensitive code. Configure libunwind before daemon threads |
648 | | // start so all later heap-profile and stack-trace unwinds use the same lock-safe policy. |
649 | 7 | configureLibunwindPHDRCache(); |
650 | 7 | updatePHDRCache(); |
651 | 7 | LOG(INFO) << "PHDR cache enabled: " << hasPHDRCache(); |
652 | 7 | #if defined(__ELF__) && !defined(__FreeBSD__) |
653 | 7 | auto symbol_index = doris::SymbolIndex::instance(); |
654 | 7 | LOG(INFO) << "SymbolIndex preloaded: objects=" << symbol_index->objects().size() |
655 | 7 | << " symbols=" << symbol_index->symbols().size(); |
656 | 7 | #endif |
657 | 7 | if (!doris::BackendOptions::init()) { |
658 | 0 | exit(-1); |
659 | 0 | } |
660 | | |
661 | | // init exec env |
662 | 7 | auto* exec_env(doris::ExecEnv::GetInstance()); |
663 | 7 | status = doris::ExecEnv::init(doris::ExecEnv::GetInstance(), paths, spill_paths, broken_paths); |
664 | 7 | if (status != Status::OK()) { |
665 | 0 | std::cerr << "failed to init doris storage engine, res=" << status; |
666 | 0 | exit_on_startup_failure(); |
667 | 0 | } |
668 | | |
669 | | // Start concurrency stats manager |
670 | 7 | doris::ConcurrencyStatsManager::instance().start(); |
671 | | |
672 | | // begin to start services |
673 | 7 | doris::ThriftRpcHelper::setup(exec_env); |
674 | | // 1. thrift server with be_port |
675 | 7 | std::shared_ptr<doris::BaseBackendService> service; |
676 | 7 | std::unique_ptr<doris::server::IServerStarter> backend_thrift_starter; |
677 | 7 | std::unique_ptr<doris::server::IServerStarter> brpc_starter; |
678 | 7 | std::unique_ptr<doris::server::IServerStarter> http_starter; |
679 | 7 | std::unique_ptr<doris::server::IServerStarter> heartbeat_thrift_starter; |
680 | 7 | std::unique_ptr<doris::server::IServerStarter> flight_starter; |
681 | 7 | bool backend_thrift_started = false; |
682 | 7 | bool brpc_started = false; |
683 | 7 | bool http_started = false; |
684 | 7 | bool heartbeat_thrift_started = false; |
685 | 7 | bool flight_started = false; |
686 | 7 | auto stop_and_join_server = [](std::unique_ptr<doris::server::IServerStarter>& starter, |
687 | 7 | bool started) { |
688 | 0 | if (starter != nullptr) { |
689 | 0 | if (started) { |
690 | 0 | starter->stop(); |
691 | 0 | } |
692 | 0 | starter->join(); |
693 | 0 | } |
694 | 0 | }; |
695 | 7 | std::function<void(Status&, std::string_view)> stop_work_if_error = [&](Status& status, |
696 | 70 | std::string_view msg) { |
697 | 70 | if (!status.ok()) { |
698 | 0 | std::cerr << msg << '\n'; |
699 | 0 | service->stop_works(); |
700 | 0 | if (doris::config::enable_graceful_exit_check) { |
701 | 0 | stop_and_join_server(flight_starter, flight_started); |
702 | 0 | stop_and_join_server(heartbeat_thrift_starter, heartbeat_thrift_started); |
703 | 0 | stop_and_join_server(http_starter, http_started); |
704 | 0 | stop_and_join_server(backend_thrift_starter, backend_thrift_started); |
705 | 0 | stop_and_join_server(brpc_starter, brpc_started); |
706 | 0 | service.reset(); |
707 | 0 | } |
708 | 0 | exit_on_startup_failure(); |
709 | 0 | } |
710 | 70 | }; |
711 | | |
712 | 7 | if (doris::config::is_cloud_mode()) { |
713 | 1 | service = std::make_shared<doris::CloudBackendService>( |
714 | 1 | exec_env->storage_engine().to_cloud(), exec_env); |
715 | 6 | } else { |
716 | 6 | service = std::make_shared<doris::BackendService>(exec_env->storage_engine().to_local(), |
717 | 6 | exec_env); |
718 | 6 | } |
719 | | |
720 | 7 | status = doris::server::create_backend_thrift_starter(exec_env, doris::config::be_port, service, |
721 | 7 | &backend_thrift_starter); |
722 | 7 | stop_work_if_error(status, "Failed to create BE server, exiting"); |
723 | 7 | status = backend_thrift_starter->start(); |
724 | 7 | backend_thrift_started = status.ok(); |
725 | 7 | stop_work_if_error(status, "Doris BE server did not start correctly, exiting"); |
726 | | |
727 | | // 2. brpc service |
728 | 7 | status = doris::server::create_brpc_starter(exec_env, doris::config::brpc_port, |
729 | 7 | doris::config::brpc_num_threads, &brpc_starter); |
730 | 7 | stop_work_if_error(status, "Failed to create BRPC service, exiting"); |
731 | 7 | status = brpc_starter->start(); |
732 | 7 | brpc_started = status.ok(); |
733 | 7 | stop_work_if_error(status, "BRPC service did not start correctly, exiting"); |
734 | | |
735 | | // 3. http service |
736 | 7 | status = |
737 | 7 | doris::server::create_http_starter(exec_env, doris::config::webserver_port, |
738 | 7 | doris::config::webserver_num_workers, &http_starter); |
739 | 7 | stop_work_if_error(status, "Failed to create BE HTTP service, exiting"); |
740 | 7 | status = http_starter->start(); |
741 | 7 | http_started = status.ok(); |
742 | 7 | stop_work_if_error(status, "Doris Be http service did not start correctly, exiting"); |
743 | | |
744 | | // 4. heart beat server |
745 | 7 | doris::ClusterInfo* cluster_info = exec_env->cluster_info(); |
746 | 7 | status = doris::server::create_heartbeat_thrift_starter( |
747 | 7 | exec_env, doris::config::heartbeat_service_port, |
748 | 7 | doris::config::heartbeat_service_thread_count, cluster_info, &heartbeat_thrift_starter); |
749 | 7 | stop_work_if_error(status, "Heartbeat services did not start correctly, exiting"); |
750 | | |
751 | 7 | status = heartbeat_thrift_starter->start(); |
752 | 7 | heartbeat_thrift_started = status.ok(); |
753 | 7 | stop_work_if_error(status, "Doris BE HeartBeat Service did not start correctly, exiting: " + |
754 | 7 | status.to_string()); |
755 | | |
756 | | // 5. arrow flight service |
757 | 7 | status = doris::server::create_flight_starter(doris::config::arrow_flight_sql_port, |
758 | 7 | &flight_starter); |
759 | 7 | stop_work_if_error(status, "Failed to create Arrow Flight service, exiting"); |
760 | 7 | status = flight_starter->start(); |
761 | 7 | flight_started = status.ok(); |
762 | 7 | stop_work_if_error( |
763 | 7 | status, "Arrow Flight Service did not start correctly, exiting, " + status.to_string()); |
764 | | |
765 | | // 6. start daemon thread to do clean or gc jobs |
766 | 7 | doris::Daemon daemon; |
767 | 7 | daemon.start(); |
768 | | |
769 | 7 | exec_env->storage_engine().notify_listeners(); |
770 | | |
771 | 7 | doris::k_is_server_ready = true; |
772 | | |
773 | | // 7. load the deployed Java plugins, once the BE is otherwise serving. |
774 | | // |
775 | | // On its own thread and non-fatal on purpose: the point is that a plugin broken by a bad |
776 | | // deployment shows up in the log now instead of inside the first user query that needs |
777 | | // it, and a plugin that cannot load must not hold up or take down everything else. When |
778 | | // no plugin is deployed this starts no JVM and returns immediately. |
779 | | // |
780 | | // Joined on the way out rather than detached, so that a stop arriving while plugins are |
781 | | // still loading waits for them instead of running the global destructors underneath a |
782 | | // thread that is inside the JVM. Warming up is bounded - one JVM start plus one pass over |
783 | | // the plugin directory - and the Java side of it is a single call, so there is nothing to |
784 | | // interrupt halfway. |
785 | 7 | std::shared_ptr<doris::Thread> plugin_warmup_thread; |
786 | 7 | if (doris::config::enable_java_support && doris::config::java_plugin_warmup) { |
787 | 0 | EXIT_IF_ERROR(doris::Thread::create( |
788 | 0 | "Jni", "java_plugin_warmup", |
789 | 0 | []() { |
790 | | // Named background thread with a thread context of its own: everything it |
791 | | // allocates would otherwise be orphan memory, and the try/catch is what |
792 | | // keeps a directory that becomes unreadable mid-iteration from reaching |
793 | | // std::terminate (directory_iterator::operator++ throws). |
794 | 0 | SCOPED_INIT_THREAD_CONTEXT(); |
795 | 0 | try { |
796 | 0 | if (Status status = doris::Jni::PluginRegistry::warmup(); !status.ok()) { |
797 | 0 | LOG(WARNING) << "failed to warm up Java plugins: " << status; |
798 | 0 | } |
799 | 0 | } catch (const std::exception& e) { |
800 | 0 | LOG(WARNING) << "failed to warm up Java plugins: " << e.what(); |
801 | 0 | } catch (...) { |
802 | 0 | LOG(WARNING) << "failed to warm up Java plugins: unknown exception"; |
803 | 0 | } |
804 | 0 | }, |
805 | 0 | &plugin_warmup_thread)); |
806 | 0 | } |
807 | | |
808 | 1.58k | while (!doris::k_doris_exit) { |
809 | | #if defined(LEAK_SANITIZER) |
810 | | __lsan_do_leak_check(); |
811 | | #endif |
812 | 1.57k | sleep(3); |
813 | 1.57k | } |
814 | 7 | doris::k_is_server_ready = false; |
815 | 7 | LOG(INFO) << "Doris main exiting."; |
816 | 7 | #if defined(LLVM_PROFILE) |
817 | 7 | __llvm_profile_write_file(); |
818 | 7 | LOG(INFO) << "Flush profile file."; |
819 | 7 | #endif |
820 | | // For graceful shutdown, need to wait for all running queries to stop |
821 | 7 | exec_env->wait_for_all_tasks_done(); |
822 | | |
823 | 7 | if (!doris::config::enable_graceful_exit_check) { |
824 | | // If not in memleak check mode, no need to wait all objects de-constructed normally, just exit. |
825 | | // It will make sure that graceful shutdown can be done definitely. |
826 | 0 | LOG(INFO) << "Doris main exited."; |
827 | 0 | google::FlushLogFiles(google::GLOG_INFO); |
828 | 0 | _exit(0); // Do not call exit(0), it will wait for all objects de-constructed normally |
829 | 0 | return 0; |
830 | 0 | } |
831 | | // Before anything is torn down: the warmup thread may still be inside the JVM, and it |
832 | | // reaches BE state that the destructors below free. The fast path above does not need |
833 | | // this - _exit() runs no destructor at all. |
834 | 7 | if (plugin_warmup_thread != nullptr) { |
835 | 0 | plugin_warmup_thread->join(); |
836 | 0 | LOG(INFO) << "Java plugin warmup stopped"; |
837 | 0 | } |
838 | 7 | daemon.stop(); |
839 | 7 | flight_starter->stop(); |
840 | 7 | flight_starter->join(); |
841 | 7 | LOG(INFO) << "Flight server stopped."; |
842 | 7 | heartbeat_thrift_starter->stop(); |
843 | 7 | heartbeat_thrift_starter->join(); |
844 | 7 | LOG(INFO) << "Heartbeat server stopped"; |
845 | | // The stream load recorder manager writes its audit records through this BE's own http |
846 | | // service, so it has to be stopped while that service is still up. Otherwise an audit |
847 | | // load that is in flight here can never be answered, and it blocks the join() done by |
848 | | // SAFE_STOP(_stream_load_recorder_manager) in ExecEnv::destroy() for up to the stream |
849 | | // load timeout, which is longer than the grace period of stop_be.sh --grace. |
850 | 7 | if (auto* recorder_manager = exec_env->stream_load_recorder_manager(); |
851 | 7 | recorder_manager != nullptr) { |
852 | 3 | recorder_manager->stop(); |
853 | 3 | } |
854 | | // TODO(zhiqiang): http_service |
855 | 7 | http_starter->stop(); |
856 | 7 | http_starter->join(); |
857 | 7 | LOG(INFO) << "Http service stopped"; |
858 | 7 | backend_thrift_starter->stop(); |
859 | 7 | backend_thrift_starter->join(); |
860 | 7 | LOG(INFO) << "Be server stopped"; |
861 | 7 | brpc_starter->stop(); |
862 | 7 | brpc_starter->join(); |
863 | 7 | LOG(INFO) << "Brpc service stopped"; |
864 | 7 | service.reset(); |
865 | 7 | LOG(INFO) << "Backend Service stopped"; |
866 | 7 | exec_env->destroy(); |
867 | | // Everything above should have released the resource context of every query and load. One |
868 | | // that is still alive is held either by a thread that has not finished or by a reference |
869 | | // that is never released; LeakSanitizer reports the latter after main() returns, but cannot |
870 | | // tell which task it belonged to, so name the task here. |
871 | 7 | if (std::string live_contexts = doris::ResourceContext::describe_live_contexts(100); |
872 | 7 | !live_contexts.empty()) { |
873 | 0 | LOG(WARNING) << live_contexts; |
874 | | // Also to be.out, where the LeakSanitizer report goes. |
875 | 0 | std::cerr << live_contexts << '\n'; |
876 | 7 | } else { |
877 | 7 | LOG(INFO) << "No resource context is alive after shutdown"; |
878 | 7 | } |
879 | 7 | LOG(INFO) << "All service stopped, doris main exited."; |
880 | 7 | return 0; |
881 | 7 | } |
882 | | |
883 | 0 | static void help(const char* progname) { |
884 | 0 | printf("%s is the Doris backend server.\n\n", progname); |
885 | 0 | printf("Usage:\n %s [OPTION]...\n\n", progname); |
886 | 0 | printf("Options:\n"); |
887 | 0 | printf(" -v, --version output version information, then exit\n"); |
888 | 0 | printf(" -?, --help show this help, then exit\n"); |
889 | 0 | } |