This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new c5c3ca17b35 [bugfix](glibc) fix glibc compatible bugs (#67694)
c5c3ca17b35 is described below
commit c5c3ca17b353594adf009d4bdb0e92666af0cd47
Author: yiguolei <[email protected]>
AuthorDate: Wed Sep 9 21:18:19 2026 +0800
[bugfix](glibc) fix glibc compatible bugs (#67694)
### What problem does this PR solve?
pick https://github.com/apache/doris/pull/67701/changes
Rust std weak-links this glibc 2.18 entry point and has an internal
fallback when it is absent. Since the LDB sysroot exposes it, the final
linker would otherwise record GLIBC_2.18.
See https://github.com/rust-lang/rust/issues/57497 for more details.
Problem Summary:
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/CMakeLists.txt | 10 ++
be/cmake/check_glibc_compatibility.cmake | 70 +++++++++++++
be/src/glibc-compatibility/lance_symbol_versions.c | 115 ++++++++++++++++++++-
be/src/glibc-compatibility/musl/expf.c | 80 ++++++++++++++
be/src/service/CMakeLists.txt | 22 +++-
5 files changed, 293 insertions(+), 4 deletions(-)
diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt
index 389c3883b29..555b2508f9d 100644
--- a/be/CMakeLists.txt
+++ b/be/CMakeLists.txt
@@ -725,7 +725,17 @@ if (GLIBC_COMPATIBILITY)
# Keep lance_c here instead of COMMON_THIRDPARTY: placing its required
libm there
# would resolve -lm symbol before Doris compatibility is scanned,
preventing
# the linker from selecting Doris' optimized implementations.
+ # lance_c can also introduce single-precision math references after the
+ # compatibility archive has already been scanned. As ClickHouse does for
its
+ # compatibility archive, force the GLIBC_2.27 math symbols to be undefined
up
+ # front so the linker extracts Doris' musl implementations instead of
binding
+ # late references to the toolchain's libm.
set(DORIS_LINK_LIBS ${DORIS_LINK_LIBS}
+ -Wl,-u,logf
+ -Wl,-u,powf
+ -Wl,-u,expf
+ -Wl,-u,exp2f
+ -Wl,-u,log2f
glibc-compatibility-explicit
glibc-compatibility
-lm
diff --git a/be/cmake/check_glibc_compatibility.cmake
b/be/cmake/check_glibc_compatibility.cmake
new file mode 100644
index 00000000000..435419bf07d
--- /dev/null
+++ b/be/cmake/check_glibc_compatibility.cmake
@@ -0,0 +1,70 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+if (NOT DEFINED ARTIFACT OR NOT EXISTS "${ARTIFACT}")
+ message(FATAL_ERROR "Cannot check glibc compatibility: '${ARTIFACT}' does
not exist")
+endif()
+
+if (NOT DEFINED BASELINE OR BASELINE STREQUAL "")
+ message(FATAL_ERROR "Cannot check glibc compatibility: BASELINE is not
set")
+endif()
+
+if (NOT DEFINED OBJDUMP OR OBJDUMP STREQUAL "" OR NOT EXISTS "${OBJDUMP}")
+ find_program(OBJDUMP NAMES objdump llvm-objdump REQUIRED)
+endif()
+
+execute_process(
+ COMMAND "${OBJDUMP}" -T "${ARTIFACT}"
+ RESULT_VARIABLE objdump_result
+ OUTPUT_VARIABLE dynamic_symbol_table
+ ERROR_VARIABLE objdump_error)
+
+if (NOT objdump_result EQUAL 0)
+ message(FATAL_ERROR
+ "Cannot read the dynamic symbol table from '${ARTIFACT}':
${objdump_error}")
+endif()
+
+string(REPLACE "\n" ";" dynamic_symbol_lines "${dynamic_symbol_table}")
+set(incompatible_symbols)
+
+foreach (symbol_line IN LISTS dynamic_symbol_lines)
+ # Only undefined symbols are runtime requirements on the target system.
+ if (symbol_line MATCHES "\\*UND\\*")
+ string(REGEX MATCH "GLIBC_[0-9]+(\\.[0-9]+)+" symbol_version
"${symbol_line}")
+ if (NOT "${symbol_version}" STREQUAL "")
+ string(REGEX REPLACE "^GLIBC_" "" numeric_version
"${symbol_version}")
+ if ("${numeric_version}" VERSION_GREATER "${BASELINE}")
+ string(REGEX MATCH "[^ \t]+$" symbol_name "${symbol_line}")
+ list(APPEND incompatible_symbols "${symbol_version}
${symbol_name}")
+ endif()
+ endif()
+ endif()
+endforeach()
+
+if (NOT "${incompatible_symbols}" STREQUAL "")
+ list(REMOVE_DUPLICATES incompatible_symbols)
+ list(SORT incompatible_symbols)
+ string(JOIN "\n " formatted_symbols ${incompatible_symbols})
+ message(FATAL_ERROR
+ "${ARTIFACT} requires glibc symbols newer than GLIBC_${BASELINE}:\n"
+ " ${formatted_symbols}\n"
+ "The production BE must remain runnable on CentOS 7 (glibc
${BASELINE}).")
+endif()
+
+message(STATUS
+ "glibc compatibility check passed: ${ARTIFACT} requires no symbols newer
than "
+ "GLIBC_${BASELINE}")
diff --git a/be/src/glibc-compatibility/lance_symbol_versions.c
b/be/src/glibc-compatibility/lance_symbol_versions.c
index b0594dce06b..66b9cdae5cb 100644
--- a/be/src/glibc-compatibility/lance_symbol_versions.c
+++ b/be/src/glibc-compatibility/lance_symbol_versions.c
@@ -18,8 +18,12 @@
#define _GNU_SOURCE
#include <fcntl.h>
+#include <pthread.h>
#include <spawn.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
#include <sys/uio.h>
+#include <unistd.h>
#if defined(__x86_64__)
#define DORIS_GLIBC_BASE_VERSION "GLIBC_2.2.5"
@@ -41,6 +45,76 @@
// glibc symbol, so it cannot recurse back into the hidden wrapper.
#define DORIS_HIDDEN __attribute__((visibility("hidden")))
+typedef void (*doris_tls_destructor)(void*);
+
+struct doris_tls_destructor_entry {
+ doris_tls_destructor destructor;
+ void* object;
+ struct doris_tls_destructor_entry* next;
+};
+
+static pthread_key_t doris_tls_destructor_key;
+static pthread_once_t doris_tls_destructor_once = PTHREAD_ONCE_INIT;
+
+static void doris_run_tls_destructors(void* value) {
+ struct doris_tls_destructor_entry* entry = value;
+ while (entry != NULL) {
+ struct doris_tls_destructor_entry* next = entry->next;
+ doris_tls_destructor destructor = entry->destructor;
+ void* object = entry->object;
+
+ // Publish the remainder before invoking the destructor. A destructor
+ // may register another TLS destructor, which must run before the older
+ // entries that are still pending.
+ if (pthread_setspecific(doris_tls_destructor_key, next) != 0) {
+ abort();
+ }
+ free(entry);
+ destructor(object);
+ entry = pthread_getspecific(doris_tls_destructor_key);
+ }
+}
+
+static void doris_run_main_thread_tls_destructors(void) {
+ doris_run_tls_destructors(pthread_getspecific(doris_tls_destructor_key));
+}
+
+static void doris_init_tls_destructor_key(void) {
+ if (pthread_key_create(&doris_tls_destructor_key,
doris_run_tls_destructors) != 0) {
+ abort();
+ }
+ // pthread_key_create() arranges for doris_run_tls_destructors() to be
+ // called automatically when an ordinary worker thread exits. However,
+ // returning from main() (which is equivalent to exit()) does not run the
+ // initial thread's pthread key destructors. Register an atexit handler so
+ // TLS destructors belonging to the thread that performs normal process
+ // termination are still invoked.
+ //
+ // This relies on Doris normally terminating the process from its initial
+ // thread. If another thread calls exit(), atexit handlers execute in that
+ // thread and pthread_getspecific() observes that thread's TLS state.
+ // atexit handlers are not invoked by abort(), _exit(), fatal signals, or
+ // SIGKILL; those paths are already abnormal process termination.
+ //
+ // Unlike glibc, this fallback puts the initial thread's TLS cleanup in the
+ // same LIFO list as atexit callbacks and static-object destructors. A
static
+ // object initialized after this handler is registered is therefore
destroyed
+ // before the TLS objects. We accept this limitation because Doris normally
+ // terminates with _exit(), and normal destructor processing is currently
used
+ // only when enable_graceful_exit_check is enabled for sanitizer leak
checks.
+ // TLS destructors on that diagnostic path must not access static-lifetime
+ // objects that may already have been destroyed. If graceful exit becomes a
+ // production path, the initial thread's TLS destructors must instead be
run
+ // explicitly before the atexit/static-destructor list.
+ //
+ // A registration failure means that normal main-thread TLS cleanup cannot
+ // be guaranteed, so fail immediately instead of continuing with a
partially
+ // installed compatibility implementation.
+ if (atexit(doris_run_main_thread_tls_destructors) != 0) {
+ abort();
+ }
+}
+
extern __typeof__(posix_spawnp) __doris_old_posix_spawnp;
DORIS_GLIBC_SYMVER(__doris_old_posix_spawnp, posix_spawnp,
DORIS_GLIBC_BASE_VERSION);
@@ -53,8 +127,8 @@
DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_destroy, posix_spawn_fil
DORIS_GLIBC_BASE_VERSION);
extern __typeof__(posix_spawn_file_actions_adddup2)
__doris_old_posix_spawn_file_actions_adddup2;
-DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_adddup2,
- posix_spawn_file_actions_adddup2, DORIS_GLIBC_BASE_VERSION);
+DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_adddup2,
posix_spawn_file_actions_adddup2,
+ DORIS_GLIBC_BASE_VERSION);
extern __typeof__(preadv) __doris_old_preadv;
DORIS_GLIBC_SYMVER(__doris_old_preadv, preadv, DORIS_GLIBC_PREADV_VERSION);
@@ -90,3 +164,40 @@ DORIS_HIDDEN ssize_t splice(int fd_in, off64_t* offset_in,
int fd_out, off64_t*
size_t length, unsigned int flags) {
return __doris_old_splice(fd_in, offset_in, fd_out, offset_out, length,
flags);
}
+
+// Rust std weak-links copy_file_range and otherwise issues the syscall itself.
+// Provide that syscall path locally so linking on glibc 2.27 does not attach a
+// GLIBC_2.27 version requirement. Old kernels return ENOSYS and Rust falls
back
+// to its generic copy loop.
+DORIS_HIDDEN ssize_t copy_file_range(int fd_in, off64_t* offset_in, int
fd_out, off64_t* offset_out,
+ size_t length, unsigned int flags) {
+ return (ssize_t)syscall(SYS_copy_file_range, fd_in, offset_in, fd_out,
offset_out, length,
+ flags);
+}
+
+// Rust std weak-links this glibc 2.18 entry point and has an internal fallback
+// when it is absent. Since the LDB sysroot exposes it, the final linker would
+// otherwise record GLIBC_2.18. Supply equivalent pthread-key based
registration
+// locally. All callers are linked into doris_be, so dso_symbol tracking for
+// dlclose is intentionally unnecessary.
+// See https://github.com/rust-lang/rust/issues/57497 for more details.
+DORIS_HIDDEN int __cxa_thread_atexit_impl(doris_tls_destructor destructor,
void* object,
+ void* dso_symbol) {
+ (void)dso_symbol;
+ if (pthread_once(&doris_tls_destructor_once,
doris_init_tls_destructor_key) != 0) {
+ abort();
+ }
+
+ struct doris_tls_destructor_entry* entry = malloc(sizeof(*entry));
+ if (entry == NULL) {
+ abort();
+ }
+ entry->destructor = destructor;
+ entry->object = object;
+ entry->next = pthread_getspecific(doris_tls_destructor_key);
+ if (pthread_setspecific(doris_tls_destructor_key, entry) != 0) {
+ free(entry);
+ abort();
+ }
+ return 0;
+}
diff --git a/be/src/glibc-compatibility/musl/expf.c
b/be/src/glibc-compatibility/musl/expf.c
new file mode 100644
index 00000000000..f9fbf8e727d
--- /dev/null
+++ b/be/src/glibc-compatibility/musl/expf.c
@@ -0,0 +1,80 @@
+/*
+ * Single-precision e^x function.
+ *
+ * Copyright (c) 2017-2018, Arm Limited.
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <math.h>
+#include <stdint.h>
+#include "libm.h"
+#include "exp2f_data.h"
+
+/*
+EXP2F_TABLE_BITS = 5
+EXP2F_POLY_ORDER = 3
+
+ULP error: 0.502 (nearest rounding.)
+Relative error: 1.69 * 2^-34 in [-ln2/64, ln2/64] (before rounding.)
+Wrong count: 170635 (all nearest rounding wrong results with fma.)
+Non-nearest ULP error: 1 (rounded ULP error)
+*/
+
+#define N (1 << EXP2F_TABLE_BITS)
+#define InvLn2N __exp2f_data.invln2_scaled
+#define T __exp2f_data.tab
+#define C __exp2f_data.poly_scaled
+
+static inline uint32_t top12(float x)
+{
+ return asuint(x) >> 20;
+}
+
+float expf(float x)
+{
+ uint32_t abstop;
+ uint64_t ki, t;
+ double_t kd, xd, z, r, r2, y, s;
+
+ xd = (double_t)x;
+ abstop = top12(x) & 0x7ff;
+ if (predict_false(abstop >= top12(88.0f))) {
+ /* |x| >= 88 or x is nan. */
+ if (asuint(x) == asuint(-INFINITY))
+ return 0.0f;
+ if (abstop >= top12(INFINITY))
+ return x + x;
+ if (x > 0x1.62e42ep6f) /* x > log(0x1p128) ~= 88.72 */
+ return __math_oflowf(0);
+ if (x < -0x1.9fe368p6f) /* x < log(0x1p-150) ~= -103.97 */
+ return __math_uflowf(0);
+ }
+
+ /* x*N/Ln2 = k + r with r in [-1/2, 1/2] and int k. */
+ z = InvLn2N * xd;
+
+ /* Round and convert z to int, the result is in [-150*N, 128*N] and
+ ideally ties-to-even rule is used, otherwise the magnitude of r
+ can be bigger which gives larger approximation error. */
+#if TOINT_INTRINSICS
+ kd = roundtoint(z);
+ ki = converttoint(z);
+#else
+# define SHIFT __exp2f_data.shift
+ kd = eval_as_double(z + SHIFT);
+ ki = asuint64(kd);
+ kd -= SHIFT;
+#endif
+ r = z - kd;
+
+ /* exp(x) = 2^(k/N) * 2^(r/N) ~= s * (C0*r^3 + C1*r^2 + C2*r + 1) */
+ t = T[ki % N];
+ t += ki << (52 - EXP2F_TABLE_BITS);
+ s = asdouble(t);
+ z = C[0] * r + C[1];
+ r2 = r * r;
+ y = C[2] * r + 1;
+ y = z * r2 + y;
+ y = y * s;
+ return eval_as_float(y);
+}
diff --git a/be/src/service/CMakeLists.txt b/be/src/service/CMakeLists.txt
index b9bcef0b7b8..1f52d4f7a60 100644
--- a/be/src/service/CMakeLists.txt
+++ b/be/src/service/CMakeLists.txt
@@ -46,13 +46,31 @@ if (${MAKE_TEST} STREQUAL "OFF" AND ${BUILD_BENCHMARK}
STREQUAL "OFF")
endif ()
pch_reuse(doris_be)
- # This permits libraries loaded by dlopen to link to the symbols in the
program.
- set_target_properties(doris_be PROPERTIES ENABLE_EXPORTS 1)
+ # Keep doris_be symbols private. Java UDF callbacks are registered
explicitly through JNI and
+ # Python UDFs run out of process, so neither requires executable exports.
Exporting all symbols
+ # would also let dlopened libraries bind to private copies from static
dependencies.
target_link_libraries(doris_be
${DORIS_LINK_LIBS}
)
+ if (OS_LINUX AND GLIBC_COMPATIBILITY)
+ # Use TARGET_FILE rather than an install/output directory so this check
+ # follows doris_be if its build location or output name changes.
+ set(GLIBC_COMPATIBILITY_CHECK
+ "${BASE_DIR}/cmake/check_glibc_compatibility.cmake")
+ set_property(TARGET doris_be APPEND PROPERTY LINK_DEPENDS
+ "${GLIBC_COMPATIBILITY_CHECK}")
+ add_custom_command(TARGET doris_be POST_BUILD
+ COMMAND "${CMAKE_COMMAND}"
+ "-DARTIFACT=$<TARGET_FILE:doris_be>"
+ "-DOBJDUMP=${CMAKE_OBJDUMP}"
+ "-DBASELINE=2.17"
+ -P "${GLIBC_COMPATIBILITY_CHECK}"
+ COMMENT "Checking that doris_be requires no glibc symbols newer
than GLIBC_2.17"
+ VERBATIM)
+ endif()
+
install(DIRECTORY DESTINATION ${OUTPUT_DIR}/lib/)
install(TARGETS doris_be DESTINATION ${OUTPUT_DIR}/lib/)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]