Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
cache.cpp
Go to the documentation of this file.
1#include <cache/cache.hpp>
2
3#include <boost/url/parse.hpp>
4#include <boost/url/url.hpp>
5
6#include <stdexcept>
7
8namespace asio = boost::asio;
9namespace urls = boost::urls;
10
11namespace Haio::Cdn {
12
14 Impl(asio::any_io_executor executor, size_t maxEntriesByIp, std::chrono::seconds window)
15 : flight(std::move(executor)), quota(maxEntriesByIp, window) {}
16
17 std::unique_ptr<CacheStore> store;
20};
21
22namespace {
23
24/** the scheme of the url picks the backend, the way it picks a bucket's reader */
25std::unique_ptr<CacheStore> storeFor(const CacheConfig& config, asio::any_io_executor executor) {
26 if (config.url.empty()) return makeMemoryStore(config.maxUsage, config.ttl);
27
28 const auto parsed = urls::parse_uri(config.url);
29 if (!parsed) throw std::runtime_error("cache url is not a url: " + config.url);
30
31 const std::string scheme{parsed->scheme()};
32 if (scheme == "file") {
33 const std::string host{parsed->host()};
34 const std::string path{parsed->path()};
35 if (host.empty() && (path.empty() || path == "/")) {
36 throw std::runtime_error("cache url has no directory: " + config.url);
37 }
38 return makeFileStore(std::filesystem::path(host + path), config.ttl, config.maxUsage);
39 }
40 if (scheme == "redis" || scheme == "rediss") {
41 return makeRedisStore(config.url, config.ttl, config.maxUsage, std::move(executor));
42 }
43 // the config already rejected anything else, so reaching here is a bug not a typo
44 throw std::runtime_error("unsupported cache scheme \"" + scheme + "\" in " + config.url);
45}
46
47}
48
49Cache::Cache(CacheConfig config, size_t maxEntriesByIp, boost::asio::any_io_executor executor) {
50 impl_ = std::make_shared<Impl>(executor, maxEntriesByIp, config.ttl);
51 // a ttl of zero keeps nothing, and still deduplicates
52 if (config.ttl.count() > 0) impl_->store = storeFor(config, std::move(executor));
53}
54
55bool Cache::enabled() const {
56 return impl_ && impl_->store != nullptr;
57}
58
59asio::awaitable<Result<CacheEntry>> Cache::fetch(std::string key, std::string client, Producer produce) {
60 if (!impl_) co_return co_await produce();
61
62 if (impl_->store) {
63 if (auto hit = co_await impl_->store->get(key)) co_return *std::move(hit);
64 }
65
66 // one producer for the key, whether or not what it makes is kept afterwards
67 co_return co_await impl_->flight.run(key, [&]() -> asio::awaitable<Result<CacheEntry>> {
68 auto made = co_await produce();
69 if (made && impl_->store && impl_->quota.mayStore(client)) {
70 co_await impl_->store->put(key, *made);
71 }
72 co_return made;
73 });
74}
75
76}
boost::asio::awaitable< Result< CacheEntry > > fetch(std::string key, std::string client, Producer produce)
Definition cache.cpp:59
std::function< boost::asio::awaitable< Result< CacheEntry > >()> Producer
bool enabled() const
Definition cache.cpp:55
std::unique_ptr< CacheStore > makeMemoryStore(size_t maxUsage, std::chrono::seconds ttl)
Definition memory.cpp:87
std::unique_ptr< CacheStore > makeRedisStore(std::string url, std::chrono::seconds ttl, size_t maxUsage, boost::asio::any_io_executor executor)
std::unique_ptr< CacheStore > makeFileStore(std::filesystem::path root, std::chrono::seconds ttl, size_t maxUsage)
Definition file.cpp:171
std::chrono::seconds ttl
std::expected< T, Error > Result
Detail::ClientQuota quota
Definition cache.cpp:19
std::unique_ptr< CacheStore > store
Definition cache.cpp:17
Impl(asio::any_io_executor executor, size_t maxEntriesByIp, std::chrono::seconds window)
Definition cache.cpp:14
Detail::SingleFlight flight
Definition cache.cpp:18