From 6bc8b1e1ed49e63857a8f40b219819f792c6a68a Mon Sep 17 00:00:00 2001 From: Karen Chen Date: Wed, 27 Nov 2024 14:56:47 -0800 Subject: [PATCH] feat: implement sliding expiration cache classes --- driver/CMakeLists.txt | 4 + driver/sliding_expiration_cache.cc | 133 ++++++++++++++++++ driver/sliding_expiration_cache.h | 125 ++++++++++++++++ ...g_expiration_cache_with_clean_up_thread.cc | 65 +++++++++ ...ng_expiration_cache_with_clean_up_thread.h | 58 ++++++++ 5 files changed, 385 insertions(+) create mode 100644 driver/sliding_expiration_cache.cc create mode 100644 driver/sliding_expiration_cache.h create mode 100644 driver/sliding_expiration_cache_with_clean_up_thread.cc create mode 100644 driver/sliding_expiration_cache_with_clean_up_thread.h diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt index 8a79b4055..1c9a090f7 100644 --- a/driver/CMakeLists.txt +++ b/driver/CMakeLists.txt @@ -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) @@ -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) diff --git a/driver/sliding_expiration_cache.cc b/driver/sliding_expiration_cache.cc new file mode 100644 index 000000000..8f34645e4 --- /dev/null +++ b/driver/sliding_expiration_cache.cc @@ -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 +void SLIDING_EXPIRATION_CACHE::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 +void SLIDING_EXPIRATION_CACHE::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 +void SLIDING_EXPIRATION_CACHE::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 +V SLIDING_EXPIRATION_CACHE::compute_if_absent(K key, std::function 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 +V SLIDING_EXPIRATION_CACHE::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 +V SLIDING_EXPIRATION_CACHE::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 +void SLIDING_EXPIRATION_CACHE::remove(K key) { + this->remove_and_dispose(key); + clean_up(); +} + +template +void SLIDING_EXPIRATION_CACHE::clear() { + for (auto& [key, cache_item] : this->cache) { + this->remove_and_dispose(key); + } + + this->cache.clear(); +} + +template +std::unordered_map SLIDING_EXPIRATION_CACHE::get_entries() { + const std::unordered_map entries; + for (auto& [key, cache_item] : this->cache) { + entries[key] = cache_item->item; + } + + return entries; +} + +template +int SLIDING_EXPIRATION_CACHE::size() { + return this->cache.size(); +} + +template +void SLIDING_EXPIRATION_CACHE::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); +} diff --git a/driver/sliding_expiration_cache.h b/driver/sliding_expiration_cache.h new file mode 100644 index 000000000..600fd7fa9 --- /dev/null +++ b/driver/sliding_expiration_cache.h @@ -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 +#include +#include +#include + +template +class SHOULD_DISPOSE_FUNC { + public: + virtual bool should_dispose(T item); +}; + +template +class ITEM_DISPOSAL_FUNC { + public: + virtual void dispose(T item); +}; + +template +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 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 should_dispose_func, ITEM_DISPOSAL_FUNC item_disposal_func) + : should_dispose_func(should_dispose_func), item_disposal_func(item_disposal_func){}; + SLIDING_EXPIRATION_CACHE(SHOULD_DISPOSE_FUNC should_dispose_func, ITEM_DISPOSAL_FUNC 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 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 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 cache; + long clean_up_interval_nanos; + std::atomic clean_up_time_nanos; + const SHOULD_DISPOSE_FUNC should_dispose_func; + const ITEM_DISPOSAL_FUNC item_disposal_func; + + void remove_and_dispose(K key); + void remove_if_expired(K key); + void clean_up(); +}; + +#endif diff --git a/driver/sliding_expiration_cache_with_clean_up_thread.cc b/driver/sliding_expiration_cache_with_clean_up_thread.cc new file mode 100644 index 000000000..355cc0c6a --- /dev/null +++ b/driver/sliding_expiration_cache_with_clean_up_thread.cc @@ -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 +void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD::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 +void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD::init_clean_up_thread() { + if (!this->is_initialized) { + std::unique_lock lock(mutex_); + if (!this->is_initialized) { + this->clean_up_thread_pool.push(this->run); + this->is_initialized = true; + } + } +} + +template +SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD( + SHOULD_DISPOSE_FUNC should_dispose_func, ITEM_DISPOSAL_FUNC item_disposal_func) { + this->init_clean_up_thread(); +} + +template +SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD( + SHOULD_DISPOSE_FUNC should_dispose_func, ITEM_DISPOSAL_FUNC item_disposal_func, long clean_up_interval_nanos) { + this->init_clean_up_thread(); +} diff --git a/driver/sliding_expiration_cache_with_clean_up_thread.h b/driver/sliding_expiration_cache_with_clean_up_thread.h new file mode 100644 index 000000000..4e7a5091f --- /dev/null +++ b/driver/sliding_expiration_cache_with_clean_up_thread.h @@ -0,0 +1,58 @@ +// 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_WITH_CLEAN_UP_THREAD__ +#define __SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD__ + +#include +#include + +#include "sliding_expiration_cache.h" + +template +class SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD : public SLIDING_EXPIRATION_CACHE { + public: + SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(); + SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(SHOULD_DISPOSE_FUNC should_dispose_func, + ITEM_DISPOSAL_FUNC item_disposal_func); + SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(SHOULD_DISPOSE_FUNC should_dispose_func, + ITEM_DISPOSAL_FUNC item_disposal_func, long clean_up_interval_nanos); + ~SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(); + + protected: + bool is_initialized = false; + std::mutex mutex_; + ctpl::thread_pool clean_up_thread_pool; + + private: + void init_clean_up_thread(); + void run(); +}; + +#endif