Skip to content

Commit 298a49b

Browse files
authored
feat: implement sliding expiration cache classes (#216)
1 parent 0b5e268 commit 298a49b

7 files changed

+609
-0
lines changed

Diff for: driver/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
104104
saml_http_client.cc
105105
saml_util.cc
106106
secrets_manager_proxy.cc
107+
sliding_expiration_cache.cc
108+
sliding_expiration_cache_with_clean_up_thread.cc
107109
topology_service.cc
108110
transact.cc
109111
utility.cc)
@@ -160,6 +162,8 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
160162
saml_http_client.h
161163
saml_util.h
162164
secrets_manager_proxy.h
165+
sliding_expiration_cache.h
166+
sliding_expiration_cache_with_clean_up_thread.h
163167
topology_service.h
164168
../MYODBC_MYSQL.h ../MYODBC_CONF.h ../MYODBC_ODBC.h)
165169
if(TELEMETRY)

Diff for: driver/sliding_expiration_cache.cc

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// This program is free software; you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License, version 2.0
5+
// (GPLv2), as published by the Free Software Foundation, with the
6+
// following additional permissions:
7+
//
8+
// This program is distributed with certain software that is licensed
9+
// under separate terms, as designated in a particular file or component
10+
// or in the license documentation. Without limiting your rights under
11+
// the GPLv2, the authors of this program hereby grant you an additional
12+
// permission to link the program and your derivative works with the
13+
// separately licensed software that they have included with the program.
14+
//
15+
// Without limiting the foregoing grant of rights under the GPLv2 and
16+
// additional permission as to separately licensed software, this
17+
// program is also subject to the Universal FOSS Exception, version 1.0,
18+
// a copy of which can be found along with its FAQ at
19+
// http://oss.oracle.com/licenses/universal-foss-exception.
20+
//
21+
// This program is distributed in the hope that it will be useful, but
22+
// WITHOUT ANY WARRANTY; without even the implied warranty of
23+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24+
// See the GNU General Public License, version 2.0, for more details.
25+
//
26+
// You should have received a copy of the GNU General Public License
27+
// along with this program. If not, see
28+
// http://www.gnu.org/licenses/gpl-2.0.html.
29+
30+
#include "sliding_expiration_cache.h"
31+
32+
#include <string>
33+
#include <utility>
34+
#include <vector>
35+
36+
template <class K, class V>
37+
void SLIDING_EXPIRATION_CACHE<K, V>::remove_and_dispose(K key) {
38+
if (this->cache.count(key)) {
39+
std::shared_ptr<CACHE_ITEM> cache_item = this->cache[key];
40+
if (item_disposal_func != nullptr) {
41+
item_disposal_func->dispose(cache_item->item);
42+
}
43+
this->cache.erase(key);
44+
}
45+
}
46+
47+
template <class K, class V>
48+
void SLIDING_EXPIRATION_CACHE<K, V>::clean_up() {
49+
if (this->clean_up_time_nanos.load() > std::chrono::steady_clock::now()) {
50+
return;
51+
}
52+
53+
this->clean_up_time_nanos =
54+
std::chrono::steady_clock::now() + std::chrono::nanoseconds(this->clean_up_interval_nanos);
55+
56+
std::vector<K> keys;
57+
keys.reserve(this->cache.size());
58+
for (auto& [key, cache_item] : this->cache) {
59+
keys.push_back(key);
60+
}
61+
for (const auto& key : keys) {
62+
this->remove_if_expired(key);
63+
}
64+
}
65+
66+
template <class K, class V>
67+
V SLIDING_EXPIRATION_CACHE<K, V>::compute_if_absent(K key, std::function<K(V)> mapping_function,
68+
long long item_expiration_nanos) {
69+
this->clean_up();
70+
auto cache_item = std::make_shared<CACHE_ITEM>(mapping_function(key),
71+
std::chrono::steady_clock::now() + std::chrono::nanoseconds(item_expiration_nanos));
72+
this->cache.emplace(key, cache_item);
73+
return cache_item->with_extend_expiration(item_expiration_nanos)->item;
74+
}
75+
76+
template <class K, class V>
77+
V SLIDING_EXPIRATION_CACHE<K, V>::put(K key, V value, long long item_expiration_nanos) {
78+
this->clean_up();
79+
std::shared_ptr<CACHE_ITEM> cache_item = std::make_shared<CACHE_ITEM>(
80+
std::move(value), std::chrono::steady_clock::now() + std::chrono::nanoseconds(item_expiration_nanos));
81+
this->cache[key] = cache_item;
82+
return cache_item->with_extend_expiration(item_expiration_nanos)->item;
83+
}
84+
85+
template <class K, class V>
86+
V SLIDING_EXPIRATION_CACHE<K, V>::get(K key, long long item_expiration_nanos, V default_value) {
87+
this->clean_up();
88+
89+
if (this->cache.count(key)) {
90+
std::shared_ptr<CACHE_ITEM> cache_item = this->cache[key];
91+
return cache_item->with_extend_expiration(item_expiration_nanos)->item;
92+
}
93+
94+
return default_value;
95+
}
96+
97+
template <class K, class V>
98+
void SLIDING_EXPIRATION_CACHE<K, V>::remove(K key) {
99+
this->remove_and_dispose(std::move(key));
100+
clean_up();
101+
}
102+
103+
template <class K, class V>
104+
void SLIDING_EXPIRATION_CACHE<K, V>::clear() {
105+
std::vector<K> keys;
106+
keys.reserve(this->cache.size());
107+
for (auto& [key, cache_item] : this->cache) {
108+
keys.push_back(key);
109+
}
110+
for (const auto& key : keys) {
111+
this->remove_and_dispose(key);
112+
}
113+
this->cache.clear();
114+
}
115+
116+
template <class K, class V>
117+
std::unordered_map<K, V> SLIDING_EXPIRATION_CACHE<K, V>::get_entries() {
118+
std::unordered_map<K, V> entries;
119+
for (auto& [key, cache_item] : this->cache) {
120+
entries[key] = cache_item->item;
121+
}
122+
123+
return entries;
124+
}
125+
126+
template <class K, class V>
127+
int SLIDING_EXPIRATION_CACHE<K, V>::size() {
128+
return this->cache.size();
129+
}
130+
131+
template <class K, class V>
132+
void SLIDING_EXPIRATION_CACHE<K, V>::set_clean_up_interval_nanos(long long clean_up_interval_nanos) {
133+
this->clean_up_interval_nanos = clean_up_interval_nanos;
134+
this->clean_up_time_nanos.store(std::chrono::steady_clock::now() + std::chrono::nanoseconds(clean_up_interval_nanos));
135+
}
136+
137+
template class SLIDING_EXPIRATION_CACHE<std::string, std::string>;

Diff for: driver/sliding_expiration_cache.h

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// This program is free software; you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License, version 2.0
5+
// (GPLv2), as published by the Free Software Foundation, with the
6+
// following additional permissions:
7+
//
8+
// This program is distributed with certain software that is licensed
9+
// under separate terms, as designated in a particular file or component
10+
// or in the license documentation. Without limiting your rights under
11+
// the GPLv2, the authors of this program hereby grant you an additional
12+
// permission to link the program and your derivative works with the
13+
// separately licensed software that they have included with the program.
14+
//
15+
// Without limiting the foregoing grant of rights under the GPLv2 and
16+
// additional permission as to separately licensed software, this
17+
// program is also subject to the Universal FOSS Exception, version 1.0,
18+
// a copy of which can be found along with its FAQ at
19+
// http://oss.oracle.com/licenses/universal-foss-exception.
20+
//
21+
// This program is distributed in the hope that it will be useful, but
22+
// WITHOUT ANY WARRANTY; without even the implied warranty of
23+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24+
// See the GNU General Public License, version 2.0, for more details.
25+
//
26+
// You should have received a copy of the GNU General Public License
27+
// along with this program. If not, see
28+
// http://www.gnu.org/licenses/gpl-2.0.html.
29+
30+
#ifndef __SLIDING_EXPIRATION_CACHE__
31+
#define __SLIDING_EXPIRATION_CACHE__
32+
33+
#include <atomic>
34+
#include <chrono>
35+
#include <functional>
36+
#include <memory>
37+
#include <unordered_map>
38+
39+
template <class T>
40+
class SHOULD_DISPOSE_FUNC {
41+
public:
42+
virtual bool should_dispose(T item);
43+
};
44+
45+
template <class T>
46+
class ITEM_DISPOSAL_FUNC {
47+
public:
48+
virtual void dispose(T item);
49+
};
50+
51+
template <class K, class V>
52+
class SLIDING_EXPIRATION_CACHE {
53+
public:
54+
class CACHE_ITEM {
55+
public:
56+
CACHE_ITEM() = default;
57+
CACHE_ITEM(V item, std::chrono::steady_clock::time_point expiration_time)
58+
: item(item), expiration_time(expiration_time){};
59+
~CACHE_ITEM() = default;
60+
V item;
61+
62+
CACHE_ITEM* with_extend_expiration(long long item_expiration_nanos) {
63+
this->expiration_time = std::chrono::steady_clock::now() + std::chrono::nanoseconds(item_expiration_nanos);
64+
return this;
65+
}
66+
67+
bool should_clean_up(SHOULD_DISPOSE_FUNC<V>* should_dispose_func) {
68+
if (should_dispose_func != nullptr) {
69+
return std::chrono::steady_clock::now() > this->expiration_time &&
70+
should_dispose_func->should_dispose(this->item);
71+
}
72+
73+
return std::chrono::steady_clock::now() > this->expiration_time;
74+
}
75+
76+
private:
77+
std::chrono::steady_clock::time_point expiration_time;
78+
};
79+
80+
SLIDING_EXPIRATION_CACHE() {
81+
this->should_dispose_func = nullptr;
82+
this->item_disposal_func = nullptr;
83+
}
84+
85+
SLIDING_EXPIRATION_CACHE(SHOULD_DISPOSE_FUNC<V>* should_dispose_func, ITEM_DISPOSAL_FUNC<V>* item_disposal_func)
86+
: should_dispose_func(should_dispose_func), item_disposal_func(item_disposal_func){};
87+
SLIDING_EXPIRATION_CACHE(SHOULD_DISPOSE_FUNC<V>* should_dispose_func, ITEM_DISPOSAL_FUNC<V>* item_disposal_func,
88+
long long clean_up_interval_nanos)
89+
: clean_up_interval_nanos(clean_up_interval_nanos),
90+
should_dispose_func(should_dispose_func),
91+
item_disposal_func(item_disposal_func){};
92+
93+
V compute_if_absent(K key, std::function<K(V)> mapping_function, long long item_expiration_nanos);
94+
95+
V put(K key, V value, long long item_expiration_nanos);
96+
V get(K key, long long item_expiration_nanos, V default_value);
97+
void remove(K key);
98+
99+
/**
100+
* Remove and dispose of all entries in the cache.
101+
*/
102+
void clear();
103+
104+
/**
105+
* Get a map copy of all entries in the cache, including expired entries.
106+
*/
107+
std::unordered_map<K, V> get_entries();
108+
109+
/**
110+
* Get the current size of the cache, including expired entries.
111+
*/
112+
int size();
113+
114+
/**
115+
* Set the cleanup interval for the cache. At cleanup time, expired entries marked for cleanup via
116+
* ShouldDisposeFunc (if defined) are disposed.
117+
*/
118+
void set_clean_up_interval_nanos(long long clean_up_interval_nanos);
119+
120+
protected:
121+
std::unordered_map<K, std::shared_ptr<CACHE_ITEM>> cache;
122+
long long clean_up_interval_nanos = 6000000000; // 1 minutes
123+
std::atomic<std::chrono::steady_clock::time_point> clean_up_time_nanos;
124+
SHOULD_DISPOSE_FUNC<V>* should_dispose_func;
125+
ITEM_DISPOSAL_FUNC<V>* item_disposal_func;
126+
127+
void remove_and_dispose(K key);
128+
void remove_if_expired(K key) {
129+
std::vector<V> items;
130+
if (this->cache.count(key)) {
131+
std::shared_ptr<CACHE_ITEM> cache_item = this->cache[key];
132+
if (cache_item != nullptr && cache_item->should_clean_up(this->should_dispose_func)) {
133+
if (item_disposal_func != nullptr) {
134+
item_disposal_func->dispose(items[0]);
135+
}
136+
this->cache.erase(key);
137+
}
138+
}
139+
}
140+
void clean_up();
141+
};
142+
143+
#endif
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// This program is free software; you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License, version 2.0
5+
// (GPLv2), as published by the Free Software Foundation, with the
6+
// following additional permissions:
7+
//
8+
// This program is distributed with certain software that is licensed
9+
// under separate terms, as designated in a particular file or component
10+
// or in the license documentation. Without limiting your rights under
11+
// the GPLv2, the authors of this program hereby grant you an additional
12+
// permission to link the program and your derivative works with the
13+
// separately licensed software that they have included with the program.
14+
//
15+
// Without limiting the foregoing grant of rights under the GPLv2 and
16+
// additional permission as to separately licensed software, this
17+
// program is also subject to the Universal FOSS Exception, version 1.0,
18+
// a copy of which can be found along with its FAQ at
19+
// http://oss.oracle.com/licenses/universal-foss-exception.
20+
//
21+
// This program is distributed in the hope that it will be useful, but
22+
// WITHOUT ANY WARRANTY; without even the implied warranty of
23+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24+
// See the GNU General Public License, version 2.0, for more details.
25+
//
26+
// You should have received a copy of the GNU General Public License
27+
// along with this program. If not, see
28+
// http://www.gnu.org/licenses/gpl-2.0.html.
29+
30+
#include "sliding_expiration_cache_with_clean_up_thread.h"
31+
32+
#include <string>
33+
34+
template <class K, class V>
35+
void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::init_clean_up_thread() {
36+
if (!this->is_initialized) {
37+
std::unique_lock lock(mutex_);
38+
if (!this->is_initialized) {
39+
this->clean_up_thread_pool.resize(this->clean_up_thread_pool.size() + 1);
40+
41+
this->clean_up_thread_pool.push([=](int id) {
42+
while (!should_stop) {
43+
const std::chrono::nanoseconds clean_up_interval = std::chrono::nanoseconds(this->clean_up_interval_nanos);
44+
std::this_thread::sleep_for(clean_up_interval);
45+
this->clean_up_time_nanos.store(std::chrono::steady_clock::now() + clean_up_interval);
46+
std::vector<K> keys;
47+
keys.reserve(this->cache.size());
48+
for (auto& [key, cache_item] : this->cache) {
49+
keys.push_back(key);
50+
}
51+
for (const auto& key : keys) {
52+
this->remove_if_expired(key);
53+
}
54+
}
55+
});
56+
this->is_initialized = true;
57+
}
58+
}
59+
}
60+
61+
template <class K, class V>
62+
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD() {
63+
this->init_clean_up_thread();
64+
}
65+
66+
template <class K, class V>
67+
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(
68+
SHOULD_DISPOSE_FUNC<V>* should_dispose_func, ITEM_DISPOSAL_FUNC<V>* item_disposal_func)
69+
: SLIDING_EXPIRATION_CACHE<K, V>(should_dispose_func, item_disposal_func) {
70+
this->init_clean_up_thread();
71+
}
72+
73+
template <class K, class V>
74+
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(
75+
SHOULD_DISPOSE_FUNC<V>* should_dispose_func, ITEM_DISPOSAL_FUNC<V>* item_disposal_func,
76+
long long clean_up_interval_nanos)
77+
: SLIDING_EXPIRATION_CACHE<K, V>(should_dispose_func, item_disposal_func, clean_up_interval_nanos) {
78+
this->init_clean_up_thread();
79+
}
80+
81+
#ifdef UNIT_TEST_BUILD
82+
template <class K, class V>
83+
SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD(
84+
long long clean_up_interval_nanos) {
85+
this->clean_up_interval_nanos = clean_up_interval_nanos;
86+
this->init_clean_up_thread();
87+
}
88+
#endif
89+
90+
template <class K, class V>
91+
void SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<K, V>::release_resources() {
92+
this->should_stop = true;
93+
this->clean_up_thread_pool.stop(true);
94+
this->clean_up_thread_pool.resize(0);
95+
this->is_initialized = false;
96+
}
97+
98+
template class SLIDING_EXPIRATION_CACHE_WITH_CLEAN_UP_THREAD<std::string, std::string>;

0 commit comments

Comments
 (0)