Skip to content

[store]feat: add conductor#2833

Merged
Chase-Rong merged 3 commits into
kvcache-ai:dev/conductorfrom
Chase-Rong:kv/conductor
Jul 12, 2026
Merged

[store]feat: add conductor#2833
Chase-Rong merged 3 commits into
kvcache-ai:dev/conductorfrom
Chase-Rong:kv/conductor

Conversation

@Chase-Rong

@Chase-Rong Chase-Rong commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Description

Add the initial C++ Mooncake Conductor service. Reference RFC:#977

This change introduces mooncake-conductor, including:

  • ZMQ subscribers for vLLM and Mooncake KV event streams, with replay support.
  • Msgpack event decoding for BlockStored / BlockRemoved events.
  • Prefix cache indexing and cache-hit query logic.
  • HTTP endpoints for query, register, unregister, services, and global view.
  • Standalone and top-level CMake integration via WITH_CONDUCTOR.
  • CI coverage for building and running conductor C++ unit tests.
  • Build documentation for the new conductor option.

Module

  • Transfer Engine (mooncake-transfer-engine)
  • Mooncake Store (mooncake-store)
  • Mooncake EP (mooncake-ep)
  • Mooncake PG (mooncake-pg)
  • Integration (mooncake-integration)
  • P2P Store (mooncake-p2p-store)
  • Python Wheel (mooncake-wheel)
  • Common (mooncake-common)
  • Mooncake RL (mooncake-rl)
  • CI/CD
  • Docs
  • Other

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Documentation update
  • Performance improvement
  • Other

How Has This Been Tested?

Added C++ unit tests for conductor config/util helpers, hash golden vectors, JSON uint64
handling, msgpack decoders, prefix indexer behavior, event manager HTTP/service flows,
ZMQ client behavior, and concurrency coverage.

Checklist

  • I have performed a self-review of my own code
  • I have formatted my code using ./scripts/code_format.sh
  • I have run pre-commit run --all-files and all hooks pass
  • I have updated the documentation (if applicable)
  • I have added tests to prove my changes are effective
  • For changes >500 LOC: I have filed an RFC issue

AI Assistance Disclosure

  • No AI tools were used
  • AI tools were used (specify below)

Codex was used to draft this PR description from the local HEAD commit. The human
submitter is responsible for reviewing and defending all changed lines.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the mooncake-conductor service, which coordinates and indexes KV cache metadata for Mooncake and vLLM instances. It includes a prefix indexer (PrefixCacheTable), a ZMQ client for event subscription, and an HTTP server for service registration and querying. The review highlights several critical issues, including a concurrency bug in GetContextData that can cause silent cache misses, a data race in Start() during concurrent service registration, an ineffective timeout loop in the HTTP server shutdown, and missing error checks during ZMQ client reconnection. Additionally, the message decoder overly restricts parent block hashes, and service subscriptions lack checks for a stopped manager state.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +82 to +93
auto [it, inserted] =
context_map_.try_emplace(model_context, new_context_data);
if (inserted) {
return new_context_data;
}
}

context_count_.fetch_add(1);
VLOG(1) << "in getContextData modelcontext model="
<< model_context.model_name
<< " instance=" << model_context.instance_id;
return new_context_data;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There is a critical concurrency bug in GetContextData. If the insertion fails because another thread has already inserted the context (inserted is false), the function still returns new_context_data (which is orphaned and not in the map) instead of the existing context data (it->second). Furthermore, context_count_ is incorrectly incremented only on duplicate/failed insertions instead of successful ones. This will lead to lost updates and silent cache misses.

Suggested change
auto [it, inserted] =
context_map_.try_emplace(model_context, new_context_data);
if (inserted) {
return new_context_data;
}
}
context_count_.fetch_add(1);
VLOG(1) << "in getContextData modelcontext model="
<< model_context.model_name
<< " instance=" << model_context.instance_id;
return new_context_data;
auto [it, inserted] =
context_map_.try_emplace(model_context, new_context_data);
if (inserted) {
context_count_.fetch_add(1);
VLOG(1) << "in getContextData modelcontext model="
<< model_context.model_name
<< " instance=" << model_context.instance_id;
return new_context_data;
}
return it->second;
}

Comment on lines +132 to +136
std::atomic<int> failure_count{0};
std::vector<std::thread> workers;
workers.reserve(services_.size());
for (const auto& svc : services_) {
workers.emplace_back([this, svc, &failure_count] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The Start() method iterates over services_ without holding any lock. Since the HTTP server is started before Start() is called, a concurrent /register request can modify services_ via services_.push_back(svc) under mu_, leading to a critical data race and undefined behavior. Copy services_ under a shared lock before iterating.

    std::vector<common::ServiceConfig> services_copy;
    {
        std::shared_lock lock(mu_);
        services_copy = services_;
    }

    std::atomic<int> failure_count{0};
    std::vector<std::thread> workers;
    workers.reserve(services_copy.size());
    for (const auto& svc : services_copy) {
        workers.emplace_back([this, svc, &failure_count] {

Comment on lines +176 to +190
std::thread shutdown_thread([this] {
LOG(INFO) << "Shutting down HTTP server";
http_server_->stop();
});
// 5-second timeout for forced shutdown
const auto deadline =
std::chrono::steady_clock::now() + std::chrono::seconds(5);
// stop() blocks until connections drain; poll for completion.
while (std::chrono::steady_clock::now() < deadline &&
shutdown_thread.joinable()) {
// join with timeout emulation: try a short join via native
// handle is non-portable; instead detach after deadline.
break;
}
shutdown_thread.join();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The HTTP server shutdown loop contains an immediate break; on the very first iteration. This makes the 5-second timeout completely ineffective, and the subsequent shutdown_thread.join() call will block the calling thread indefinitely if the server fails to stop or if connections do not drain. Consider using an atomic flag to poll the thread status and detaching the thread if the timeout is exceeded.

        std::atomic<bool> done{false};
        std::thread shutdown_thread([this, &done] {
            LOG(INFO) << "Shutting down HTTP server";
            http_server_->stop();
            done.store(true);
        });
        const auto deadline =
            std::chrono::steady_clock::now() + std::chrono::seconds(5);
        while (std::chrono::steady_clock::now() < deadline && !done.load()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        if (done.load()) {
            shutdown_thread.join();
        } else {
            LOG(WARNING) << "HTTP server shutdown timed out, detaching thread";
            shutdown_thread.detach();
        }

Comment on lines +114 to +117
if (auto err = Connect(); !err.empty()) {
LOG(ERROR) << "Reconnect failed service=" << config_.cache_pool_key
<< " error=" << err;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If Connect() fails during reconnection, the error is logged but the function still proceeds to request a replay. Since the sockets are not connected or are null, calling RequestReplay will fail or cause undefined behavior. Return early from HandleReconnect if the connection attempt fails.

Suggested change
if (auto err = Connect(); !err.empty()) {
LOG(ERROR) << "Reconnect failed service=" << config_.cache_pool_key
<< " error=" << err;
}
if (auto err = Connect(); !err.empty()) {
LOG(ERROR) << "Reconnect failed service=" << config_.cache_pool_key
<< " error=" << err;
return;
}

Comment on lines +546 to +550
} else if (f[2].type == object_type::POSITIVE_INTEGER &&
f[2].via.u64 > 0xFFFFFFFFULL) {
// Require a full 8-byte integer marker: minimal-width encoders
// only emit this width for values above 2**32.
event.parent_block_hash = f[2].via.u64;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The parser rejects any parent block hash that is less than or equal to 0xFFFFFFFFULL (2^32) with an expected integer error. Since parent block hashes can naturally be small integers (especially in test environments or with specific hash generation schemes), this check is overly restrictive and causes valid messages to be rejected. Msgpack-cxx automatically decodes integers of any size into via.u64, so checking f[2].type == object_type::POSITIVE_INTEGER is sufficient.

Suggested change
} else if (f[2].type == object_type::POSITIVE_INTEGER &&
f[2].via.u64 > 0xFFFFFFFFULL) {
// Require a full 8-byte integer marker: minimal-width encoders
// only emit this width for values above 2**32.
event.parent_block_hash = f[2].via.u64;
} else if (f[2].type == object_type::POSITIVE_INTEGER) {
event.parent_block_hash = f[2].via.u64;

Comment on lines +207 to +210
std::pair<bool, std::string> EventManager::SubscribeToService(
const common::ServiceConfig& svc) {
// Use (instance_id, tenant_id) as composite key to support
// multi-tenant replicas

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The SubscribeToService method does not check if the manager has been stopped. If a /register request is processed concurrently with or after Stop(), a new ZMQClient will be started and added to subscribers_, leaking background threads and resources. Check stopped_ and reject the subscription if the manager is stopped.

std::pair<bool, std::string> EventManager::SubscribeToService(
    const common::ServiceConfig& svc) {
    if (stopped_) {
        return {false, "manager is stopped"};
    }
    // Use (instance_id, tenant_id) as composite key to support
    // multi-tenant replicas

Comment on lines +54 to +55
LOG(WARNING) << "Config file does not exist, exiting. path="
<< config_path;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The log message says exiting when the config file does not exist, but the code actually returns an empty list and continues execution normally. Update the log message to accurately reflect that execution is continuing.

Suggested change
LOG(WARNING) << "Config file does not exist, exiting. path="
<< config_path;
LOG(WARNING) << "Config file does not exist, continuing with empty service list. path="
<< config_path;

@github-actions github-actions Bot added documentation Improvements or additions to documentation run-ci CI/Build labels Jul 10, 2026
@Chase-Rong Chase-Rong merged commit d3ebf38 into kvcache-ai:dev/conductor Jul 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/Build documentation Improvements or additions to documentation run-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant