#include "pybind_client.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <numa.h>
#include <pthread.h>
#include <signal.h>
#include <thread>
#include <stop_token>
#include <cstdlib>
#include <optional>
#include "client_buffer.hpp"
#include "config.h"
#include "mutex.h"
#include "types.h"
#include "utils.h"
#ifdef USE_UB
#include "transport/ub_transport/memfabric_smem_dl.h"
#endif
namespace mooncake {
ResourceTracker &ResourceTracker::getInstance() {
static ResourceTracker *instance = new ResourceTracker();
return *instance;
}
ResourceTracker::ResourceTracker() {
startSignalThread();
std::atexit(exitHandler);
}
ResourceTracker::~ResourceTracker() {
}
void ResourceTracker::registerInstance(
const std::shared_ptr<PyClient> &instance) {
MutexLocker locker(&mutex_);
instances_.push_back(instance);
}
void ResourceTracker::cleanupAllResources() {
bool expected = false;
if (!cleaned_.compare_exchange_strong(expected, true,
std::memory_order_acq_rel)) {
return;
}
MutexLocker locker(&mutex_);
for (auto &wp : instances_) {
if (auto sp = wp.lock()) {
LOG(INFO) << "Cleaning up DistributedObjectStore instance";
sp->tearDownAll();
}
}
}
void ResourceTracker::signalHandler(int signal) {
getInstance().cleanupAllResources();
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signal, &sa, nullptr);
raise(signal);
}
void ResourceTracker::exitHandler() { getInstance().cleanupAllResources(); }
void ResourceTracker::startSignalThread() {
std::call_once(signal_once_, [this]() {
std::promise<void> ready;
auto ready_future = ready.get_future();
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
signal_thread_ = std::jthread(
[set, ready = std::move(ready)](std::stop_token st) mutable {
pthread_t self = pthread_self();
std::stop_callback cb(
st, [self]() { pthread_kill(self, SIGUSR1); });
ready.set_value();
for (;;) {
int sig = 0;
int rc = sigwait(&set, &sig);
if (rc != 0) {
LOG(ERROR) << "sigwait failed: " << strerror(rc);
continue;
}
if (sig == SIGUSR1) {
if (st.stop_requested()) {
break;
}
continue;
}
LOG(INFO) << "Received signal " << sig
<< ", cleaning up resources";
ResourceTracker::getInstance().cleanupAllResources();
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(sig, &sa, nullptr);
raise(sig);
break;
}
});
ready_future.wait();
});
}
PyClient::PyClient() {
easylog::set_min_severity(easylog::Severity::WARN);
}
PyClient::~PyClient() {
tearDownAll_internal();
}
std::shared_ptr<PyClient> PyClient::create() {
auto sp = std::shared_ptr<PyClient>(new PyClient());
ResourceTracker::getInstance().registerInstance(sp);
return sp;
}
tl::expected<void, ErrorCode> PyClient::setup_internal(
const std::string &local_hostname, const std::string &metadata_server,
size_t global_segment_size, size_t local_buffer_size,
const std::string &protocol, const std::string &rdma_devices,
const std::string &master_server_addr,
const std::shared_ptr<TransferEngine> &transfer_engine) {
this->protocol = protocol;
std::string hostname = local_hostname;
size_t colon_pos = hostname.find(":");
if (colon_pos == std::string::npos) {
port_binder_ = std::make_unique<AutoPortBinder>();
int port = port_binder_->getPort();
if (port < 0) {
LOG(ERROR) << "Failed to bind available port";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
this->local_hostname = hostname + ":" + std::to_string(port);
} else {
this->local_hostname = local_hostname;
}
std::optional<std::string> device_name =
(rdma_devices.empty() ? std::nullopt
: std::make_optional(rdma_devices));
#ifdef USE_UB
MemFabricSmemDl::SetSmemTypeFlag(SMEM_BM);
#endif
auto client_opt = mooncake::Client::Create(
this->local_hostname, metadata_server, protocol, device_name,
master_server_addr, transfer_engine);
if (!client_opt) {
LOG(ERROR) << "Failed to create client";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
client_ = *client_opt;
#ifdef USE_UB
std::pair<void *, size_t> segment = MemFabricGetSegment();
if (segment.second == 0) {
LOG(INFO) << "Skip to mount segment, size is zero";
return {};
}
auto mountRes = client_->MountSegment(segment.first, segment.second);
if (!mountRes.has_value()) {
LOG(ERROR) << "Failed to mount segment: " << toString(mountRes.error());
return tl::unexpected(mountRes.error());
}
LOG(INFO) << "Success to mount segment dram{" << std::hex << segment.first << " " << segment.second
<< "}";
return {};
#endif
client_buffer_allocator_ =
ClientBufferAllocator::create(local_buffer_size, this->protocol);
if (local_buffer_size > 0) {
auto result = client_->RegisterLocalMemory(
client_buffer_allocator_->getBase(), local_buffer_size,
kWildcardLocation, false, true);
if (!result.has_value()) {
LOG(ERROR) << "Failed to register local memory: "
<< toString(result.error());
return tl::unexpected(result.error());
}
} else {
LOG(INFO) << "Local buffer size is 0, skip registering local memory";
}
auto max_mr_size = globalConfig().max_mr_size;
uint64_t total_glbseg_size = global_segment_size;
uint64_t current_glbseg_size = 0;
while (global_segment_size > 0) {
size_t segment_size = std::min(global_segment_size, max_mr_size);
global_segment_size -= segment_size;
current_glbseg_size += segment_size;
LOG(INFO) << "Mounting segment: " << segment_size << " bytes, "
<< current_glbseg_size << " of " << total_glbseg_size;
void *ptr =
allocate_buffer_allocator_memory(segment_size, this->protocol);
if (!ptr) {
LOG(ERROR) << "Failed to allocate segment memory";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (this->protocol == "ascend") {
ascend_segment_ptrs_.emplace_back(ptr);
} else {
segment_ptrs_.emplace_back(ptr);
}
auto mount_result = client_->MountSegment(ptr, segment_size);
if (!mount_result.has_value()) {
LOG(ERROR) << "Failed to mount segment: "
<< toString(mount_result.error());
return tl::unexpected(mount_result.error());
}
}
if (total_glbseg_size == 0) {
LOG(INFO) << "Global segment size is 0, skip mounting segment";
}
return {};
}
int PyClient::setup(const std::string &local_hostname,
const std::string &metadata_server,
size_t global_segment_size, size_t local_buffer_size,
const std::string &protocol,
const std::string &rdma_devices,
const std::string &master_server_addr,
const std::shared_ptr<TransferEngine> &transfer_engine) {
return to_py_ret(setup_internal(
local_hostname, metadata_server, global_segment_size, local_buffer_size,
protocol, rdma_devices, master_server_addr, transfer_engine));
}
tl::expected<void, ErrorCode> PyClient::initAll_internal(
const std::string &protocol_, const std::string &device_name,
size_t mount_segment_size) {
if (client_) {
LOG(ERROR) << "Client is already initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
uint64_t buffer_allocator_size = 1024 * 1024 * 1024;
return setup_internal("localhost:12345", "127.0.0.1:2379",
mount_segment_size, buffer_allocator_size, protocol_,
device_name);
}
int PyClient::initAll(const std::string &protocol_,
const std::string &device_name,
size_t mount_segment_size) {
return to_py_ret(
initAll_internal(protocol_, device_name, mount_segment_size));
}
tl::expected<void, ErrorCode> PyClient::tearDownAll_internal() {
bool expected = false;
if (!closed_.compare_exchange_strong(expected, true,
std::memory_order_acq_rel)) {
return {};
}
if (!client_) {
return {};
}
client_.reset();
client_buffer_allocator_.reset();
port_binder_.reset();
segment_ptrs_.clear();
local_hostname = "";
device_name = "";
protocol = "";
return {};
}
int PyClient::tearDownAll() { return to_py_ret(tearDownAll_internal()); }
tl::expected<void, ErrorCode> PyClient::put_internal(
const std::string &key, std::span<const char> value,
const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto alloc_result = client_buffer_allocator_->allocate(value.size_bytes());
if (!alloc_result) {
LOG(ERROR) << "Failed to allocate buffer for put operation, key: "
<< key << ", value size: " << value.size();
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto &buffer_handle = *alloc_result;
memcpy(buffer_handle.ptr(), value.data(), value.size_bytes());
std::vector<Slice> slices = split_into_slices(buffer_handle);
auto put_result = client_->Put(key, slices, config);
if (!put_result) {
return tl::unexpected(put_result.error());
}
return {};
}
int PyClient::put(const std::string &key, std::span<const char> value,
const ReplicateConfig &config) {
return to_py_ret(put_internal(key, value, config));
}
tl::expected<void, ErrorCode> PyClient::put_batch_internal(
const std::vector<std::string> &keys,
const std::vector<std::span<const char>> &values,
const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (keys.size() != values.size()) {
LOG(ERROR) << "Key and value size mismatch";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
std::vector<BufferHandle> buffer_handles;
std::unordered_map<std::string, std::vector<Slice>> batched_slices;
batched_slices.reserve(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
auto &key = keys[i];
auto &value = values[i];
auto alloc_result =
client_buffer_allocator_->allocate(value.size_bytes());
if (!alloc_result) {
LOG(ERROR)
<< "Failed to allocate buffer for put_batch operation, key: "
<< key << ", value size: " << value.size();
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto &buffer_handle = *alloc_result;
memcpy(buffer_handle.ptr(), value.data(), value.size_bytes());
auto slices = split_into_slices(buffer_handle);
buffer_handles.emplace_back(std::move(*alloc_result));
batched_slices.emplace(key, std::move(slices));
}
std::vector<std::vector<mooncake::Slice>> ordered_batched_slices;
ordered_batched_slices.reserve(keys.size());
for (const auto &key : keys) {
auto it = batched_slices.find(key);
if (it != batched_slices.end()) {
ordered_batched_slices.emplace_back(it->second);
} else {
LOG(ERROR) << "Missing slices for key: " << key;
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
}
auto results = client_->BatchPut(keys, ordered_batched_slices, config);
for (size_t i = 0; i < results.size(); ++i) {
if (!results[i]) {
return tl::unexpected(results[i].error());
}
}
return {};
}
int PyClient::put_batch(const std::vector<std::string> &keys,
const std::vector<std::span<const char>> &values,
const ReplicateConfig &config) {
return to_py_ret(put_batch_internal(keys, values, config));
}
tl::expected<void, ErrorCode> PyClient::put_parts_internal(
const std::string &key, std::vector<std::span<const char>> values,
const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
size_t total_size = 0;
for (const auto &value : values) {
total_size += value.size_bytes();
}
if (total_size == 0) {
LOG(WARNING) << "Attempting to put empty data for key: " << key;
return {};
}
auto alloc_result = client_buffer_allocator_->allocate(total_size);
if (!alloc_result) {
LOG(ERROR) << "Failed to allocate buffer for put_parts operation, key: "
<< key << ", total size: " << total_size;
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto &buffer_handle = *alloc_result;
size_t offset = 0;
for (const auto &value : values) {
memcpy(static_cast<char *>(buffer_handle.ptr()) + offset, value.data(),
value.size_bytes());
offset += value.size_bytes();
}
std::vector<Slice> slices = split_into_slices(buffer_handle);
auto put_result = client_->Put(key, slices, config);
if (!put_result) {
LOG(ERROR) << "Put operation failed with error: "
<< toString(put_result.error());
return tl::unexpected(put_result.error());
}
return {};
}
int PyClient::put_parts(const std::string &key,
std::vector<std::span<const char>> values,
const ReplicateConfig &config) {
return to_py_ret(put_parts_internal(key, values, config));
}
tl::expected<void, ErrorCode> PyClient::remove_internal(
const std::string &key) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto remove_result = client_->Remove(key);
if (!remove_result) {
return tl::unexpected(remove_result.error());
}
return {};
}
int PyClient::remove(const std::string &key) {
return to_py_ret(remove_internal(key));
}
tl::expected<long, ErrorCode> PyClient::removeByRegex_internal(
const std::string &str) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
return client_->RemoveByRegex(str);
}
long PyClient::removeByRegex(const std::string &str) {
return to_py_ret(removeByRegex_internal(str));
}
tl::expected<int64_t, ErrorCode> PyClient::removeAll_internal() {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
return client_->RemoveAll();
}
long PyClient::removeAll() { return to_py_ret(removeAll_internal()); }
tl::expected<bool, ErrorCode> PyClient::isExist_internal(
const std::string &key) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
return client_->IsExist(key);
}
int PyClient::isExist(const std::string &key) {
auto result = isExist_internal(key);
if (result.has_value()) {
return *result ? 1 : 0;
} else {
return toInt(result.error());
}
}
std::vector<int> PyClient::batchIsExist(const std::vector<std::string> &keys) {
auto internal_results = batchIsExist_internal(keys);
std::vector<int> results;
results.reserve(internal_results.size());
for (const auto &result : internal_results) {
if (result.has_value()) {
results.push_back(result.value() ? 1 : 0);
} else {
results.push_back(toInt(result.error()));
}
}
return results;
}
tl::expected<int64_t, ErrorCode> PyClient::getSize_internal(
const std::string &key) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto query_result = client_->Query(key);
if (!query_result) {
return tl::unexpected(query_result.error());
}
const std::vector<Replica::Descriptor> &replica_list =
query_result.value().replicas;
int64_t total_size = 0;
if (!replica_list.empty()) {
auto &replica = replica_list[0];
total_size = calculate_total_size(replica);
} else {
LOG(ERROR) << "Internal error: replica_list is empty";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
return total_size;
}
int64_t PyClient::getSize(const std::string &key) {
return to_py_ret(getSize_internal(key));
}
std::shared_ptr<BufferHandle> PyClient::get_buffer(const std::string &key) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return nullptr;
}
auto query_result = client_->Query(key);
if (!query_result) {
if (query_result.error() == ErrorCode::OBJECT_NOT_FOUND ||
query_result.error() == ErrorCode::REPLICA_IS_NOT_READY) {
return nullptr;
}
LOG(ERROR) << "Query failed for key: " << key
<< " with error: " << toString(query_result.error());
return nullptr;
}
const std::vector<Replica::Descriptor> &replica_list =
query_result.value().replicas;
if (replica_list.empty()) {
LOG(ERROR) << "Empty replica list for key: " << key;
return nullptr;
}
const auto &replica = replica_list[0];
uint64_t total_length = calculate_total_size(replica);
if (total_length == 0) {
return nullptr;
}
auto alloc_result = client_buffer_allocator_->allocate(total_length);
if (!alloc_result) {
LOG(ERROR) << "Failed to allocate buffer for get_buffer, key: " << key;
return nullptr;
}
auto &buffer_handle = *alloc_result;
std::vector<Slice> slices;
allocateSlices(slices, replica, buffer_handle);
auto get_result = client_->Get(key, query_result.value(), slices);
if (!get_result) {
LOG(ERROR) << "Get failed for key: " << key
<< " with error: " << toString(get_result.error());
return nullptr;
}
return std::make_shared<BufferHandle>(std::move(buffer_handle));
}
std::vector<std::shared_ptr<BufferHandle>> PyClient::batch_get_buffer_internal(
const std::vector<std::string> &keys) {
std::vector<std::shared_ptr<BufferHandle>> final_results(keys.size(),
nullptr);
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return final_results;
}
if (keys.empty()) {
return final_results;
}
auto query_results = client_->BatchQuery(keys);
struct KeyOp {
size_t original_index;
std::string key;
QueryResult query_result;
std::unique_ptr<BufferHandle> buffer_handle;
std::vector<Slice> slices;
};
std::vector<KeyOp> valid_ops;
valid_ops.reserve(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
const auto &key = keys[i];
if (!query_results[i]) {
if (query_results[i].error() != ErrorCode::OBJECT_NOT_FOUND &&
query_results[i].error() != ErrorCode::REPLICA_IS_NOT_READY) {
LOG(ERROR) << "Query failed for key '" << key
<< "': " << toString(query_results[i].error());
}
continue;
}
auto query_result_values = query_results[i].value();
if (query_result_values.replicas.empty()) {
LOG(ERROR) << "Empty replica list for key: " << key;
continue;
}
const auto &replica = query_result_values.replicas[0];
uint64_t total_size = calculate_total_size(replica);
if (total_size == 0) {
continue;
}
auto alloc_result = client_buffer_allocator_->allocate(total_size);
if (!alloc_result) {
LOG(ERROR) << "Failed to allocate buffer for key: " << key;
continue;
}
auto buffer_handle =
std::make_unique<BufferHandle>(std::move(*alloc_result));
std::vector<Slice> slices;
allocateSlices(slices, replica, *buffer_handle);
valid_ops.emplace_back(
KeyOp{.original_index = i,
.key = key,
.query_result = std::move(query_result_values),
.buffer_handle = std::move(buffer_handle),
.slices = std::move(slices)});
}
if (valid_ops.empty()) {
return final_results;
}
std::vector<std::string> batch_keys;
std::vector<QueryResult> batch_query_results;
std::unordered_map<std::string, std::vector<Slice>> batch_slices;
batch_keys.reserve(valid_ops.size());
batch_query_results.reserve(valid_ops.size());
for (auto &op : valid_ops) {
batch_keys.push_back(op.key);
batch_query_results.push_back(op.query_result);
batch_slices[op.key] = op.slices;
}
auto batch_get_results =
client_->BatchGet(batch_keys, batch_query_results, batch_slices);
for (size_t i = 0; i < valid_ops.size(); ++i) {
if (batch_get_results[i]) {
auto &op = valid_ops[i];
final_results[op.original_index] =
std::make_shared<BufferHandle>(std::move(*op.buffer_handle));
} else {
LOG(ERROR) << "BatchGet failed for key '" << valid_ops[i].key
<< "': " << toString(batch_get_results[i].error());
}
}
return final_results;
}
std::vector<std::shared_ptr<BufferHandle>> PyClient::batch_get_buffer(
const std::vector<std::string> &keys) {
return batch_get_buffer_internal(keys);
}
tl::expected<void, ErrorCode> PyClient::register_buffer_internal(void *buffer,
size_t size) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
return client_->RegisterLocalMemory(buffer, size, kWildcardLocation, false,
true);
}
int PyClient::register_buffer(void *buffer, size_t size) {
return to_py_ret(register_buffer_internal(buffer, size));
}
tl::expected<void, ErrorCode> PyClient::unregister_buffer_internal(
void *buffer) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto unregister_result = client_->unregisterLocalMemory(buffer, true);
if (!unregister_result) {
LOG(ERROR) << "Unregister buffer failed with error: "
<< toString(unregister_result.error());
return tl::unexpected(unregister_result.error());
}
return {};
}
int PyClient::unregister_buffer(void *buffer) {
return to_py_ret(unregister_buffer_internal(buffer));
}
tl::expected<int64_t, ErrorCode> PyClient::get_into_internal(
const std::string &key, void *buffer, size_t size) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto query_result = client_->Query(key);
if (!query_result) {
if (query_result.error() == ErrorCode::OBJECT_NOT_FOUND ||
query_result.error() == ErrorCode::REPLICA_IS_NOT_READY) {
VLOG(1) << "Object not found for key: " << key;
return tl::unexpected(query_result.error());
}
LOG(ERROR) << "Query failed for key: " << key
<< " with error: " << toString(query_result.error());
return tl::unexpected(query_result.error());
}
const std::vector<Replica::Descriptor> &replica_list =
query_result.value().replicas;
if (replica_list.empty()) {
LOG(ERROR) << "Internal error: replica_list is empty";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
auto &replica = replica_list[0];
uint64_t total_size = calculate_total_size(replica);
if (size < total_size) {
LOG(ERROR) << "User buffer too small. Required: " << total_size
<< ", provided: " << size;
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
std::vector<mooncake::Slice> slices;
uint64_t offset = 0;
if (replica.is_memory_replica() == false) {
while (offset < total_size) {
auto chunk_size = std::min(total_size - offset, kMaxSliceSize);
void *chunk_ptr = static_cast<char *>(buffer) + offset;
slices.emplace_back(Slice{chunk_ptr, chunk_size});
offset += chunk_size;
}
} else {
for (auto &handle :
replica.get_memory_descriptor().buffer_descriptors) {
void *chunk_ptr = static_cast<char *>(buffer) + offset;
slices.emplace_back(Slice{chunk_ptr, handle.size_});
offset += handle.size_;
}
}
auto get_result = client_->Get(key, query_result.value(), slices);
if (!get_result) {
LOG(ERROR) << "Get failed for key: " << key
<< " with error: " << toString(get_result.error());
return tl::unexpected(get_result.error());
}
return static_cast<int64_t>(total_size);
}
int64_t PyClient::get_into(const std::string &key, void *buffer, size_t size) {
return to_py_ret(get_into_internal(key, buffer, size));
}
std::string PyClient::get_hostname() const { return local_hostname; }
std::vector<int> PyClient::batch_put_from(const std::vector<std::string> &keys,
const std::vector<void *> &buffers,
const std::vector<size_t> &sizes,
const ReplicateConfig &config) {
auto internal_results =
batch_put_from_internal(keys, buffers, sizes, config);
std::vector<int> results;
results.reserve(internal_results.size());
for (const auto &result : internal_results) {
results.push_back(to_py_ret(result));
}
return results;
}
std::vector<tl::expected<void, ErrorCode>> PyClient::batch_put_from_internal(
const std::vector<std::string> &keys, const std::vector<void *> &buffers,
const std::vector<size_t> &sizes, const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if (keys.size() != buffers.size() || keys.size() != sizes.size()) {
LOG(ERROR) << "Mismatched sizes for keys, buffers, and sizes";
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
std::unordered_map<std::string, std::vector<mooncake::Slice>> all_slices;
for (size_t i = 0; i < keys.size(); ++i) {
const std::string &key = keys[i];
void *buffer = buffers[i];
size_t size = sizes[i];
std::vector<mooncake::Slice> slices;
uint64_t offset = 0;
while (offset < size) {
auto chunk_size = std::min(size - offset, kMaxSliceSize);
void *chunk_ptr = static_cast<char *>(buffer) + offset;
slices.emplace_back(Slice{chunk_ptr, chunk_size});
offset += chunk_size;
}
all_slices[key] = std::move(slices);
}
std::vector<std::vector<mooncake::Slice>> ordered_batched_slices;
ordered_batched_slices.reserve(keys.size());
for (const auto &key : keys) {
auto it = all_slices.find(key);
if (it != all_slices.end()) {
ordered_batched_slices.emplace_back(it->second);
} else {
LOG(ERROR) << "Missing slices for key: " << key;
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
}
return client_->BatchPut(keys, ordered_batched_slices, config);
}
tl::expected<void, ErrorCode> PyClient::put_from_internal(
const std::string &key, void *buffer, size_t size,
const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
}
if (size == 0) {
LOG(WARNING) << "Attempting to put empty data for key: " << key;
return {};
}
std::vector<mooncake::Slice> slices;
uint64_t offset = 0;
while (offset < size) {
auto chunk_size = std::min(size - offset, kMaxSliceSize);
void *chunk_ptr = static_cast<char *>(buffer) + offset;
slices.emplace_back(Slice{chunk_ptr, chunk_size});
offset += chunk_size;
}
auto put_result = client_->Put(key, slices, config);
if (!put_result) {
return tl::unexpected(put_result.error());
}
return {};
}
int PyClient::put_from(const std::string &key, void *buffer, size_t size,
const ReplicateConfig &config) {
return to_py_ret(put_from_internal(key, buffer, size, config));
}
std::vector<int64_t> PyClient::batch_get_into(
const std::vector<std::string> &keys, const std::vector<void *> &buffers,
const std::vector<size_t> &sizes) {
auto internal_results = batch_get_into_internal(keys, buffers, sizes);
std::vector<int64_t> results;
results.reserve(internal_results.size());
for (const auto &result : internal_results) {
results.push_back(to_py_ret(result));
}
return results;
}
std::vector<tl::expected<int64_t, ErrorCode>> PyClient::batch_get_into_internal(
const std::vector<std::string> &keys, const std::vector<void *> &buffers,
const std::vector<size_t> &sizes) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return std::vector<tl::expected<int64_t, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if (keys.size() != buffers.size() || keys.size() != sizes.size()) {
LOG(ERROR) << "Input vector sizes mismatch: keys=" << keys.size()
<< ", buffers=" << buffers.size()
<< ", sizes=" << sizes.size();
return std::vector<tl::expected<int64_t, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
const size_t num_keys = keys.size();
std::vector<tl::expected<int64_t, ErrorCode>> results;
results.reserve(num_keys);
if (num_keys == 0) {
return results;
}
const auto query_results = client_->BatchQuery(keys);
struct ValidKeyInfo {
std::string key;
size_t original_index;
QueryResult query_result;
std::vector<Slice> slices;
uint64_t total_size;
};
std::vector<ValidKeyInfo> valid_operations;
valid_operations.reserve(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
const auto &key = keys[i];
if (!query_results[i]) {
const auto error = query_results[i].error();
results.emplace_back(tl::unexpected(error));
if (error != ErrorCode::OBJECT_NOT_FOUND &&
error != ErrorCode::REPLICA_IS_NOT_READY) {
LOG(ERROR) << "Query failed for key '" << key
<< "': " << toString(error);
}
continue;
}
auto query_result_values = query_results[i].value();
if (query_result_values.replicas.empty()) {
LOG(ERROR) << "Empty replica list for key: " << key;
results.emplace_back(tl::unexpected(ErrorCode::INVALID_REPLICA));
continue;
}
const auto &replica = query_result_values.replicas[0];
uint64_t total_size = calculate_total_size(replica);
if (sizes[i] < total_size) {
LOG(ERROR) << "Buffer too small for key '" << key
<< "': required=" << total_size
<< ", available=" << sizes[i];
results.emplace_back(tl::unexpected(ErrorCode::INVALID_PARAMS));
continue;
}
std::vector<Slice> key_slices;
uint64_t offset = 0;
if (replica.is_memory_replica() == false) {
while (offset < total_size) {
auto chunk_size = std::min(total_size - offset, kMaxSliceSize);
void *chunk_ptr = static_cast<char *>(buffers[i]) + offset;
key_slices.emplace_back(Slice{chunk_ptr, chunk_size});
offset += chunk_size;
}
} else {
for (auto &handle :
replica.get_memory_descriptor().buffer_descriptors) {
void *chunk_ptr = static_cast<char *>(buffers[i]) + offset;
key_slices.emplace_back(Slice{chunk_ptr, handle.size_});
offset += handle.size_;
}
}
valid_operations.push_back(
{.key = key,
.original_index = i,
.query_result = std::move(query_result_values),
.slices = std::move(key_slices),
.total_size = total_size});
results.emplace_back(static_cast<int64_t>(total_size));
}
if (valid_operations.empty()) {
return results;
}
std::vector<std::string> batch_keys;
std::vector<QueryResult> batch_query_results;
std::unordered_map<std::string, std::vector<Slice>> batch_slices;
batch_keys.reserve(valid_operations.size());
batch_query_results.reserve(valid_operations.size());
for (const auto &op : valid_operations) {
batch_keys.push_back(op.key);
batch_query_results.push_back(op.query_result);
batch_slices[op.key] = op.slices;
}
const auto batch_get_results =
client_->BatchGet(batch_keys, batch_query_results, batch_slices);
for (size_t j = 0; j < batch_get_results.size(); ++j) {
const auto &op = valid_operations[j];
if (!batch_get_results[j]) {
const auto error = batch_get_results[j].error();
LOG(ERROR) << "BatchGet failed for key '" << op.key
<< "': " << toString(error);
results[op.original_index] = tl::unexpected(error);
}
}
return results;
}
std::vector<tl::expected<bool, ErrorCode>> PyClient::batchIsExist_internal(
const std::vector<std::string> &keys) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return std::vector<tl::expected<bool, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if (keys.empty()) {
LOG(WARNING) << "Empty keys vector provided to batchIsExist_internal";
return std::vector<tl::expected<bool, ErrorCode>>();
}
return client_->BatchIsExist(keys);
}
int PyClient::put_from_with_metadata(const std::string &key, void *buffer,
void *metadata_buffer, size_t size,
size_t metadata_size,
const ReplicateConfig &config) {
if (config.prefer_alloc_in_same_node) {
LOG(ERROR) << "prefer_alloc_in_same_node is not supported.";
return -1;
}
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return -1;
}
if (size == 0) {
LOG(WARNING) << "Attempting to put empty data for key: " << key;
return 0;
}
std::vector<mooncake::Slice> slices;
uint64_t metadata_offset = 0;
while (metadata_offset < metadata_size) {
auto metadata_chunk_size =
std::min(metadata_size - metadata_offset, kMaxSliceSize);
void *metadata_chunk_ptr =
static_cast<char *>(metadata_buffer) + metadata_offset;
slices.emplace_back(Slice{metadata_chunk_ptr, metadata_chunk_size});
metadata_offset += metadata_chunk_size;
}
uint64_t offset = 0;
while (offset < size) {
auto chunk_size = std::min(size - offset, kMaxSliceSize);
void *chunk_ptr = static_cast<char *>(buffer) + offset;
slices.emplace_back(Slice{chunk_ptr, chunk_size});
offset += chunk_size;
}
auto put_result = client_->Put(key, slices, config);
if (!put_result) {
LOG(ERROR) << "Put operation failed with error: "
<< toString(put_result.error());
return -toInt(put_result.error());
}
return 0;
}
std::vector<int> PyClient::batch_put_from_multi_buffers(
const std::vector<std::string> &keys,
const std::vector<std::vector<void *>> &all_buffers,
const std::vector<std::vector<size_t>> &sizes,
const ReplicateConfig &config) {
auto start = std::chrono::steady_clock::now();
auto internal_results =
batch_put_from_multi_buffers_internal(keys, all_buffers, sizes, config);
std::vector<int> results;
results.reserve(internal_results.size());
for (const auto &result : internal_results) {
results.push_back(to_py_ret(result));
}
auto duration_call = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start);
LOG(INFO) << "batch_put_from_multi_buffers: " << duration_call.count()
<< "us";
return results;
}
std::vector<tl::expected<void, ErrorCode>>
PyClient::batch_put_from_multi_buffers_internal(
const std::vector<std::string> &keys,
const std::vector<std::vector<void *>> &all_buffers,
const std::vector<std::vector<size_t>> &all_sizes,
const ReplicateConfig &config) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if ((keys.size() != all_buffers.size()) ||
(all_buffers.size() != all_sizes.size())) {
LOG(ERROR) << "Mismatched sizes for keys, buffers, and sizes";
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
std::vector<std::vector<mooncake::Slice>> batched_slices(keys.size());
for (size_t i = 0; i < all_buffers.size(); ++i) {
const auto &buffers = all_buffers[i];
const auto &sizes = all_sizes[i];
if (buffers.size() != sizes.size()) {
LOG(ERROR) << "Mismatched buffers and sizes of key:" << keys[i];
return std::vector<tl::expected<void, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
batched_slices[i].reserve(buffers.size());
for (size_t j = 0; j < buffers.size(); ++j) {
batched_slices[i].emplace_back(Slice{buffers[j], sizes[j]});
}
}
return client_->BatchPut(keys, batched_slices, config);
}
std::vector<int> PyClient::batch_get_into_multi_buffers(
const std::vector<std::string> &keys,
const std::vector<std::vector<void *>> &all_buffers,
const std::vector<std::vector<size_t>> &all_sizes,
bool prefer_alloc_in_same_node) {
auto start = std::chrono::steady_clock::now();
auto internal_results = batch_get_into_multi_buffers_internal(
keys, all_buffers, all_sizes, prefer_alloc_in_same_node);
std::vector<int> results;
results.reserve(internal_results.size());
for (const auto &result : internal_results) {
results.push_back(to_py_ret(result));
}
auto duration_call = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start);
LOG(INFO) << "batch_get_into_multi_buffers: " << duration_call.count()
<< "us";
return results;
}
std::vector<tl::expected<int64_t, ErrorCode>>
PyClient::batch_get_into_multi_buffers_internal(
const std::vector<std::string> &keys,
const std::vector<std::vector<void *>> &all_buffers,
const std::vector<std::vector<size_t>> &all_sizes,
bool prefer_alloc_in_same_node) {
if (!client_) {
LOG(ERROR) << "Client is not initialized";
return std::vector<tl::expected<int64_t, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
if (keys.size() != all_buffers.size() || keys.size() != all_sizes.size()) {
LOG(ERROR) << "Input vector sizes mismatch: keys=" << keys.size()
<< ", buffers=" << all_buffers.size()
<< ", sizes=" << all_sizes.size();
return std::vector<tl::expected<int64_t, ErrorCode>>(
keys.size(), tl::unexpected(ErrorCode::INVALID_PARAMS));
}
const size_t num_keys = keys.size();
std::vector<tl::expected<int64_t, ErrorCode>> results;
results.reserve(num_keys);
if (num_keys == 0) {
return results;
}
const auto query_results = client_->BatchQuery(keys);
struct ValidKeyInfo {
std::string key;
size_t original_index;
QueryResult query_result;
std::vector<Slice> slices;
uint64_t total_size;
};
std::vector<ValidKeyInfo> valid_operations;
valid_operations.reserve(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
const auto &key = keys[i];
if (!query_results[i]) {
const auto error = query_results[i].error();
results.emplace_back(tl::unexpected(error));
if (error != ErrorCode::OBJECT_NOT_FOUND) {
LOG(ERROR) << "Query failed for key '" << key
<< "': " << toString(error);
}
continue;
}
auto query_result_values = query_results[i].value();
if (query_result_values.replicas.empty()) {
LOG(ERROR) << "Empty replica list for key: " << key;
results.emplace_back(tl::unexpected(ErrorCode::INVALID_REPLICA));
continue;
}
const auto &replica = query_result_values.replicas[0];
uint64_t total_size = calculate_total_size(replica);
const auto &sizes = all_sizes[i];
uint64_t dst_total_size = 0;
for (auto &size : sizes) {
dst_total_size += size;
}
if (dst_total_size < total_size) {
LOG(ERROR) << "Buffer too small for key '" << key
<< "': required=" << total_size
<< ", available=" << dst_total_size;
results.emplace_back(tl::unexpected(ErrorCode::INVALID_PARAMS));
continue;
}
const auto &buffers = all_buffers[i];
std::vector<Slice> key_slices;
key_slices.reserve(buffers.size());
if (replica.is_memory_replica()) {
for (size_t j = 0; j < buffers.size(); ++j) {
key_slices.emplace_back(Slice{buffers[j], sizes[j]});
}
} else {
LOG(ERROR) << "Invalid replica type for key: " << key;
results.emplace_back(tl::unexpected(ErrorCode::INVALID_PARAMS));
continue;
}
valid_operations.push_back(
{.key = key,
.original_index = i,
.query_result = std::move(query_result_values),
.slices = std::move(key_slices),
.total_size = total_size});
results.emplace_back(static_cast<int64_t>(total_size));
}
if (valid_operations.empty()) {
return results;
}
std::vector<std::string> batch_keys;
std::vector<QueryResult> batch_query_results;
std::unordered_map<std::string, std::vector<Slice>> batch_slices;
batch_keys.reserve(valid_operations.size());
batch_query_results.reserve(valid_operations.size());
for (auto &op : valid_operations) {
batch_keys.push_back(op.key);
batch_query_results.push_back(op.query_result);
batch_slices[op.key] = op.slices;
}
auto batch_get_results =
client_->BatchGet(batch_keys, batch_query_results, batch_slices,
prefer_alloc_in_same_node);
for (size_t j = 0; j < batch_get_results.size(); ++j) {
const auto &op = valid_operations[j];
if (!batch_get_results[j]) {
const auto error = batch_get_results[j].error();
LOG(ERROR) << "BatchGet failed for key '" << op.key
<< "': " << toString(error);
results[op.original_index] = tl::unexpected(error);
}
}
return results;
}
}