Skip to content

Commit f55de47

Browse files
committed
Adds Cache Groups concepts to Cripts
- cleans up the notion around cached URLs and headers, and cache keys. - adds APIs to set the lookup status as well
1 parent 163056e commit f55de47

6 files changed

Lines changed: 782 additions & 0 deletions

File tree

doc/developer-guide/cripts/cripts-misc.en.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,63 @@ Debug logging uses the same format string syntax as ``fmt::format()`` in ``libfm
414414
debug tags in your ATS configuration to enable debug output for your Cripts.
415415
The default debug tag for Cripts is the name of the Cript itself, either
416416
the Cript source file, or the compiled plugin name.
417+
418+
Cache Groups
419+
============
420+
421+
As a way to manage assosication between cache entries, Cripts provides an infrastructure
422+
for cache groups. A cache group is a set of cache entries that are logically
423+
associated with each other via custom identifiers.
424+
425+
Example implementation of the Cache Groups RFC
426+
427+
.. code-block:: cpp
428+
429+
do_create_instance()
430+
{
431+
// Create a cache-group for this site / remap rule(s). They can be shared.
432+
instance.data[0] = cripts::Cache::Group::Manager::Factory("example_site");
433+
}
434+
435+
do_delete_instance()
436+
{
437+
void *ptr = AsPointer(instance.data[0]);
438+
439+
if (ptr) {
440+
delete static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
441+
instance.data[0] = nullptr;
442+
}
443+
}
444+
445+
do_cache_lookup()
446+
{
447+
if (cached.response.lookupstatus != cripts::LookupStatus::MISS) {
448+
void *ptr = AsPointer(instance.data[0]);
449+
450+
if (ptr) {
451+
auto date = cached.response.AsDate("Date");
452+
if (date > 0) {
453+
auto cache_groups = cached.response["Cache-Groups"];
454+
if (!cache_groups.empty()) {
455+
borrow cg = *static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
456+
if (cg->Lookup(cache_groups.split(','), date)) {
457+
cached.response.lookupstatus = cripts::LookupStatus::HIT_STALE;
458+
}
459+
}
460+
}
461+
}
462+
}
463+
}
464+
465+
do_read_response()
466+
{
467+
void *ptr = AsPointer(instance.data[0]);
468+
469+
if (ptr) {
470+
auto invalidation = client.request["Cache-Group-Invalidation"];
471+
if (!invalidation.empty()) {
472+
borrow cg = *static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
473+
cg->Insert(invalidation.split(','));
474+
}
475+
}
476+
}

example/cripts/cache_groups.cc

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
#define CRIPTS_CONVENIENCE_APIS 1
20+
21+
#include <cripts/CacheGroup.hpp>
22+
#include <cripts/Preamble.hpp>
23+
24+
do_create_instance()
25+
{
26+
// Create a cache-group for this site / remap rule(s). They can be shared.
27+
instance.data[0] = cripts::Cache::Group::Manager::Factory("example");
28+
}
29+
30+
do_delete_instance()
31+
{
32+
void *ptr = AsPointer(instance.data[0]);
33+
34+
if (ptr) {
35+
delete static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
36+
instance.data[0] = nullptr;
37+
}
38+
}
39+
40+
do_cache_lookup()
41+
{
42+
if (cached.response.lookupstatus != cripts::LookupStatus::MISS) {
43+
void *ptr = AsPointer(instance.data[0]);
44+
45+
if (ptr) {
46+
auto date = cached.response.AsDate("Date");
47+
48+
if (date > 0) {
49+
auto cache_groups = cached.response["Cache-Groups"];
50+
51+
CDebug("Looking up {}", cache_groups);
52+
if (!cache_groups.empty()) {
53+
borrow cg = *static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
54+
55+
if (cg->Lookup(cache_groups.split(','), date)) {
56+
CDebug("Cache Group hit, forcing revalidation for request");
57+
cached.response.lookupstatus = cripts::LookupStatus::HIT_STALE;
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
do_read_response()
66+
{
67+
void *ptr = AsPointer(instance.data[0]);
68+
69+
if (ptr) {
70+
auto invalidation = client.request["Cache-Group-Invalidation"];
71+
72+
if (!invalidation.empty()) {
73+
borrow cg = *static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
74+
75+
cg->Insert(invalidation.split(','));
76+
}
77+
}
78+
79+
// This is just for simulating origin responses that would include cache-groups.
80+
#if 0
81+
server.response["Cache-Groups"] = "\"foo\", \"bar\"";
82+
#endif
83+
}
84+
85+
// The RFC draft does not support / provide definitions for this. It is useful,
86+
// but should be protected with appropriate ACLs / authentication.
87+
#if 0
88+
do_remap()
89+
{
90+
void *ptr = AsPointer(instance.data[0]);
91+
92+
if (ptr && urls.pristine.path == ".well-known/Cache-Groups") {
93+
auto invalidation = client.request["Cache-Group-Invalidation"];
94+
95+
if (!invalidation.empty()) {
96+
borrow cg = *static_cast<std::shared_ptr<cripts::Cache::Group> *>(ptr);
97+
98+
cg->Insert(invalidation.split(','));
99+
CDebug("Forcing a cache miss for cache-groups: {}", invalidation);
100+
StatusCode(202);
101+
}
102+
}
103+
}
104+
#endif
105+
106+
#include <cripts/Epilogue.hpp>

include/cripts/CacheGroup.hpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
#pragma once
19+
20+
#include <unordered_map>
21+
#include <string>
22+
#include <vector>
23+
#include <chrono>
24+
#include <mutex>
25+
#include <shared_mutex>
26+
#include <fstream>
27+
#include <atomic>
28+
#include <memory>
29+
#include <cstdint>
30+
31+
#include "cripts/Context.hpp"
32+
#include "cripts/Time.hpp"
33+
34+
// Implemented in the .cc file
35+
int _cripts_cache_group_sync(TSCont cont, TSEvent event, void *edata);
36+
37+
namespace cripts::Cache
38+
{
39+
40+
class Group
41+
{
42+
private:
43+
using self_type = Group;
44+
45+
struct _Entry {
46+
cripts::Time::Point timestamp; // Timestamp of when the entry was created
47+
size_t length; // Length of the group ID
48+
uint32_t prefix; // First 4 characters of the group ID
49+
uint64_t hash; // Hash value of the group ID, needed when writing to disk
50+
};
51+
52+
using _MapType = std::unordered_map<uint64_t, _Entry>;
53+
54+
struct _MapSlot {
55+
std::unique_ptr<_MapType> map;
56+
std::string path;
57+
cripts::Time::Point created;
58+
cripts::Time::Point last_write;
59+
cripts::Time::Point last_sync;
60+
};
61+
62+
public:
63+
static constexpr uint64_t VERSION = (static_cast<uint64_t>('C') << 56) | (static_cast<uint64_t>('G') << 48) |
64+
(static_cast<uint64_t>('M') << 40) | (static_cast<uint64_t>('A') << 32) |
65+
(static_cast<uint64_t>('P') << 24) | (static_cast<uint64_t>('S') << 16) |
66+
(static_cast<uint64_t>('0') << 8) | 0x00; // Change this on version bump
67+
68+
Group(const std::string &name, const std::string &base_dir, size_t max_entries = 1024, size_t num_maps = 3)
69+
{
70+
Initialize(name, base_dir, num_maps, max_entries, std::chrono::seconds{63072000});
71+
}
72+
73+
// Not used at the moment.
74+
Group() = default;
75+
76+
~Group() { WriteToDisk(); }
77+
78+
Group(const self_type &) = delete;
79+
self_type &operator=(const self_type &) = delete;
80+
81+
void Initialize(const std::string &name, const std::string &base_dir, size_t num_maps = 3, size_t max_entries = 1024,
82+
std::chrono::seconds max_age = std::chrono::seconds{63072000});
83+
84+
void
85+
SetMaxEntries(size_t max_entries)
86+
{
87+
std::unique_lock lock(_mutex);
88+
_max_entries = max_entries;
89+
}
90+
91+
void
92+
SetMaxAge(std::chrono::seconds max_age)
93+
{
94+
std::unique_lock lock(_mutex);
95+
_max_age = max_age;
96+
}
97+
98+
void Insert(cripts::string_view key);
99+
void Insert(const std::vector<cripts::string_view> &keys);
100+
bool Lookup(cripts::string_view key, cripts::Time::Point age) const;
101+
bool Lookup(const std::vector<cripts::string_view> &keys, cripts::Time::Point age) const;
102+
103+
bool
104+
Lookup(cripts::string_view key, time_t age) const
105+
{
106+
return Lookup(key, cripts::Time::Clock::from_time_t(age));
107+
}
108+
109+
bool
110+
Lookup(const std::vector<cripts::string_view> &keys, time_t age) const
111+
{
112+
return Lookup(keys, cripts::Time::Clock::from_time_t(age));
113+
}
114+
115+
cripts::Time::Point
116+
LastSync() const
117+
{
118+
std::shared_lock lock(_mutex);
119+
return _last_sync;
120+
}
121+
122+
void WriteToDisk();
123+
void LoadFromDisk();
124+
125+
private:
126+
mutable std::shared_mutex _mutex;
127+
std::string _name = "CacheGroup";
128+
size_t _num_maps = 3;
129+
size_t _max_entries = 1024;
130+
std::chrono::seconds _max_age = std::chrono::seconds(63072000);
131+
std::atomic<size_t> _map_index = 0;
132+
cripts::Time::Point _last_sync = cripts::Time::Point{};
133+
134+
std::vector<_MapSlot> _slots;
135+
std::ofstream _txn_log;
136+
std::string _log_path;
137+
std::string _base_dir;
138+
139+
void appendLog(const _Entry &entry);
140+
void clearLog();
141+
void syncMap(size_t index);
142+
143+
public:
144+
class Manager
145+
{
146+
friend int ::_cripts_cache_group_sync(TSCont cont, TSEvent event, void *edata);
147+
using self_type = Manager;
148+
149+
public:
150+
static void *Factory(const std::string &name, size_t max_entries = 1024, size_t num_maps = 3);
151+
152+
Manager(const self_type &) = delete;
153+
self_type &operator=(const self_type &) = delete;
154+
155+
protected:
156+
void _scheduleCont();
157+
158+
std::unordered_map<std::string, std::weak_ptr<Group>> _groups;
159+
std::mutex _mutex;
160+
161+
private:
162+
Manager()
163+
{
164+
_base_dir = TSRuntimeDirGet();
165+
166+
if (std::filesystem::exists(_base_dir)) {
167+
_base_dir += "/cache_groups";
168+
if (!std::filesystem::exists(_base_dir)) {
169+
std::filesystem::create_directories(_base_dir);
170+
std::filesystem::permissions(_base_dir, std::filesystem::perms::group_write, std::filesystem::perm_options::add);
171+
}
172+
}
173+
_scheduleCont(); // Kick it off
174+
}
175+
176+
~Manager()
177+
{
178+
if (_action) {
179+
TSActionCancel(_action);
180+
_action = nullptr;
181+
}
182+
if (_cont) {
183+
TSContDestroy(_cont);
184+
_cont = nullptr;
185+
}
186+
}
187+
188+
static self_type &_instance();
189+
190+
TSCont _cont = nullptr;
191+
TSAction _action = nullptr;
192+
std::string _base_dir;
193+
};
194+
};
195+
} // namespace cripts::Cache

include/cripts/Matcher.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <algorithm>
2424
#include <vector>
2525
#include <tuple>
26+
#include <algorithm>
2627

2728
#include "cripts/Headers.hpp"
2829
#include "cripts/Lulu.hpp"

src/cripts/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ list(REMOVE_ITEM CPP_FILES ${TEST_CPP_FILES})
2828

2929
set(CRIPTS_PUBLIC_HEADERS
3030
${PROJECT_SOURCE_DIR}/include/cripts/Bundle.hpp
31+
${PROJECT_SOURCE_DIR}/include/cripts/CacheGroup.hpp
3132
${PROJECT_SOURCE_DIR}/include/cripts/Certs.hpp
3233
${PROJECT_SOURCE_DIR}/include/cripts/Configs.hpp
3334
${PROJECT_SOURCE_DIR}/include/cripts/ConfigsBase.hpp

0 commit comments

Comments
 (0)