Fix jemalloc background thread busy-spin on macOS (clock mismatch)

On macOS, `nstime_get` used `mach_absolute_time` (a monotonic uptime clock)
because `JEMALLOC_HAVE_MACH_ABSOLUTE_TIME` was defined in the Darwin config
headers, while the background thread's condition variable is created with the
default clock (`CLOCK_REALTIME`): macOS has no `pthread_condattr_setclock`, and
jemalloc only attempts to bind the condvar to a monotonic clock when
`JEMALLOC_HAVE_CLOCK_MONOTONIC[_COARSE]` is defined (it is not, on Darwin).

`background_thread_sleep` builds the `pthread_cond_timedwait` deadline from the
`nstime` (uptime) clock and passes it to a `CLOCK_REALTIME` wait. The uptime
value is decades behind the realtime epoch, so the deadline is always in the
past and the wait returns `ETIMEDOUT` immediately - the background thread
busy-spins a CPU core during the whole decay window instead of sleeping for
`dirty_decay_ms`. This would replace the old `madvise` storm with a hot
background-thread loop on exactly the allocation-churning workload that
enabling `background_thread:true` was meant to speed up.

Fix: undef `JEMALLOC_HAVE_MACH_ABSOLUTE_TIME` in the Darwin headers so
`nstime_get` falls back to `gettimeofday` (`CLOCK_REALTIME`) - the same clock
the condvar waits on - so the timed-wait deadline is honoured. This is the
canonical clock for `pthread_cond_timedwait` on macOS. Linux and FreeBSD are
unaffected (they use their own config headers).

This is a prerequisite for `JEMALLOC_BACKGROUND_THREAD` to work correctly on
macOS, enabled in the previous commits of this branch.

See https://github.com/ClickHouse/ClickHouse/issues/108429

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Alexey Milovidov
2026-06-29 00:08:24 +00:00
co-authored by Claude Opus 4.8
parent d660d0cfcc
commit 3b3c99a08d
3 changed files with 40 additions and 2 deletions
+6
View File
@@ -59,6 +59,12 @@ else()
# JEMALLOC_BACKGROUND_THREAD and switching malloc_mutex_t to pthread_mutex_t (undef
# JEMALLOC_OS_UNFAIR_LOCK), as on Linux and FreeBSD.
#
# In the same Darwin headers we also undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME so that nstime_get() uses
# gettimeofday() (CLOCK_REALTIME). macOS cannot bind a pthread condition variable to a monotonic clock
# (no pthread_condattr_setclock), so the background thread waits on CLOCK_REALTIME; nstime must use the
# same clock, otherwise the pthread_cond_timedwait() deadline is always in the past and the thread
# busy-spins instead of sleeping for dirty_decay_ms.
#
# Previously dirty_decay_ms was set to 0 here (immediate purging) to bound RSS, under the assumption
# that background_thread was unavailable. That assumption was wrong, and immediate purging is very
# expensive for allocation-churning workloads (e.g. recursive CTEs): every free issues a madvise()
@@ -112,8 +112,24 @@
/*
* Defined if mach_absolute_time() is available.
*
* ClickHouse: intentionally left undefined so that nstime_get() falls back to
* gettimeofday() (CLOCK_REALTIME). With this defined, nstime_get() uses
* mach_absolute_time(), a monotonic uptime clock. But the background thread's
* condition variable is created with the default clock (CLOCK_REALTIME): macOS
* provides no pthread_condattr_setclock to bind it to a monotonic clock, and
* jemalloc only attempts that when JEMALLOC_HAVE_CLOCK_MONOTONIC[_COARSE] is
* defined, which it is not here. background_thread_sleep() builds the
* pthread_cond_timedwait() deadline from the nstime (uptime) clock and passes
* it to a CLOCK_REALTIME wait; the uptime value is decades behind the realtime
* epoch, so the deadline is always in the past and the wait returns ETIMEDOUT
* immediately - the background thread busy-spins a CPU core instead of sleeping
* for dirty_decay_ms. Falling back to gettimeofday() makes nstime_get() use the
* same clock the condvar waits on (CLOCK_REALTIME), so the deadline is honoured.
* This is a prerequisite for JEMALLOC_BACKGROUND_THREAD to work correctly on
* macOS. See https://github.com/ClickHouse/ClickHouse/issues/108429
*/
#define JEMALLOC_HAVE_MACH_ABSOLUTE_TIME
/* #undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME */
/*
* Defined if clock_gettime(CLOCK_REALTIME, ...) is available.
@@ -112,8 +112,24 @@
/*
* Defined if mach_absolute_time() is available.
*
* ClickHouse: intentionally left undefined so that nstime_get() falls back to
* gettimeofday() (CLOCK_REALTIME). With this defined, nstime_get() uses
* mach_absolute_time(), a monotonic uptime clock. But the background thread's
* condition variable is created with the default clock (CLOCK_REALTIME): macOS
* provides no pthread_condattr_setclock to bind it to a monotonic clock, and
* jemalloc only attempts that when JEMALLOC_HAVE_CLOCK_MONOTONIC[_COARSE] is
* defined, which it is not here. background_thread_sleep() builds the
* pthread_cond_timedwait() deadline from the nstime (uptime) clock and passes
* it to a CLOCK_REALTIME wait; the uptime value is decades behind the realtime
* epoch, so the deadline is always in the past and the wait returns ETIMEDOUT
* immediately - the background thread busy-spins a CPU core instead of sleeping
* for dirty_decay_ms. Falling back to gettimeofday() makes nstime_get() use the
* same clock the condvar waits on (CLOCK_REALTIME), so the deadline is honoured.
* This is a prerequisite for JEMALLOC_BACKGROUND_THREAD to work correctly on
* macOS. See https://github.com/ClickHouse/ClickHouse/issues/108429
*/
#define JEMALLOC_HAVE_MACH_ABSOLUTE_TIME
/* #undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME */
/*
* Defined if clock_gettime(CLOCK_REALTIME, ...) is available.