perf(index): run supervised index workers at below-normal CPU priority

A full index saturates every core in the parallel extraction pass, and
interactive CLI/MCP queries (CPU-bound at start-up) were measured taking
twice their wall time behind it with flat CPU time: pure run-queue
contention. The index-worker role now lowers its own priority once,
before any indexing thread is created: nice 10 on POSIX,
BELOW_NORMAL_PRIORITY_CLASS on Windows. It never raises priority: a
process already nicer than 10 (or in the IDLE / BELOW_NORMAL class) is
left alone. Plain nice only, not macOS background QoS, which would pin
indexing to efficiency cores and throttle its I/O. Failure is logged and
ignored.

The test runs the helper in a forked child and checks both legs: a
normal-priority child ends at nice >= 10, and a child started nicer than
that is left where it was.

Refs #2834

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
This commit is contained in:
Shane McCarron committed 2026-10-29 11:46:19 -05:00
1 parent a3831c9795
commit 349d80c643
4 files changed
+91

No files matched your search

+25
View File
@@ -729,3 +729,28 @@ const char *cbm_resolve_cache_dir(void) {
}
return buf;
}
/* ── CPU scheduling priority ────────────────── */
#ifndef _WIN32
#include <sys/resource.h>
#endif
bool cbm_lower_cpu_priority(void) {
#ifdef _WIN32
HANDLE self = GetCurrentProcess();
DWORD current = GetPriorityClass(self);
if (current == IDLE_PRIORITY_CLASS || current == BELOW_NORMAL_PRIORITY_CLASS) {
return true; /* already at least this low: never raise priority */
}
return SetPriorityClass(self, BELOW_NORMAL_PRIORITY_CLASS) != 0;
#else
/* getpriority can legitimately return -1, so errno is the only error signal. */
errno = 0;
int current = getpriority(PRIO_PROCESS, 0);
if (errno == 0 && current >= CBM_LOW_CPU_PRIORITY_NICE) {
return true; /* already at least this nice: never raise priority */
}
return setpriority(PRIO_PROCESS, 0, CBM_LOW_CPU_PRIORITY_NICE) == 0;
#endif
}
+13
View File
@@ -215,4 +215,17 @@ int64_t cbm_file_size(const char *path);
* On POSIX, this is a no-op. Returns the input pointer. */
char *cbm_normalize_path_sep(char *path);
/* ── CPU scheduling priority ───────────────────────────────────── */
enum { CBM_LOW_CPU_PRIORITY_NICE = 10 };
/* Lower this process's CPU scheduling priority so background work yields the
* cores to interactive processes. POSIX: nice CBM_LOW_CPU_PRIORITY_NICE (never
* raises an already-nicer process). Plain nice only, deliberately not macOS
* background QoS: that pins work to efficiency cores and throttles I/O.
* Windows: BELOW_NORMAL_PRIORITY_CLASS. On Linux nice is per thread, so call
* this before creating threads; later threads inherit it.
* Returns false when the priority could not be changed. */
bool cbm_lower_cpu_priority(void);
#endif /* CBM_PLATFORM_H */
+6
View File
@@ -902,6 +902,12 @@ static int run_cli(int argc, char **argv, cbm_project_lock_manager_t *project_lo
* own here, and only here (cbm_sqlite_dedicated_heap). */
cbm_sqlite_dedicated_heap(true);
cbm_memev_process_role("worker");
/* A full index saturates every core; run it below normal priority so
* interactive CLI/MCP queries are not starved. Before any indexing
* thread exists, so they all inherit it. Failure is harmless. */
if (!cbm_lower_cpu_priority()) {
cbm_log_info("index.worker.priority", "lowered", "false");
}
}
cbm_index_set_worker_role_options(index_worker, response_out, worker_single_thread,
worker_marker, worker_quarantine,
+47
View File
@@ -16,6 +16,9 @@
#include <unistd.h>
#ifndef _WIN32
#include <sys/stat.h> /* chmod for the symlink-policy mode legs */
#include <errno.h>
#include <sys/resource.h> /* getpriority for the worker-priority leg */
#include <sys/wait.h>
#endif
#ifdef __linux__
@@ -1442,6 +1445,49 @@ TEST(regex_estimate_counts_brackets_and_escapes_literally) {
PASS();
}
/* An index worker lowers its own priority (#2834). A non-root process cannot
* raise it back, so the helper runs in a forked child that reports its nice. */
TEST(platform_lower_cpu_priority_nices_the_process) {
#ifdef _WIN32
SKIP_PLATFORM("POSIX nice contract");
#else
int fds[2];
ASSERT_EQ(pipe(fds), 0);
pid_t child = fork();
if (child == 0) {
(void)close(fds[0]);
/* report: lowered ok, nice after; then, from a nicer start, ok, target, nice after */
int report[5] = {cbm_lower_cpu_priority() ? 1 : 0, 0, 0, 0, 0};
errno = 0;
report[1] = getpriority(PRIO_PROCESS, 0);
if (errno != 0) {
report[0] = 0;
}
/* Never-raise leg: start nicer than the helper's level; it must leave it. */
report[3] = report[1] >= 15 ? 19 : 15;
if (setpriority(PRIO_PROCESS, 0, report[3]) == 0) {
report[2] = cbm_lower_cpu_priority() ? 1 : 0;
errno = 0;
report[4] = getpriority(PRIO_PROCESS, 0);
}
_exit(write(fds[1], report, sizeof(report)) == (ssize_t)sizeof(report) ? 0 : 1);
}
ASSERT_TRUE(child > 0);
(void)close(fds[1]);
int report[5] = {0, 0, 0, 0, 0};
ssize_t got = read(fds[0], report, sizeof(report));
(void)close(fds[0]);
int status = 0;
(void)waitpid(child, &status, 0);
ASSERT_EQ(got, (ssize_t)sizeof(report));
ASSERT_EQ(report[0], 1);
ASSERT_TRUE(report[1] >= CBM_LOW_CPU_PRIORITY_NICE);
ASSERT_EQ(report[2], 1);
ASSERT_EQ(report[4], report[3]);
PASS();
#endif
}
SUITE(platform) {
RUN_TEST(platform_file_apis_survive_max_path_overflow);
RUN_TEST(platform_mkdir_p_follows_own_symlink_only_when_opted_in);
@@ -1477,6 +1523,7 @@ SUITE(platform) {
RUN_TEST(platform_env_long_refuses_what_it_cannot_read);
RUN_TEST(platform_env_dup_honours_the_settings_resolver);
RUN_TEST(platform_system_info);
RUN_TEST(platform_lower_cpu_priority_nices_the_process);
RUN_TEST(regex_compile_refuses_oversized_expansion);
RUN_TEST(regex_compile_accepts_expansion_under_budget);
RUN_TEST(regex_compile_refuses_overlong_pattern);