Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/cpp/src/kvs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <sstream>
#include "internal/kvs_helper.hpp"
#include "kvs.hpp"
#include <filesystem>

//TODO Default Value Handling TBD
//TODO Add Score Logging
Expand Down Expand Up @@ -375,10 +376,60 @@ score::ResultBlank Kvs::remove_key(const std::string_view key) {
return result;
}

score::Result<size_t> Kvs::get_file_size(const score::filesystem::Path& file_path) {
std::error_code ec;
const auto size = std::filesystem::file_size(file_path.CStr(), ec);

if (ec) {
// Check if the error is "file not found"
if (ec == std::errc::no_such_file_or_directory) {
// File does not exist, its size is 0. This is not an error.
return 0;
}
logger->LogError() << "Error: Could not get size of file " << file_path << ": " << ec.message();
return score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}

return size;
}

/* Helper Function to get current storage size */
score::Result<size_t> Kvs::get_current_storage_size() {
size_t total_size = 0;
const std::array<const char*, 2> file_extensions = {".json", ".hash"};

for (size_t snapshot_index = 0; snapshot_index <= KVS_MAX_SNAPSHOTS; ++snapshot_index) {
const std::string snapshot_suffix = "_" + to_string(snapshot_index);

for (const char* extension : file_extensions) {
const score::filesystem::Path file_path = filename_prefix.Native() + snapshot_suffix + extension;
auto size_result = get_file_size(file_path);
if (!size_result) {
return size_result; // Propagate error directly
}
total_size += size_result.value();
}
}
return total_size;
}

/* Helper Function to write JSON data to a file for flush process (also adds Hash file)*/
score::ResultBlank Kvs::write_json_data(const std::string& buf)
{
score::ResultBlank result = score::MakeUnexpected(ErrorCode::UnmappedError);

auto current_size_res = get_current_storage_size();
if (!current_size_res) {
return score::MakeUnexpected(static_cast<ErrorCode>(*current_size_res.error()));
}

const size_t total_size = current_size_res.value() + buf.size();

if (total_size > KVS_MAX_STORAGE_BYTES) {
logger->LogError() << "error: KVS storage limit exceeded. total_size:" << total_size << " KVS_MAX_STORAGE_BYTES:" << KVS_MAX_STORAGE_BYTES;
return score::MakeUnexpected(ErrorCode::OutOfStorageSpace);
}

score::filesystem::Path json_path{filename_prefix.Native() + "_0.json"};
score::filesystem::Path dir = json_path.ParentPath();
if (!dir.Empty()) {
Expand Down
4 changes: 4 additions & 0 deletions src/cpp/src/kvs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "score/mw/log/logger.h"

#define KVS_MAX_SNAPSHOTS 3
#define KVS_MAX_STORAGE_BYTES (100) /* Max total storage size for all snapshots including hash files in bytes */


namespace score::mw::per::kvs {

Expand Down Expand Up @@ -367,6 +369,8 @@ class Kvs final {
std::unique_ptr<score::mw::log::Logger> logger;

/* Private Methods */
score::Result<size_t> get_file_size(const score::filesystem::Path& file_path);
score::Result<size_t> get_current_storage_size();
score::ResultBlank snapshot_rotate();
score::Result<std::unordered_map<std::string, KvsValue>> parse_json_data(const std::string& data);
score::Result<std::unordered_map<std::string, KvsValue>> open_json(const score::filesystem::Path& prefix, OpenJsonNeedFile need_file);
Expand Down
42 changes: 42 additions & 0 deletions src/cpp/tests/test_kvs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1216,3 +1216,45 @@ TEST(kvs_get_filename, get_hashname_failure){

cleanup_environment();
}


TEST(kvs, flush_fails_when_storage_limit_exceeded) {
// Use a unique directory for this test to avoid conflicts
const std::string test_dir = "./kvs_storage_test/";
// Ensure the directory is clean before starting
std::filesystem::remove_all(test_dir);

// 1. Setup KVS
KvsBuilder builder(instance_id);
builder.dir("./kvs_storage_test/");
auto open_res = builder.build();
ASSERT_TRUE(open_res);
Kvs kvs = std::move(open_res.value());

// 2. Add data that is close to the limit.
// There is overhead for the JSON structure (key, type info, braces, etc.) and the hash file (4 bytes).
// We will make the data payload a bit smaller than the max to account for this.
size_t overhead_estimate = 100;
size_t data_size = KVS_MAX_STORAGE_BYTES - overhead_estimate;
std::string large_data(data_size, 'a');

auto set_res1 = kvs.set_value("large_data", KvsValue(large_data.c_str()));
ASSERT_TRUE(set_res1);

// 3. Flush the first batch of data and assert it succeeds.
auto flush_res1 = kvs.flush();
ASSERT_TRUE(flush_res1);

// 4. Add a little more data, which should push the total size over the limit.
auto set_res2 = kvs.set_value("extra_data", KvsValue("this should not fit"));
ASSERT_TRUE(set_res2);

// 5. The second flush should fail because the storage limit is exceeded.
auto flush_res2 = kvs.flush();
ASSERT_FALSE(flush_res2);
ASSERT_EQ(static_cast<ErrorCode>(*flush_res2.error()), ErrorCode::OutOfStorageSpace);

// Cleanup the test directory
std::filesystem::remove_all(test_dir);
}

Loading