Skip to content

PromRegistry::SeriesFor returns a reference into a vector it reallocates: sanitizer lanes are RED on main #330

Description

@localai-bot

Both sanitizer lanes are red on main, not on any one PR

Reproduced on pristine origin/main d06a3792 with no other commits applied:

cmake -S . -B build-sanitize -G Ninja -DVLLM_CPP_BUILD_TESTS=ON \
  -DVLLM_CPP_CUDA=OFF -DVLLM_CPP_SANITIZE=thread
cmake --build build-sanitize --target test_llm_engine
setarch $(uname -m) -R ./build-sanitize/tests/test_llm_engine    # exit 66

(setarch -R is needed locally: TSan otherwise aborts with unexpected memory mapping before running a single test.)

WARNING: ThreadSanitizer: data race
    #8 PromRegistry::SeriesFor(...)  src/vllm/v1/metrics/prometheus.cpp:134
    #9 PromRegistry::SetGauge(...)   src/vllm/v1/metrics/prometheus.cpp:165
SUMMARY: ThreadSanitizer: data race in operator delete(void*, unsigned long)

CI agrees: on PR #307 the ASan lane fails six tests — test_load_direct_upload, test_llama_embedding_fold, test_laguna_nvfp4_loader, test_llm_engine, test_openai_api_server, test_capi — with heap-use-after-free.

Root cause

SeriesFor hands out a reference into a std::vector<Series> and then grows that same vector:

PromRegistry::Series& PromRegistry::SeriesFor(Family& fam, const std::vector<std::string>& labelvalues) {
  for (auto& s : fam.series) {
    if (s.labelvalues == labelvalues) return s;
  }
  ...
  fam.series.push_back(std::move(s));   // reallocates: every previously returned Series& dangles
  return fam.series.back();
}

std::vector reallocation invalidates all references to its elements, so any Series& obtained before a later SeriesFor call on the same family is dangling — that is the heap-use-after-free. Callers (IncCounter, SetGauge, Observe, Prime) all go through it.

Separately, PromRegistry contains no mutex or atomic at all (grep -c 'mutex|lock_guard|atomic' = 0 in both the header and the .cpp), while /metrics is now served concurrently with the engine's own recording. A scrape iterating fam.series while a writer push_backs is a data race on the container itself, independent of the dangling-reference bug.

Suggested direction

Stability of references is the actual requirement, so a container that provides it (std::deque, or std::vector<std::unique_ptr<Series>>, or returning an index rather than a reference), plus a lock or per-series atomics around the concurrent recording path. Not attempted here — this belongs to the metrics row, not the one that found it.

Provenance

Bisected by elimination, not assumed: PR #307's spec-only commit c3db7804 passed both sanitizer lanes; the failures appear only after merges that pulled in 9c55e3df ("wire /metrics to the engine the server actually serves from", #277), and then reproduce on pristine main with none of #307 applied. Found while gating container images (#170).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions