Skip to content

[C API] Guard SVS_THREADPOOL_KIND_OMP case in threadpool.hpp behind #if SVS_OMP #399

Description

@eshenayo

Summary

At pin 5717f6855a73a9ab7524748e2a10a57ed865737d,
bindings/c/src/threadpool.hpp:161 references OMPThreadPool
unconditionally inside a switch on svs_threadpool_kind:

// bindings/c/src/threadpool.hpp:155-169 (approximate)
svs::threads::ThreadPoolHandle build() const {
    using namespace svs::threads;
    switch (kind) {
        case SVS_THREADPOOL_KIND_NATIVE:
            return ThreadPoolHandle(NativeThreadPool(num_threads));
        case SVS_THREADPOOL_KIND_OMP:
            return ThreadPoolHandle(OMPThreadPool(num_threads));   // <-- always compiled
        case SVS_THREADPOOL_KIND_SINGLE_THREAD:
            return ThreadPoolHandle(SequentialThreadPool());
        case SVS_THREADPOOL_KIND_CUSTOM:
            return ThreadPoolHandle(CustomThreadPool{user_ops_, user_self_});
        default:
            throw std::invalid_argument("Unknown svs_threadpool_kind value.");
    }
}

OMPThreadPool is defined at include/svs/lib/threads/threadpool.h:291-311
inside #if SVS_OMP. When the C API is compiled with SVS_OMP=0,
OMPThreadPool does not exist and this switch case fails to compile.
As a result, SVS_OMP=1 is currently required to build the C API,
which in turn forces libgomp.so.1 into the module's DT_NEEDED set
even when the caller never uses SVS_THREADPOOL_KIND_OMP at runtime.

This ask wraps the SVS_THREADPOOL_KIND_OMP case in #if SVS_OMP so
consumers who install a SVS_THREADPOOL_KIND_CUSTOM threadpool can
build without OpenMP entirely.

Motivation

valkey-search's SVS integration installs a custom threadpool
(SVS_THREADPOOL_KIND_CUSTOM) that returns size() = 1 and runs
parallel_for sequentially on the caller's reader thread. This is
the concurrency model documented in the valkey-search RFC and matches
HNSW's shape (throughput scales via concurrent searches at a higher
level, not multi-threading inside a single search). Under this model,
the OMPThreadPool code path is dead at runtime -- no omp_* symbol
is ever invoked -- but its symbols must still resolve at link time
because the switch case is unconditional.

Consequence: libgomp.so.1 appears in the module's DT_NEEDED and
must be added to ci/check_module_allocators.sh's ALLOWED_NEEDED
list. It is dead weight for our deployment: it adds a runtime
dependency the module never exercises.

Guarding the case behind #if SVS_OMP is a mechanical change that
lets us compile the C API with SVS_OMP=0 and drop the libgomp
dependency entirely.

Proposed change

Two-line edit in bindings/c/src/threadpool.hpp:

svs::threads::ThreadPoolHandle build() const {
    using namespace svs::threads;
    switch (kind) {
        case SVS_THREADPOOL_KIND_NATIVE:
            return ThreadPoolHandle(NativeThreadPool(num_threads));
#if SVS_OMP
        case SVS_THREADPOOL_KIND_OMP:
            return ThreadPoolHandle(OMPThreadPool(num_threads));
#endif
        case SVS_THREADPOOL_KIND_SINGLE_THREAD:
            return ThreadPoolHandle(SequentialThreadPool());
        case SVS_THREADPOOL_KIND_CUSTOM:
            return ThreadPoolHandle(CustomThreadPool{user_ops_, user_self_});
        default:
            throw std::invalid_argument("Unknown svs_threadpool_kind value.");
    }
}

Behavior when compiled with SVS_OMP=0:

  • SVS_THREADPOOL_KIND_OMP becomes an unknown value at runtime and
    falls through to the default clause, which already throws
    std::invalid_argument.
  • The enum value in the C API header can stay (backward-compatible),
    but callers passing it against an SVS_OMP=0 build will get a
    clear runtime error rather than a compile failure elsewhere.

Optionally, expose the compile-time OMP status via a macro or a
runtime probe (e.g., bool svs_has_omp_support(void)) so callers can
detect at runtime whether SVS_THREADPOOL_KIND_OMP is available.
Not strictly required.

Acceptance criteria

  1. svs_c_api builds with SVS_ENABLE_OMP=0 on Linux without
    compile errors. find_package(OpenMP REQUIRED) in the C API
    CMake becomes conditional on SVS_ENABLE_OMP.
  2. A built-with-SVS_OMP=0 library, when linked into a consumer,
    has no omp_* symbol references (nm grep clean).
  3. Runtime: svs_index_builder_set_threadpool(builder, SVS_THREADPOOL_KIND_OMP, ...)
    on a SVS_OMP=0 build returns a std::invalid_argument
    through the C-API's error handle (either via the default clause
    or an explicit "OMP support not compiled in" message).
  4. All other threadpool kinds (NATIVE, SINGLE_THREAD, CUSTOM) work
    identically to today's behavior under both SVS_OMP=0 and
    SVS_OMP=1.
  5. Existing tests in bindings/c/tests/ pass under both settings.

Non-goals

  • Removing OMP support entirely. Consumers who want native OMP
    parallelism inside SVS keep it via SVS_OMP=1.
  • Changing the NativeThreadPool behavior (which uses std::thread,
    not OpenMP).

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions