Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/foundation/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 13 additions & 0 deletions src/foundation/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 6 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
47 changes: 47 additions & 0 deletions tests/test_platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading