Skip to content

Commit

Permalink
Fix: Memory corruption during serialization and deserialization type …
Browse files Browse the repository at this point in the history
…strings (#10)

* New serializer module
Removed serialization and deserialization methods from utlils/compression.h
Added utils/serialization.h to handle serilization and deserialization of values

* Serialization and deserialization of type strings
Added overloaded methods to specifically handle type strings.
Used constexpr to identify which overloaded method to invoke given V type.
  • Loading branch information
s-bose7 authored Jun 26, 2024
1 parent 29a711e commit 5482ae5
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 41 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_library(MemCache STATIC
src/keynode.cpp
src/mapitem.cpp
utils/memory_info.h
utils/serialization.h
)

# Add Snappy
Expand Down
6 changes: 6 additions & 0 deletions include/memcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include "../utils/memory_info.h"
#include "../utils/compression.h"
#include "../utils/serialization.h"


using namespace std;
using namespace chrono;
Expand Down Expand Up @@ -91,6 +93,10 @@ class MemCache {
void run_ttl_thread();
void apply_expiration_policy();

// A set of methods for doing serialization and deserialization
string serialize(const V& val);
V deserialize(const string& serialized_val);

public:

// Constructor
Expand Down
36 changes: 30 additions & 6 deletions src/memcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ V MemCache<K, V>::get(K key) {
MapItem<KeyNode<K>, string> map_item = bykey.at(key);
update_frequency_of_the(key);
// Return after decompressing the value
return Compressor::deserialize<V>(
Compressor::uncompress(map_item.value), std::is_arithmetic<V>{}
);
return deserialize(Compressor::uncompress(map_item.value));
}
return V();
}
Expand Down Expand Up @@ -74,6 +72,34 @@ unsigned int MemCache<K, V>::size(){
return curr_size;
}

template<typename K, typename V>
string MemCache<K, V>::serialize(const V& val){
if constexpr (std::is_arithmetic<V>::value) {
// For arithmetic data types
return Serializer::serialize<V>(val, std::true_type());
} else if constexpr (std::is_same<V, string>::value) {
// For string data types
return Serializer::serialize(val);
} else {
// For custom object types i.e. user-defined types
return Serializer::serialize<V>(val, std::false_type());
}
}

template<typename K, typename V>
V MemCache<K, V>::deserialize(const string& serialized_val){
if constexpr (std::is_arithmetic<V>::value) {
// For arithmetic data types
return Serializer::deserialize<V>(serialized_val, std::true_type());
} else if constexpr (std::is_same<V, string>::value) {
// For string data types
return Serializer::deserialize(serialized_val);
} else {
// For custom object types i.e. user-defined types
return Serializer::deserialize<V>(serialized_val, std::false_type());
}
}


template<typename K, typename V>
void MemCache<K, V>::put(K key, V value, unsigned long ttl) {
Expand All @@ -83,9 +109,7 @@ void MemCache<K, V>::put(K key, V value, unsigned long ttl) {
ttl = INT_MAX; // Default ttl value
}
// Compress value
string compressed_val = Compressor::compress(
Compressor::serialize(value, std::is_arithmetic<V>{})
);
string compressed_val = Compressor::compress(serialize(value));
expiration_map[key] = steady_clock::now() + chrono::seconds(ttl);
if(bykey.count(key) > 0) {
// Update the value of the key
Expand Down
10 changes: 5 additions & 5 deletions tests/test_atomicity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <gtest/gtest.h>

// Shared cache
MemCache<string, int> cache(100);
MemCache<string, string> cache(100);


// Test atomicity for put operation
Expand All @@ -17,7 +17,7 @@ TEST(AtomicityTest, AtomicPutOperation) {

for (int i=1; i<=num_threads; i++) {
threads.emplace_back([&]() {
cache.put("key", 2606);
cache.put("key", "val");
++counter;
});
}
Expand All @@ -27,22 +27,22 @@ TEST(AtomicityTest, AtomicPutOperation) {
}

EXPECT_EQ(counter.load(), num_threads);
EXPECT_EQ(cache.get("key"), 2606);
EXPECT_EQ(cache.get("key"), "val");
EXPECT_EQ(cache.size(), 1);
}


// Test atomicity of get operation
TEST(AtomicityTest, AtomicGetOperation) {
cache.put("foo", 3205);
cache.put("foo", "bar");

std::atomic<int> counter(0);
const int num_threads = 100;
std::vector<std::thread> threads;

for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() {
if(cache.get("foo") == 3205) {
if(cache.get("foo") == "bar") {
++counter;
}
});
Expand Down
30 changes: 0 additions & 30 deletions utils/compression.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,11 @@

#include <string>
#include <snappy.h>
#include <sstream>
#include <cstring>
#include <type_traits>

class Compressor {

public:

template<typename T>
static std::string serialize(const T& data, std::true_type) {
return std::to_string(data);
}

template<typename T>
static T deserialize(const std::string& decompressed, std::true_type) {
std::istringstream iss(decompressed);
T data;
iss >> data;
return data;
}

template<typename T>
static std::string serialize(const T& data, std::false_type) {
// A byte-by-byte copy of the memory representation of the value object.
const char* byte_seq = reinterpret_cast<const char*>(&data);
return std::string(byte_seq, sizeof(T));
}

template<typename T>
static T deserialize(const std::string& decompressed, std::false_type) {
T value;
std::memcpy(&value, decompressed.data(), sizeof(T));
return value;
}

static std::string compress(const std::string& serialized){
std::string compressed;
snappy::Compress(serialized.data(), serialized.size(), &compressed);
Expand Down
51 changes: 51 additions & 0 deletions utils/serialization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef SERIALIZATION_H
#define SERIALIZATION_H

#include <sstream>
#include <cstring>
#include <type_traits>

class Serializer {

public:

// Serialize function for std::string
static std::string serialize(const std::string& data) {
return data;
}

// Deserialize function for std::string
static std::string deserialize(const std::string& serialized_val) {
return serialized_val;
}

template<typename T>
static std::string serialize(const T& data, std::true_type) {
return std::to_string(data);
}

template<typename T>
static T deserialize(const std::string& serialized_val, std::true_type) {
std::istringstream iss(serialized_val);
T data;
iss >> data;
return data;
}

template<typename T>
static std::string serialize(const T& data, std::false_type) {

// A byte-by-byte copy of the memory representation of the value object.
const char* byte_seq = reinterpret_cast<const char*>(&data);
return std::string(byte_seq, sizeof(T));
}

template<typename T>
static T deserialize(const std::string& serialized_val, std::false_type) {
T value;
std::memcpy(&value, serialized_val.data(), sizeof(T));
return value;
}
};

#endif

0 comments on commit 5482ae5

Please sign in to comment.