Skip to content

Commit

Permalink
Merge pull request #422 from mmd-osm/patch/memcache_ttl
Browse files Browse the repository at this point in the history
memcached: add non-zero expiration value
  • Loading branch information
mmd-osm authored Jul 13, 2024
2 parents 3e16991 + 44f1424 commit 2bb72a5
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 48 deletions.
2 changes: 1 addition & 1 deletion include/cgimap/rate_limiter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class memcached_rate_limiter : public rate_limiter {
void update(const std::string &key, int bytes, bool moderator) override;

private:
memcached_st *ptr;
memcached_st *ptr = nullptr;

struct state;
};
Expand Down
110 changes: 63 additions & 47 deletions src/rate_limiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
*/

#include <vector>
#include <fmt/core.h>
#include <libmemcached/memcached.h>

#include "cgimap/logger.hpp"
#include "cgimap/options.hpp"
#include "cgimap/rate_limiter.hpp"

Expand All @@ -28,21 +30,23 @@ struct memcached_rate_limiter::state {

memcached_rate_limiter::memcached_rate_limiter(
const boost::program_options::variables_map &options) {
if (options.count("memcache") && (ptr = memcached_create(nullptr)) != nullptr) {
memcached_server_st *server_list;

if (!options.count("memcache"))
return;

if ((ptr = memcached_create(nullptr)) != nullptr) {

memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
// memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1);

server_list =
memcached_servers_parse(options["memcache"].as<std::string>().c_str());
const auto server = options["memcache"].as<std::string>();

memcached_server_st * server_list = memcached_servers_parse(server.c_str());
memcached_server_push(ptr, server_list);

memcached_server_list_free(server_list);
} else {
ptr = nullptr;

logger::message(fmt::format("memcached rate limiting enabled ({})", server));
}
}

Expand All @@ -52,22 +56,22 @@ memcached_rate_limiter::~memcached_rate_limiter() {
}

std::tuple<bool, int> memcached_rate_limiter::check(const std::string &key, bool moderator) {

uint32_t bytes_served = 0;
std::string mc_key;
state *sp;
size_t length;
uint32_t flags;
memcached_return error;
memcached_return_t error;

mc_key = "cgimap:" + key;
auto bytes_per_sec = global_settings::get_ratelimiter_ratelimit(moderator);
const auto mc_key = "cgimap:" + key;
const auto bytes_per_sec = global_settings::get_ratelimiter_ratelimit(moderator);

if (ptr &&
(sp = (state *)memcached_get(ptr, mc_key.data(), mc_key.size(), &length,
&flags, &error)) != nullptr) {
assert(length == sizeof(state));

int64_t elapsed = time(nullptr) - sp->last_update;
const int64_t elapsed = time(nullptr) - sp->last_update;

if (elapsed * bytes_per_sec < sp->bytes_served) {
bytes_served = sp->bytes_served - elapsed * bytes_per_sec;
Expand All @@ -76,7 +80,7 @@ std::tuple<bool, int> memcached_rate_limiter::check(const std::string &key, bool
free(sp);
}

auto max_bytes = global_settings::get_ratelimiter_maxdebt(moderator);
const auto max_bytes = global_settings::get_ratelimiter_maxdebt(moderator);
if (bytes_served < max_bytes) {
return {false, 0};
} else {
Expand All @@ -86,51 +90,63 @@ std::tuple<bool, int> memcached_rate_limiter::check(const std::string &key, bool
}

void memcached_rate_limiter::update(const std::string &key, int bytes, bool moderator) {
if (ptr) {
time_t now = time(nullptr);
std::string mc_key;
state *sp;
size_t length;
uint32_t flags;
memcached_return error;

mc_key = "cgimap:" + key;
auto bytes_per_sec = global_settings::get_ratelimiter_ratelimit(moderator);
if (!ptr)
return;

retry:
const auto now = time(nullptr);

if (ptr &&
(sp = (state *)memcached_get(ptr, mc_key.data(), mc_key.size(), &length,
&flags, &error)) != nullptr) {
assert(length == sizeof(state));
state *sp;
size_t length;
uint32_t flags;
memcached_return_t error;

int64_t elapsed = now - sp->last_update;
const auto mc_key = "cgimap:" + key;
const auto bytes_per_sec = global_settings::get_ratelimiter_ratelimit(moderator);

sp->last_update = now;
// upper limit in memcached for relative TTL values
// anything bigger is considered as absolute timestamp
constexpr auto REALTIME_MAXDELTA = 60L*60L*24L*30L;

if (elapsed * bytes_per_sec < sp->bytes_served) {
sp->bytes_served = sp->bytes_served - elapsed * bytes_per_sec + bytes;
} else {
sp->bytes_served = bytes;
}
// calculate number of seconds after which the memcached entry is guaranteed
// to be irrelevant (adding a bit of headroom).
const auto memcached_expiration = std::min(REALTIME_MAXDELTA,
2L * global_settings::get_ratelimiter_maxdebt(moderator) / bytes_per_sec);

// should use CAS but it's a right pain so we'll wing it for now...
memcached_replace(ptr, mc_key.data(), mc_key.size(), (char *)sp,
sizeof(state), 0, 0);
retry:

free(sp);
if (!ptr)
return;

if ((sp = (state *)memcached_get(ptr, mc_key.data(), mc_key.size(), &length,
&flags, &error)) != nullptr) {
assert(length == sizeof(state));

const int64_t elapsed = now - sp->last_update;

sp->last_update = now;

if (elapsed * bytes_per_sec < sp->bytes_served) {
sp->bytes_served = sp->bytes_served - elapsed * bytes_per_sec + bytes;
} else {
state s;
sp->bytes_served = bytes;
}

s.last_update = now;
s.bytes_served = bytes;
// should use CAS but it's a right pain so we'll wing it for now...
auto rc = memcached_replace(ptr, mc_key.data(), mc_key.size(), (char *)sp,
sizeof(state), memcached_expiration, 0);
free(sp);

if (memcached_add(ptr, mc_key.data(), mc_key.size(), (char *)&s,
sizeof(state), 0, 0) == MEMCACHED_NOTSTORED) {
goto retry;
}
} else {
state s;

s.last_update = now;
s.bytes_served = bytes;

auto rc = memcached_add(ptr, mc_key.data(), mc_key.size(), (char *)&s, sizeof(state), memcached_expiration, 0);

if (rc == MEMCACHED_NOTSTORED) {
goto retry;
}
}

return;
}

0 comments on commit 2bb72a5

Please sign in to comment.