Skip to content

Commit

Permalink
feat: implement sliding expiration cache classes
Browse files Browse the repository at this point in the history
  • Loading branch information
karenc-bq committed Dec 18, 2024
1 parent bfd42a7 commit 6bc8b1e
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 0 deletions.
4 changes: 4 additions & 0 deletions driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
saml_http_client.cc
saml_util.cc
secrets_manager_proxy.cc
sliding_expiration_cache.cc
sliding_expiration_cache_with_clean_up_thread.cc
topology_service.cc
transact.cc
utility.cc)
Expand Down Expand Up @@ -160,6 +162,8 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
saml_http_client.h
saml_util.h
secrets_manager_proxy.h
sliding_expiration_cache.h
sliding_expiration_cache_with_clean_up_thread.h
topology_service.h
../MYODBC_MYSQL.h ../MYODBC_CONF.h ../MYODBC_ODBC.h)
if(TELEMETRY)
Expand Down
133 changes: 133 additions & 0 deletions driver/sliding_expiration_cache.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0
// (GPLv2), as published by the Free Software Foundation, with the
// following additional permissions:
//
// This program is distributed with certain software that is licensed
// under separate terms, as designated in a particular file or component
// or in the license documentation. Without limiting your rights under
// the GPLv2, the authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with the program.
//
// Without limiting the foregoing grant of rights under the GPLv2 and
// additional permission as to separately licensed software, this
// program is also subject to the Universal FOSS Exception, version 1.0,
// a copy of which can be found along with its FAQ at
// http://oss.oracle.com/licenses/universal-foss-exception.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// http://www.gnu.org/licenses/gpl-2.0.html.

#include "sliding_expiration_cache.h"

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::remove_and_dispose(K key) {
const CACHE_ITEM cache_item = this->cache.erase(key);
if (cache_item != nullptr && item_disposal_func != nullptr) {
item_disposal_func->dispose(cache_item.item);
}
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::remove_if_expired(K key) {
V item = nullptr;
for (auto& [key, cache_item] : this->cache) {
if (cache_item != nullptr && cache_item->should_clean_up(this->should_dispose_func)) {
item = cache_item.item;
break;
}
}

if (item != nullptr) {
return;
}

if (item_disposal_func != nullptr) {
item_disposal_func->dispose(item);
}
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::clean_up() {
if (this->clean_up_time_nanos.load() > std::chrono::steady_clock::now()) {
return;
}

this->clean_up_time_nanos =
std::chrono::system_clock::now() + std::chrono::nanoseconds(this->clean_up_interval_nanos);

for (auto& [key, cache_item] : this->cache) {
this->remove_if_expired(key);
}
}

template <class K, class V>
V SLIDING_EXPIRATION_CACHE<K, V>::compute_if_absent(K key, std::function<K(V)> mapping_function,
long item_expiration_nanos) {
this->clean_up();
const CACHE_ITEM cache_item = this->cache->emplace(
key, new CACHE_ITEM(mapping_function(key),
std::chrono::system_clock::now() + std::chrono::nanoseconds(item_expiration_nanos)));
return cache_item->with_extend_expiration(item_expiration_nanos)->item;
}

template <class K, class V>
V SLIDING_EXPIRATION_CACHE<K, V>::put(K key, V value, long item_expiration_nanos) {
this->clean_up();
const CACHE_ITEM cache_item = this->cache[key];
this->cache[key] =
new CACHE_ITEM(value, std::chrono::system_clock::now() + std::chrono::nanoseconds(item_expiration_nanos));
return cache_item == nullptr ? cache_item : cache_item->with_extend_expiration(item_expiration_nanos)->item;
}

template <class K, class V>
V SLIDING_EXPIRATION_CACHE<K, V>::get(K key, long item_expiration_nanos) {
this->clean_up();
const CACHE_ITEM cache_item = this->cache[key];
return cache_item == nullptr ? cache_item : cache_item->with_extend_expiration(item_expiration_nanos)->item;
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::remove(K key) {
this->remove_and_dispose(key);
clean_up();
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::clear() {
for (auto& [key, cache_item] : this->cache) {
this->remove_and_dispose(key);
}

this->cache.clear();
}

template <class K, class V>
std::unordered_map<K, V> SLIDING_EXPIRATION_CACHE<K, V>::get_entries() {
const std::unordered_map<K, V> entries;
for (auto& [key, cache_item] : this->cache) {
entries[key] = cache_item->item;
}

return entries;
}

template <class K, class V>
int SLIDING_EXPIRATION_CACHE<K, V>::size() {
return this->cache.size();
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE<K, V>::set_clean_up_interval_nanos(long clean_up_interval_nanos) {
this->clean_up_interval_nanos = clean_up_interval_nanos;
this->clean_up_time_nanos = std::chrono::system_clock::now() + std::chrono::nanoseconds(clean_up_interval_nanos);
}
125 changes: 125 additions & 0 deletions driver/sliding_expiration_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0
// (GPLv2), as published by the Free Software Foundation, with the
// following additional permissions:
//
// This program is distributed with certain software that is licensed
// under separate terms, as designated in a particular file or component
// or in the license documentation. Without limiting your rights under
// the GPLv2, the authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with the program.
//
// Without limiting the foregoing grant of rights under the GPLv2 and
// additional permission as to separately licensed software, this
// program is also subject to the Universal FOSS Exception, version 1.0,
// a copy of which can be found along with its FAQ at
// http://oss.oracle.com/licenses/universal-foss-exception.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// http://www.gnu.org/licenses/gpl-2.0.html.

#ifndef __SLIDING_EXPIRATION_CACHE__
#define __SLIDING_EXPIRATION_CACHE__

#include <atomic>
#include <chrono>
#include <functional>
#include <unordered_map>

template <class T>
class SHOULD_DISPOSE_FUNC {
public:
virtual bool should_dispose(T item);
};

template <class T>
class ITEM_DISPOSAL_FUNC {
public:
virtual void dispose(T item);
};

template <class K, class V>
class SLIDING_EXPIRATION_CACHE {
public:
class CACHE_ITEM {
const V item;
std::chrono::steady_clock::time_point expiration_time;

public:
CACHE_ITEM(V item, std::chrono::steady_clock::time_point expiration_time)
: item(item), expiration_time(expiration_time){};
~CACHE_ITEM() = default;

protected:
bool should_clean_up(SHOULD_DISPOSE_FUNC<V> should_dispose_func) {
if (should_dispose_func != nullptr) {
return std::chrono::steady_clock::now() > expiration_time && should_dispose_func->should_dispose(this->item);
}
return false;
}

CACHE_ITEM with_extend_expiration(long item_expiration_nanos) {
this->expiration_time = std::chrono::steady_clock::now() + std::chrono::nanoseconds(item_expiration_nanos);
return this;
}
};

SLIDING_EXPIRATION_CACHE(SHOULD_DISPOSE_FUNC<V> should_dispose_func, ITEM_DISPOSAL_FUNC<V> item_disposal_func)
: should_dispose_func(should_dispose_func), item_disposal_func(item_disposal_func){};
SLIDING_EXPIRATION_CACHE(SHOULD_DISPOSE_FUNC<V> should_dispose_func, ITEM_DISPOSAL_FUNC<V> item_disposal_func,
long clean_up_interval_nanos)
: clean_up_interval_nanos(clean_up_interval_nanos),
should_dispose_func(should_dispose_func),
item_disposal_func(item_disposal_func){};

V compute_if_absent(K key, std::function<K(V)> mapping_function, long item_expiration_nanos);

V put(K key, V value, long item_expiration_nanos);
V get(K key, long item_expiration_nanos);
void remove(K key);

/**
* Remove and dispose of all entries in the cache.
*/
void clear();

/**
* Get a map copy of all entries in the cache, including expired entries.
*Return a map copy of all entries in the cache, including expired entries
*/
std::unordered_map<K, V> get_entries();

/**
* Get the current size of the cache, including expired entries.
* Return the current size of the cache, including expired entries.
*/
int size();

/**
* Set the cleanup interval for the cache. At cleanup time, expired entries marked for cleanup via
* ShouldDisposeFunc (if defined) are disposed.
*/
void set_clean_up_interval_nanos(long clean_up_interval_nanos);

protected:
const std::unordered_map<K, CACHE_ITEM> cache;
long clean_up_interval_nanos;
std::atomic<std::chrono::steady_clock::time_point> clean_up_time_nanos;
const SHOULD_DISPOSE_FUNC<V> should_dispose_func;
const ITEM_DISPOSAL_FUNC<V> item_disposal_func;

void remove_and_dispose(K key);
void remove_if_expired(K key);
void clean_up();
};

#endif
65 changes: 65 additions & 0 deletions driver/sliding_expiration_cache_with_clean_up_thread.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0
// (GPLv2), as published by the Free Software Foundation, with the
// following additional permissions:
//
// This program is distributed with certain software that is licensed
// under separate terms, as designated in a particular file or component
// or in the license documentation. Without limiting your rights under
// the GPLv2, the authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with the program.
//
// Without limiting the foregoing grant of rights under the GPLv2 and
// additional permission as to separately licensed software, this
// program is also subject to the Universal FOSS Exception, version 1.0,
// a copy of which can be found along with its FAQ at
// http://oss.oracle.com/licenses/universal-foss-exception.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// http://www.gnu.org/licenses/gpl-2.0.html.

#include "sliding_expiration_cache_with_clean_up_thread.h"

template <class K, class V>
void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::run() {
while (true) {
const std::chrono::nanoseconds clean_up_interval = std::chrono::nanoseconds(this->clean_up_interval_nanos);
std::this_thread::sleep_for(clean_up_interval);
this->clean_up_time_nanos = std::chrono::system_clock::now() + clean_up_interval;
for (auto& [key, cache_item] : this->cache) {
this->remove_if_expired(key);
}
}
}

template <class K, class V>
void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::init_clean_up_thread() {
if (!this->is_initialized) {
std::unique_lock<std::mutex> lock(mutex_);
if (!this->is_initialized) {
this->clean_up_thread_pool.push(this->run);
this->is_initialized = true;
}
}
}

template <class K, class V>
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(
SHOULD_DISPOSE_FUNC<V> should_dispose_func, ITEM_DISPOSAL_FUNC<V> item_disposal_func) {
this->init_clean_up_thread();
}

template <class K, class V>
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(
SHOULD_DISPOSE_FUNC<V> should_dispose_func, ITEM_DISPOSAL_FUNC<V> item_disposal_func, long clean_up_interval_nanos) {
this->init_clean_up_thread();
}
Loading

0 comments on commit 6bc8b1e

Please sign in to comment.