Files
lmcache--lmcache/examples/lmc_external_native_connector/csrc/connector.cpp
T
2026-07-13 12:24:33 +08:00

250 lines
8.1 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include "connector.h"
#include <fcntl.h>
#include <cerrno>
#include <cstdio>
#include <stdexcept>
#include <string>
namespace example_connector {
// ---------------------------------------------------------------
// Shared I/O helpers
// ---------------------------------------------------------------
static void write_all(int fd, const void* data, size_t len) {
size_t written = 0;
const char* ptr = static_cast<const char*>(data);
while (written < len) {
ssize_t n = ::write(fd, ptr + written, len - written);
if (n < 0) {
if (errno == EINTR) continue;
throw std::runtime_error("write failed: " + std::string(strerror(errno)));
}
if (n == 0) throw std::runtime_error("write returned 0");
written += static_cast<size_t>(n);
}
}
static size_t read_all(int fd, void* buf, size_t len) {
size_t total = 0;
char* ptr = static_cast<char*>(buf);
while (total < len) {
ssize_t n = ::read(fd, ptr + total, len - total);
if (n < 0) {
if (errno == EINTR) continue;
throw std::runtime_error("read failed: " + std::string(strerror(errno)));
}
if (n == 0) break;
total += static_cast<size_t>(n);
}
return total;
}
// ---------------------------------------------------------------
// ExampleFSConnector
// ---------------------------------------------------------------
// Key encoding constants — must match fs_l2_adapter.py
static constexpr char KEY_SEP = '@';
static constexpr const char* PATH_SLASH_REPLACEMENT = "-SEP-";
static constexpr const char* FILE_EXT = ".data";
std::string ExampleFSConnector::replace_all(const std::string& str,
const std::string& from,
const std::string& to) {
std::string result = str;
size_t pos = 0;
while ((pos = result.find(from, pos)) != std::string::npos) {
result.replace(pos, from.size(), to);
pos += to.size();
}
return result;
}
std::string ExampleFSConnector::safe_filename(const std::string& key) {
// Input key format (from _object_key_to_string):
// Unsalted: <model_name>@<kv_rank_hex>@<chunk_hash_hex>
// Salted : <model_name>@<kv_rank_hex>@<chunk_hash_hex>@<cache_salt>
//
// Output filename:
// Unsalted: <model_name_safe>@0x<kv_rank_hex>@<chunk_hash_hex>.data
// Salted :
// <model_name_safe>@0x<kv_rank_hex>@<chunk_hash_hex>@<cache_salt>.data
//
// Both model_name and cache_salt are forbidden from containing '@'
// (invariant enforced on the Python side), so splitting on '@' is
// unambiguous.
std::vector<std::string> parts;
size_t start = 0;
for (size_t pos = 0; pos <= key.size(); ++pos) {
if (pos == key.size() || key[pos] == KEY_SEP) {
parts.emplace_back(key.substr(start, pos - start));
start = pos + 1;
}
}
if (parts.size() != 3 && parts.size() != 4) {
throw std::runtime_error(
"ExampleFSConnector: malformed key "
"(expected 3 or 4 '@'-separated fields): " +
key);
}
const std::string& model = parts[0];
const std::string& kv_rank_hex = parts[1];
const std::string& chunk_hash = parts[2];
const std::string cache_salt = parts.size() == 4 ? parts[3] : std::string();
// Replace '/' with '-SEP-' for filesystem safety
std::string safe_model = replace_all(model, "/", PATH_SLASH_REPLACEMENT);
std::string result;
result.reserve(safe_model.size() + kv_rank_hex.size() + chunk_hash.size() +
cache_salt.size() + 32);
result += safe_model;
result += KEY_SEP;
result += "0x";
result += kv_rank_hex;
result += KEY_SEP;
result += chunk_hash;
if (!cache_salt.empty()) {
result += KEY_SEP;
result += cache_salt;
}
result += FILE_EXT;
return result;
}
ExampleFSConnector::ExampleFSConnector(std::string base_path, int num_workers)
: ConnectorBase(num_workers), base_path_(std::move(base_path)) {
std::filesystem::create_directories(base_path_);
start_workers();
}
ExampleFSConnector::~ExampleFSConnector() { close(); }
WorkerFSConn ExampleFSConnector::create_connection() {
WorkerFSConn conn;
conn.base_path = base_path_;
return conn;
}
void ExampleFSConnector::do_single_get(WorkerFSConn& conn,
const std::string& key, void* buf,
size_t len, size_t /*chunk_size*/) {
auto path = conn.base_path / safe_filename(key);
int fd = ::open(path.c_str(), O_RDONLY);
if (fd < 0) {
throw std::runtime_error("open for read failed: " + path.string() + ": " +
strerror(errno));
}
size_t n = read_all(fd, buf, len);
::close(fd);
if (n != len) {
throw std::runtime_error("incomplete read for " + path.string());
}
}
void ExampleFSConnector::do_single_set(WorkerFSConn& conn,
const std::string& key, const void* buf,
size_t len, size_t /*chunk_size*/) {
auto file_path = conn.base_path / safe_filename(key);
if (std::filesystem::exists(file_path)) return;
auto tmp_path = file_path;
tmp_path.replace_extension(".tmp");
int fd = ::open(tmp_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
throw std::runtime_error("open for write failed: " + tmp_path.string() +
": " + strerror(errno));
}
try {
write_all(fd, buf, len);
} catch (...) {
::close(fd);
std::filesystem::remove(tmp_path);
throw;
}
::close(fd);
std::error_code ec;
std::filesystem::rename(tmp_path, file_path, ec);
if (ec) {
std::error_code remove_ec;
std::filesystem::remove(tmp_path, remove_ec);
throw std::runtime_error("rename failed: " + ec.message());
}
}
bool ExampleFSConnector::do_single_exists(WorkerFSConn& conn,
const std::string& key) {
auto path = conn.base_path / safe_filename(key);
return std::filesystem::exists(path);
}
bool ExampleFSConnector::do_single_delete(WorkerFSConn& conn,
const std::string& key) {
auto path = conn.base_path / safe_filename(key);
std::error_code ec;
return std::filesystem::remove(path, ec);
}
// ---------------------------------------------------------------
// ExampleMemoryConnector
// ---------------------------------------------------------------
ExampleMemoryConnector::ExampleMemoryConnector(int num_workers)
: ConnectorBase(num_workers),
store_(std::make_shared<SharedMemoryStore>()) {
start_workers();
}
ExampleMemoryConnector::~ExampleMemoryConnector() { close(); }
WorkerMemConn ExampleMemoryConnector::create_connection() {
WorkerMemConn conn;
conn.store = store_;
return conn;
}
void ExampleMemoryConnector::do_single_get(WorkerMemConn& conn,
const std::string& key, void* buf,
size_t len, size_t /*chunk_size*/) {
std::lock_guard<std::mutex> lk(conn.store->mu);
auto it = conn.store->data.find(key);
if (it == conn.store->data.end()) {
throw std::runtime_error("key not found: " + key);
}
if (it->second.size() != len) {
throw std::runtime_error("size mismatch for key: " + key);
}
std::memcpy(buf, it->second.data(), len);
}
void ExampleMemoryConnector::do_single_set(WorkerMemConn& conn,
const std::string& key,
const void* buf, size_t len,
size_t /*chunk_size*/) {
std::lock_guard<std::mutex> lk(conn.store->mu);
auto& vec = conn.store->data[key];
vec.resize(len);
std::memcpy(vec.data(), buf, len);
}
bool ExampleMemoryConnector::do_single_exists(WorkerMemConn& conn,
const std::string& key) {
std::lock_guard<std::mutex> lk(conn.store->mu);
return conn.store->data.count(key) > 0;
}
bool ExampleMemoryConnector::do_single_delete(WorkerMemConn& conn,
const std::string& key) {
std::lock_guard<std::mutex> lk(conn.store->mu);
return conn.store->data.erase(key) > 0;
}
} // namespace example_connector