Skip to content

Commit

Permalink
Expand redis service and refactor things into StringUtils STL
Browse files Browse the repository at this point in the history
  • Loading branch information
Oipo committed Jan 31, 2024
1 parent 4134f7d commit 5be95d0
Show file tree
Hide file tree
Showing 10 changed files with 931 additions and 184 deletions.
23 changes: 0 additions & 23 deletions include/ichor/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,29 +152,6 @@ namespace Ichor {
inline constexpr bool PreventOthersHandling = false;
inline constexpr bool AllowOthersHandling = true;

/// Code modified from https://stackoverflow.com/a/73078442/1460998
/// converts a string to an integer with little error checking. Only use if you're very sure that the string is actually a number.
static constexpr inline int64_t FastAtoi(const char* str) noexcept {
int64_t val = 0;
uint8_t x;
bool neg{};
if(str[0] == '-') {
str++;
neg = true;
}
while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x;
return neg ? -val : val;
}

/// Code from https://stackoverflow.com/a/73078442/1460998
/// converts a string to an unsigned integer with little error checking. Only use if you're very sure that the string is actually a number.
static constexpr inline uint64_t FastAtoiu(const char* str) noexcept {
uint64_t val = 0;
uint8_t x;
while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x;
return val;
}

// Code from https://artificial-mind.net/blog/2020/10/31/constexpr-for
template <auto Start, auto End, auto Inc, class F>
constexpr void constexpr_for(F&& f) {
Expand Down
31 changes: 24 additions & 7 deletions include/ichor/services/redis/HiredisService.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
#include <ichor/services/redis/IRedis.h>
#include <ichor/services/logging/Logger.h>
#include <ichor/services/timer/ITimerFactory.h>
#include <ichor/stl/StringUtils.h>
#include <hiredis/hiredis.h>

namespace Ichor {
/**
* Service for the redis protocol.
*
* Properties:
* - "Address" - What address to connect to (required)
* - "Port" - What port to connect to (required)
* - "PollIntervalMs" - with which interval in milliseconds to poll hiredis for responses. Lower values reduce latency at the cost of more CPU usage. (default: 10 ms)
* - "TryConnectIntervalMs" - with which interval in milliseconds to try (re)connecting (default: 10 ms)
* - "TimeoutMs" - with which interval in milliseconds to timeout for (re)connecting, after which the service stops itself (default: 10'000 ms)
* - "Address" std::string - What address to connect to (required)
* - "Port" uint16_t - What port to connect to (required)
* - "PollIntervalMs" uint64_t - with which interval in milliseconds to poll hiredis for responses. Lower values reduce latency at the cost of more CPU usage. (default: 10 ms)
* - "TryConnectIntervalMs" uint64_t - with which interval in milliseconds to try (re)connecting (default: 100 ms)
* - "TimeoutMs" uint64_t - with which interval in milliseconds to timeout for (re)connecting, after which the service stops itself (default: 10'000 ms)
* - "Debug" bool - Enable verbose logging of redis requests and responses (default: false)
*/
class HiredisService final : public IRedis, public AdvancedService<HiredisService> {
public:
Expand All @@ -26,17 +28,27 @@ namespace Ichor {
void onRedisConnect(int status);
void onRedisDisconnect(int status);

void setDebug(bool debug) noexcept;
[[nodiscard]] bool getDebug() const noexcept;

// see IRedis for function descriptions
Task<tl::expected<RedisAuthReply, RedisError>> auth(std::string_view user, std::string_view password) final;
Task<tl::expected<RedisSetReply, RedisError>> set(std::string_view key, std::string_view value) final;
Task<tl::expected<RedisSetReply, RedisError>> set(std::string_view key, std::string_view value, RedisSetOptions const &opts) final;
Task<tl::expected<RedisGetReply, RedisError>> get(std::string_view key) final;
Task<tl::expected<RedisGetReply, RedisError>> getdel(std::string_view key) final;
Task<tl::expected<RedisIntegerReply, RedisError>> del(std::string_view keys) final;
Task<tl::expected<RedisIntegerReply, RedisError>> incr(std::string_view keys) final;
Task<tl::expected<RedisIntegerReply, RedisError>> incrBy(std::string_view keys, int64_t incr) final;
Task<tl::expected<RedisIntegerReply, RedisError>> incrByFloat(std::string_view keys, double incr) final;
Task<tl::expected<RedisIntegerReply, RedisError>> decr(std::string_view keys) final;
Task<tl::expected<RedisIntegerReply, RedisError>> decrBy(std::string_view keys, int64_t decr) final;
Task<tl::expected<RedisIntegerReply, RedisError>> strlen(std::string_view key) final;
Task<tl::expected<void, RedisError>> multi() final;
Task<tl::expected<std::vector<std::variant<RedisGetReply, RedisSetReply, RedisAuthReply, RedisIntegerReply>>, RedisError>> exec() final;
Task<tl::expected<void, RedisError>> discard() final;
Task<tl::expected<std::unordered_map<std::string, std::string>, RedisError>> info() final;
Task<tl::expected<Version, RedisError>> getServerVersion() final;

private:
Task<tl::expected<void, Ichor::StartError>> start() final;
Expand All @@ -51,18 +63,23 @@ namespace Ichor {
void addDependencyInstance(IEventQueue &queue, IService&);
void removeDependencyInstance(IEventQueue &queue, IService&);

tl::expected<void, Ichor::StartError> connect();
tl::expected<void, Ichor::StartError> connect(std::string const &addr, uint16_t port);

friend DependencyRegister;

ILogger *_logger{};
redisAsyncContext *_redisContext{};
AsyncManualResetEvent _startEvt{};
AsyncManualResetEvent _disconnectEvt{};
ITimerFactory *_timerFactory{};
IEventQueue *_queue{};
ITimer *_timeoutTimer{};
std::vector<NameHashType> _queuedResponseTypes{};
bool _timeoutTimerRunning{};
bool _debug{};
tl::optional<Version> _redisVersion{};
uint64_t _pollIntervalMs{10};
uint64_t _tryConnectIntervalMs{10};
uint64_t _tryConnectIntervalMs{100};
uint64_t _timeoutMs{10'000};
uint64_t _timeWhenDisconnected{};
};
Expand Down
36 changes: 30 additions & 6 deletions include/ichor/services/redis/IRedis.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <ichor/coroutines/Task.h>
#include <ichor/stl/StringUtils.h>
#include <string_view>
#include <tl/optional.h>
#include <tl/expected.h>
Expand Down Expand Up @@ -37,7 +38,11 @@ namespace Ichor {
};

enum class RedisError : uint_fast16_t {
DISCONNECTED
UNKNOWN,
DISCONNECTED,
FUNCTION_NOT_AVAILABLE_IN_SERVER, // probably redis-server version too low
TRANSACTION_NOT_STARTED, // probably forgot to execute a multi command
QUEUED, // Queued for transaction, use exec() to get the result
};

class IRedis {
Expand Down Expand Up @@ -66,6 +71,11 @@ namespace Ichor {
/// \return coroutine with the reply from Redis
virtual Task<tl::expected<RedisGetReply, RedisError>> get(std::string_view key) = 0;

/// Get key and delete the key
/// \param key
/// \return coroutine with the reply from Redis
virtual Task<tl::expected<RedisGetReply, RedisError>> getdel(std::string_view key) = 0;

/// Deletes a key in redis
/// \param keys space-seperated list of keys to delete
/// \return coroutine with the number of deleted values
Expand Down Expand Up @@ -102,12 +112,26 @@ namespace Ichor {
/// Returns the length of the string
/// \param key
/// \return coroutine with the length of the value stored for the key
// virtual Task<tl::expected<RedisIntegerReply, RedisError>> strlen(std::string_view key, int64_t decr) = 0;
virtual Task<tl::expected<RedisIntegerReply, RedisError>> strlen(std::string_view key) = 0;

/// Start a transaction
/// \return coroutine with a possible error
virtual Task<tl::expected<void, RedisError>> multi() = 0;

/// Execute all commands in a transaction
/// \return coroutine with a possible error
virtual Task<tl::expected<std::vector<std::variant<RedisGetReply, RedisSetReply, RedisAuthReply, RedisIntegerReply>>, RedisError>> exec() = 0;

/// Abort a transaction
/// \return coroutine with a possible error
virtual Task<tl::expected<void, RedisError>> discard() = 0;

/// Returns information and statistics about the server
/// \return coroutine with the length of the value stored for the key
virtual Task<tl::expected<std::unordered_map<std::string, std::string>, RedisError>> info() = 0;

virtual Task<tl::expected<Version, RedisError>> getServerVersion() = 0;

// strlen
// multi
// exec
// discard
// getrange
// setrange
// append (multi?)
Expand Down
100 changes: 100 additions & 0 deletions include/ichor/stl/StringUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#include <vector>
#include <string_view>
#include <algorithm>
#include <tl/optional.h>
#include <fmt/core.h>

namespace Ichor {

/// Code modified from https://stackoverflow.com/a/73078442/1460998
/// converts a string to an integer with little error checking. Only use if you're very sure that the string is actually a number.
static constexpr inline int64_t FastAtoi(const char* str) noexcept {
int64_t val = 0;
uint8_t x;
bool neg{};
if(str[0] == '-') {
str++;
neg = true;
}
while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x;
return neg ? -val : val;
}

/// Code from https://stackoverflow.com/a/73078442/1460998
/// converts a string to an unsigned integer with little error checking. Only use if you're very sure that the string is actually a number.
static constexpr inline uint64_t FastAtoiu(const char* str) noexcept {
uint64_t val = 0;
uint8_t x;
while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x;
return val;
}

struct Version {
uint64_t major;
uint64_t minor;
uint64_t patch;

auto operator<=>(Version const &v) const = default;
};

// Taken from https://www.cppstories.com/2018/07/string-view-perf-followup/
static inline std::vector<std::string_view> split(std::string_view str, std::string_view delims) {
std::vector<std::string_view> output;
if(delims.size() == 1) {
auto count = std::count(str.cbegin(), str.cend(), delims[0]);
output.reserve(static_cast<uint64_t>(count));
}

for(auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last; first = second + 1) {
second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));

if(first != second) {
output.emplace_back(first, second - first);
}
}

return output;
}

static inline tl::optional<Version> parseStringAsVersion(std::string_view str) {
if(str.empty()) {
return {};
}

auto wrongLetterCount = std::count_if(str.cbegin(), str.cend(), [](char const c) {
return c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' && c != '6' && c != '7' && c != '8' && c != '9' && c != '.';
});

if(wrongLetterCount != 0) {
return {};
}

if(*str.crbegin() == '.') {
return {};
}

auto splitStr = split(str, ".");

if(splitStr.size() != 3) {
return {};
}

return Version{FastAtoiu(splitStr[0].data()), FastAtoiu(splitStr[1].data()), FastAtoiu(splitStr[2].data())};
}
}



template <>
struct fmt::formatter<Ichor::Version> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.end();
}

template <typename FormatContext>
auto format(const Ichor::Version& v, FormatContext& ctx) {
return fmt::format_to(ctx.out(), "{}.{}.{}", v.major, v.minor, v.patch);
}
};
1 change: 1 addition & 0 deletions src/services/etcd/EtcdV2Service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <ichor/dependency_management/DependencyRegister.h>
#include <ichor/events/RunFunctionEvent.h>
#include <ichor/ScopeGuard.h>
#include <ichor/stl/StringUtils.h>
#include <base64/base64.h>
#include <ichor/glaze.h>
#include <glaze/util/type_traits.hpp>
Expand Down
Loading

0 comments on commit 5be95d0

Please sign in to comment.