Commit ca1d31710568ce91cdc761fa880e9f1541d967d6
1 parent
602cf672
Generate shared and static libraries, one master header
Configure CMake to generate libredox.so and libredox_static.a, and have all examples use the dynamic library. This is how Redox should be used in practice, and greatly reduces the compilation time of the examples. Also renamed redox.[ch]pp to client.[ch]pp and created one master header redox.hpp for users to include. This header right now just includes client.hpp, command.hpp, and subscriber.hpp.
Showing
18 changed files
with
492 additions
and
456 deletions
CMakeLists.txt
| ... | ... | @@ -7,7 +7,7 @@ option(examples "Build all examples." ON) |
| 7 | 7 | |
| 8 | 8 | # Use RelWithDebInfo if no configuration specified |
| 9 | 9 | if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) |
| 10 | - set(CMAKE_BUILD_TYPE RelWithDebInfo) | |
| 10 | + set(CMAKE_BUILD_TYPE Release) | |
| 11 | 11 | endif(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) |
| 12 | 12 | |
| 13 | 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") |
| ... | ... | @@ -22,7 +22,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") |
| 22 | 22 | set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) |
| 23 | 23 | |
| 24 | 24 | set(SRC_CORE |
| 25 | - ${SRC_DIR}/redox.cpp | |
| 25 | + ${SRC_DIR}/client.cpp | |
| 26 | 26 | ${SRC_DIR}/command.cpp |
| 27 | 27 | ${SRC_DIR}/subscriber.cpp |
| 28 | 28 | ) |
| ... | ... | @@ -31,11 +31,24 @@ set(SRC_LOGGER ${SRC_DIR}/utils/logger.cpp) |
| 31 | 31 | |
| 32 | 32 | set(SRC_ALL ${SRC_CORE} ${SRC_LOGGER}) |
| 33 | 33 | |
| 34 | +include_directories(${SRC_DIR}) | |
| 35 | + | |
| 36 | +# --------------------------------------------------------- | |
| 37 | +# Linked libraries | |
| 38 | +# --------------------------------------------------------- | |
| 39 | + | |
| 40 | +set(LIB_REDOX redox hiredis ev pthread) | |
| 41 | + | |
| 34 | 42 | # --------------------------------------------------------- |
| 35 | -# Libraries | |
| 43 | +# Library generation | |
| 36 | 44 | # --------------------------------------------------------- |
| 37 | 45 | |
| 38 | -set(LIB_REDIS hiredis ev pthread) | |
| 46 | +if (library) | |
| 47 | + | |
| 48 | + add_library(redox SHARED ${SRC_ALL}) | |
| 49 | + add_library(redox_static STATIC ${SRC_ALL}) | |
| 50 | + | |
| 51 | +endif() | |
| 39 | 52 | |
| 40 | 53 | # --------------------------------------------------------- |
| 41 | 54 | # Test suite |
| ... | ... | @@ -44,11 +57,11 @@ if (tests) |
| 44 | 57 | |
| 45 | 58 | enable_testing() |
| 46 | 59 | find_package(GTest REQUIRED) |
| 47 | - include_directories(${GTEST_INCLUDE_DIRS}) | |
| 48 | 60 | |
| 49 | - add_executable(test_redox test/test.cpp ${SRC_ALL}) | |
| 61 | + add_executable(test_redox test/test.cpp) | |
| 50 | 62 | |
| 51 | - target_link_libraries(test_redox ${LIB_REDIS} gtest) | |
| 63 | + target_include_directories(test_redox PUBLIC ${GTEST_INCLUDE_DIRS}) | |
| 64 | + target_link_libraries(test_redox ${LIB_REDOX} gtest) | |
| 52 | 65 | |
| 53 | 66 | # So that we can run 'make test' |
| 54 | 67 | add_test(test_redox test_redox) |
| ... | ... | @@ -60,37 +73,37 @@ endif() |
| 60 | 73 | # --------------------------------------------------------- |
| 61 | 74 | if (examples) |
| 62 | 75 | |
| 63 | - add_executable(basic examples/basic.cpp ${SRC_ALL}) | |
| 64 | - target_link_libraries(basic ${LIB_REDIS}) | |
| 76 | + add_executable(basic examples/basic.cpp) | |
| 77 | + target_link_libraries(basic ${LIB_REDOX}) | |
| 65 | 78 | |
| 66 | - add_executable(basic_threaded examples/basic_threaded.cpp ${SRC_ALL}) | |
| 67 | - target_link_libraries(basic_threaded ${LIB_REDIS}) | |
| 79 | + add_executable(basic_threaded examples/basic_threaded.cpp) | |
| 80 | + target_link_libraries(basic_threaded ${LIB_REDOX}) | |
| 68 | 81 | |
| 69 | - add_executable(lpush_benchmark examples/lpush_benchmark.cpp ${SRC_ALL}) | |
| 70 | - target_link_libraries(lpush_benchmark ${LIB_REDIS}) | |
| 82 | + add_executable(lpush_benchmark examples/lpush_benchmark.cpp) | |
| 83 | + target_link_libraries(lpush_benchmark ${LIB_REDOX}) | |
| 71 | 84 | |
| 72 | - add_executable(speed_test_async examples/speed_test_async.cpp ${SRC_ALL}) | |
| 73 | - target_link_libraries(speed_test_async ${LIB_REDIS}) | |
| 85 | + add_executable(speed_test_async examples/speed_test_async.cpp) | |
| 86 | + target_link_libraries(speed_test_async ${LIB_REDOX}) | |
| 74 | 87 | |
| 75 | - add_executable(speed_test_sync examples/speed_test_sync.cpp ${SRC_ALL}) | |
| 76 | - target_link_libraries(speed_test_sync ${LIB_REDIS}) | |
| 88 | + add_executable(speed_test_sync examples/speed_test_sync.cpp) | |
| 89 | + target_link_libraries(speed_test_sync ${LIB_REDOX}) | |
| 77 | 90 | |
| 78 | - add_executable(speed_test_async_multi examples/speed_test_async_multi.cpp ${SRC_ALL}) | |
| 79 | - target_link_libraries(speed_test_async_multi ${LIB_REDIS}) | |
| 91 | + add_executable(speed_test_async_multi examples/speed_test_async_multi.cpp) | |
| 92 | + target_link_libraries(speed_test_async_multi ${LIB_REDOX}) | |
| 80 | 93 | |
| 81 | - add_executable(data_types examples/data_types.cpp ${SRC_ALL}) | |
| 82 | - target_link_libraries(data_types ${LIB_REDIS}) | |
| 94 | + add_executable(data_types examples/data_types.cpp) | |
| 95 | + target_link_libraries(data_types ${LIB_REDOX}) | |
| 83 | 96 | |
| 84 | - add_executable(multi_client examples/multi-client.cpp ${SRC_ALL}) | |
| 85 | - target_link_libraries(multi_client ${LIB_REDIS}) | |
| 97 | + add_executable(multi_client examples/multi-client.cpp) | |
| 98 | + target_link_libraries(multi_client ${LIB_REDOX}) | |
| 86 | 99 | |
| 87 | - add_executable(binary_data examples/binary_data.cpp ${SRC_ALL}) | |
| 88 | - target_link_libraries(binary_data ${LIB_REDIS}) | |
| 100 | + add_executable(binary_data examples/binary_data.cpp) | |
| 101 | + target_link_libraries(binary_data ${LIB_REDOX}) | |
| 89 | 102 | |
| 90 | - add_executable(pub_sub examples/pub_sub.cpp ${SRC_ALL}) | |
| 91 | - target_link_libraries(pub_sub ${LIB_REDIS}) | |
| 103 | + add_executable(pub_sub examples/pub_sub.cpp) | |
| 104 | + target_link_libraries(pub_sub ${LIB_REDOX}) | |
| 92 | 105 | |
| 93 | 106 | add_executable(speed_test_pubsub examples/speed_test_pubsub ${SRC_ALL}) |
| 94 | - target_link_libraries(speed_test_pubsub ${LIB_REDIS}) | |
| 107 | + target_link_libraries(speed_test_pubsub ${LIB_REDOX}) | |
| 95 | 108 | |
| 96 | 109 | endif() | ... | ... |
examples/basic.cpp
examples/basic_threaded.cpp
examples/binary_data.cpp
examples/data_types.cpp
examples/lpush_benchmark.cpp
examples/multi-client.cpp
examples/pub_sub.cpp
examples/speed_test_async.cpp
examples/speed_test_async_multi.cpp
examples/speed_test_pubsub.cpp
examples/speed_test_sync.cpp
src/redox.cpp renamed to src/client.cpp
src/client.hpp
0 โ 100644
| 1 | +/** | |
| 2 | +* Redox - A modern, asynchronous, and wicked fast C++11 client for Redis | |
| 3 | +* | |
| 4 | +* https://github.com/hmartiro/redox | |
| 5 | +* | |
| 6 | +* Copyright 2015 - Hayk Martirosyan <hayk.mart at gmail dot com> | |
| 7 | +* | |
| 8 | +* Licensed under the Apache License, Version 2.0 (the "License"); | |
| 9 | +* you may not use this file except in compliance with the License. | |
| 10 | +* You may obtain a copy of the License at | |
| 11 | +* | |
| 12 | +* http://www.apache.org/licenses/LICENSE-2.0 | |
| 13 | +* | |
| 14 | +* Unless required by applicable law or agreed to in writing, software | |
| 15 | +* distributed under the License is distributed on an "AS IS" BASIS, | |
| 16 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 17 | +* See the License for the specific language governing permissions and | |
| 18 | +* limitations under the License. | |
| 19 | +*/ | |
| 20 | + | |
| 21 | +#pragma once | |
| 22 | + | |
| 23 | +#include <iostream> | |
| 24 | +#include <functional> | |
| 25 | + | |
| 26 | +#include <thread> | |
| 27 | +#include <mutex> | |
| 28 | +#include <condition_variable> | |
| 29 | +#include <atomic> | |
| 30 | + | |
| 31 | +#include <string> | |
| 32 | +#include <queue> | |
| 33 | +#include <set> | |
| 34 | +#include <unordered_map> | |
| 35 | +#include <unordered_set> | |
| 36 | + | |
| 37 | +#include <hiredis/hiredis.h> | |
| 38 | +#include <hiredis/async.h> | |
| 39 | +#include <hiredis/adapters/libev.h> | |
| 40 | + | |
| 41 | +#include "utils/logger.hpp" | |
| 42 | +#include "command.hpp" | |
| 43 | + | |
| 44 | +namespace redox { | |
| 45 | + | |
| 46 | +static const std::string REDIS_DEFAULT_HOST = "localhost"; | |
| 47 | +static const int REDIS_DEFAULT_PORT = 6379; | |
| 48 | + | |
| 49 | +/** | |
| 50 | +* Redox intro here. | |
| 51 | +*/ | |
| 52 | +class Redox { | |
| 53 | + | |
| 54 | +public: | |
| 55 | + | |
| 56 | + // Connection states | |
| 57 | + static const int NOT_YET_CONNECTED = 0; | |
| 58 | + static const int CONNECTED = 1; | |
| 59 | + static const int DISCONNECTED = 2; | |
| 60 | + static const int CONNECT_ERROR = 3; | |
| 61 | + static const int DISCONNECT_ERROR = 4; | |
| 62 | + | |
| 63 | + // ------------------------------------------------ | |
| 64 | + // Core public API | |
| 65 | + // ------------------------------------------------ | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * Initializes everything, connects over TCP to a Redis server. | |
| 69 | + */ | |
| 70 | + Redox( | |
| 71 | + const std::string& host = REDIS_DEFAULT_HOST, | |
| 72 | + const int port = REDIS_DEFAULT_PORT, | |
| 73 | + std::function<void(int)> connection_callback = nullptr, | |
| 74 | + std::ostream& log_stream = std::cout, | |
| 75 | + log::Level log_level = log::Info | |
| 76 | + ); | |
| 77 | + | |
| 78 | + /** | |
| 79 | + * Initializes everything, connects over unix sockets to a Redis server. | |
| 80 | + */ | |
| 81 | + Redox( | |
| 82 | + const std::string& path, | |
| 83 | + std::function<void(int)> connection_callback, | |
| 84 | + std::ostream& log_stream = std::cout, | |
| 85 | + log::Level log_level = log::Info | |
| 86 | + ); | |
| 87 | + | |
| 88 | + /** | |
| 89 | + * Disconnects from the Redis server, shuts down the event loop, and cleans up. | |
| 90 | + * Internally calls disconnect() and wait(). | |
| 91 | + */ | |
| 92 | + ~Redox(); | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * Connects to Redis and starts an event loop in a separate thread. Returns | |
| 96 | + * true once everything is ready, or false on failure. | |
| 97 | + */ | |
| 98 | + bool connect(); | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * Signal the event loop thread to disconnect from Redis and shut down. | |
| 102 | + */ | |
| 103 | + void disconnect(); | |
| 104 | + | |
| 105 | + /** | |
| 106 | + * Blocks until the event loop exits and disconnection is complete, then returns. | |
| 107 | + * Usually no need to call manually as it is handled in the destructor. | |
| 108 | + */ | |
| 109 | + void wait(); | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * Asynchronously runs a command and invokes the callback when a reply is | |
| 113 | + * received or there is an error. The callback is guaranteed to be invoked | |
| 114 | + * exactly once. The Command object is provided to the callback, and the | |
| 115 | + * memory for it is automatically freed when the callback returns. | |
| 116 | + */ | |
| 117 | + template<class ReplyT> | |
| 118 | + void command( | |
| 119 | + const std::string& cmd, | |
| 120 | + const std::function<void(Command<ReplyT>&)>& callback = nullptr | |
| 121 | + ); | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * Asynchronously runs a command and ignores any errors or replies. | |
| 125 | + */ | |
| 126 | + void command(const std::string& cmd); | |
| 127 | + | |
| 128 | + /** | |
| 129 | + * Synchronously runs a command, returning the Command object only once | |
| 130 | + * a reply is received or there is an error. The user is responsible for | |
| 131 | + * calling .free() on the returned Command object. | |
| 132 | + */ | |
| 133 | + template<class ReplyT> | |
| 134 | + Command<ReplyT>& commandSync(const std::string& cmd); | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Synchronously runs a command, returning only once a reply is received | |
| 138 | + * or there's an error. Returns true on successful reply, false on error. | |
| 139 | + */ | |
| 140 | + bool commandSync(const std::string& cmd); | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * Creates an asynchronous command that is run every [repeat] seconds, | |
| 144 | + * with the first one run in [after] seconds. If [repeat] is 0, the | |
| 145 | + * command is run only once. The user is responsible for calling .free() | |
| 146 | + * on the returned Command object. | |
| 147 | + */ | |
| 148 | + template<class ReplyT> | |
| 149 | + Command<ReplyT>& commandLoop( | |
| 150 | + const std::string& cmd, | |
| 151 | + const std::function<void(Command<ReplyT>&)>& callback, | |
| 152 | + double repeat, | |
| 153 | + double after = 0.0 | |
| 154 | + ); | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * Creates an asynchronous command that is run once after a given | |
| 158 | + * delay. The callback is invoked exactly once on a successful reply | |
| 159 | + * or error, and the Command object memory is automatically freed | |
| 160 | + * after the callback returns. | |
| 161 | + */ | |
| 162 | + template<class ReplyT> | |
| 163 | + void commandDelayed( | |
| 164 | + const std::string& cmd, | |
| 165 | + const std::function<void(Command<ReplyT>&)>& callback, | |
| 166 | + double after | |
| 167 | + ); | |
| 168 | + | |
| 169 | + // ------------------------------------------------ | |
| 170 | + // Wrapper methods for convenience only | |
| 171 | + // ------------------------------------------------ | |
| 172 | + | |
| 173 | + /** | |
| 174 | + * Redis GET command wrapper - return the value for the given key, or throw | |
| 175 | + * an exception if there is an error. Blocking call. | |
| 176 | + */ | |
| 177 | + std::string get(const std::string& key); | |
| 178 | + | |
| 179 | + /** | |
| 180 | + * Redis SET command wrapper - set the value for the given key. Return | |
| 181 | + * true if succeeded, false if error. Blocking call. | |
| 182 | + */ | |
| 183 | + bool set(const std::string& key, const std::string& value); | |
| 184 | + | |
| 185 | + /** | |
| 186 | + * Redis DEL command wrapper - delete the given key. Return true if succeeded, | |
| 187 | + * false if error. Blocking call. | |
| 188 | + */ | |
| 189 | + bool del(const std::string& key); | |
| 190 | + | |
| 191 | + /** | |
| 192 | + * Redis PUBLISH command wrapper - publish the given message to all subscribers. | |
| 193 | + * Non-blocking call. | |
| 194 | + */ | |
| 195 | + void publish(const std::string& topic, const std::string& msg); | |
| 196 | + | |
| 197 | + // ------------------------------------------------ | |
| 198 | + // Public members | |
| 199 | + // ------------------------------------------------ | |
| 200 | + | |
| 201 | + // Hiredis context, left public to allow low-level access | |
| 202 | + redisAsyncContext * ctx_; | |
| 203 | + | |
| 204 | + // Redox server over TCP | |
| 205 | + const std::string host_; | |
| 206 | + const int port_; | |
| 207 | + | |
| 208 | + // Redox server over unix | |
| 209 | + const std::string path_; | |
| 210 | + | |
| 211 | + // Logger | |
| 212 | + log::Logger logger_; | |
| 213 | + | |
| 214 | +private: | |
| 215 | + | |
| 216 | + // ------------------------------------------------ | |
| 217 | + // Private methods | |
| 218 | + // ------------------------------------------------ | |
| 219 | + | |
| 220 | + // One stop shop for creating commands. The base of all public | |
| 221 | + // methods that run commands. | |
| 222 | + template<class ReplyT> | |
| 223 | + Command<ReplyT>& createCommand( | |
| 224 | + const std::string& cmd, | |
| 225 | + const std::function<void(Command<ReplyT>&)>& callback = nullptr, | |
| 226 | + double repeat = 0.0, | |
| 227 | + double after = 0.0, | |
| 228 | + bool free_memory = true | |
| 229 | + ); | |
| 230 | + | |
| 231 | + // Setup code for the constructors | |
| 232 | + void init_ev(); | |
| 233 | + void init_hiredis(); | |
| 234 | + | |
| 235 | + // Callbacks invoked on server connection/disconnection | |
| 236 | + static void connectedCallback(const redisAsyncContext* c, int status); | |
| 237 | + static void disconnectedCallback(const redisAsyncContext* c, int status); | |
| 238 | + | |
| 239 | + // Main event loop, run in a separate thread | |
| 240 | + void runEventLoop(); | |
| 241 | + | |
| 242 | + // Return the command map corresponding to the templated reply type | |
| 243 | + template<class ReplyT> | |
| 244 | + std::unordered_map<long, Command<ReplyT>*>& getCommandMap(); | |
| 245 | + | |
| 246 | + // Return the given Command from the relevant command map, or nullptr if not there | |
| 247 | + template<class ReplyT> | |
| 248 | + Command<ReplyT>* findCommand(long id); | |
| 249 | + | |
| 250 | + // Send all commands in the command queue to the server | |
| 251 | + static void processQueuedCommands(struct ev_loop* loop, ev_async* async, int revents); | |
| 252 | + | |
| 253 | + // Process the command with the given ID. Return true if the command had the | |
| 254 | + // templated type, and false if it was not in the command map of that type. | |
| 255 | + template<class ReplyT> | |
| 256 | + bool processQueuedCommand(long id); | |
| 257 | + | |
| 258 | + // Callback given to libev for a Command's timer watcher, to be processed in | |
| 259 | + // a deferred or looping state | |
| 260 | + template<class ReplyT> | |
| 261 | + static void submitCommandCallback(struct ev_loop* loop, ev_timer* timer, int revents); | |
| 262 | + | |
| 263 | + // Submit an asynchronous command to the Redox server. Return | |
| 264 | + // true if succeeded, false otherwise. | |
| 265 | + template<class ReplyT> | |
| 266 | + static bool submitToServer(Command<ReplyT>* c); | |
| 267 | + | |
| 268 | + // Callback given to hiredis to invoke when a reply is received | |
| 269 | + template<class ReplyT> | |
| 270 | + static void commandCallback(redisAsyncContext* ctx, void* r, void* privdata); | |
| 271 | + | |
| 272 | + // Free all commands in the commands_to_free_ queue | |
| 273 | + static void freeQueuedCommands(struct ev_loop* loop, ev_async* async, int revents); | |
| 274 | + | |
| 275 | + // Free the command with the given ID. Return true if the command had the templated | |
| 276 | + // type, and false if it was not in the command map of that type. | |
| 277 | + template<class ReplyT> | |
| 278 | + bool freeQueuedCommand(long id); | |
| 279 | + | |
| 280 | + // Invoked by Command objects when they are completed. Removes them | |
| 281 | + // from the command map. | |
| 282 | + template<class ReplyT> | |
| 283 | + void deregisterCommand(const long id) { | |
| 284 | + std::lock_guard<std::mutex> lg1(command_map_guard_); | |
| 285 | + getCommandMap<ReplyT>().erase(id); | |
| 286 | + commands_deleted_ += 1; | |
| 287 | + } | |
| 288 | + | |
| 289 | + // Free all commands remaining in the command maps | |
| 290 | + long freeAllCommands(); | |
| 291 | + | |
| 292 | + // Helper function for freeAllCommands to access a specific command map | |
| 293 | + template<class ReplyT> | |
| 294 | + long freeAllCommandsOfType(); | |
| 295 | + | |
| 296 | + // ------------------------------------------------ | |
| 297 | + // Private members | |
| 298 | + // ------------------------------------------------ | |
| 299 | + | |
| 300 | + // Manage connection state | |
| 301 | + std::atomic_int connect_state_ = {NOT_YET_CONNECTED}; | |
| 302 | + std::mutex connect_lock_; | |
| 303 | + std::condition_variable connect_waiter_; | |
| 304 | + | |
| 305 | + // User connect/disconnect callbacks | |
| 306 | + std::function<void(int)> user_connection_callback_; | |
| 307 | + | |
| 308 | + // Dynamically allocated libev event loop | |
| 309 | + struct ev_loop* evloop_; | |
| 310 | + | |
| 311 | + // Asynchronous watchers | |
| 312 | + ev_async watcher_command_; // For processing commands | |
| 313 | + ev_async watcher_stop_; // For breaking the loop | |
| 314 | + ev_async watcher_free_; // For freeing commands | |
| 315 | + | |
| 316 | + // Track of Command objects allocated. Also provides unique Command IDs. | |
| 317 | + std::atomic_long commands_created_ = {0}; | |
| 318 | + std::atomic_long commands_deleted_ = {0}; | |
| 319 | + | |
| 320 | + // Separate thread to have a non-blocking event loop | |
| 321 | + std::thread event_loop_thread_; | |
| 322 | + | |
| 323 | + // Variable and CV to know when the event loop starts running | |
| 324 | + std::atomic_bool running_ = {false}; | |
| 325 | + std::mutex running_waiter_lock_; | |
| 326 | + std::condition_variable running_waiter_; | |
| 327 | + | |
| 328 | + // Variable and CV to know when the event loop stops running | |
| 329 | + std::atomic_bool to_exit_ = {false}; // Signal to exit | |
| 330 | + std::atomic_bool exited_ = {false}; // Event thread exited | |
| 331 | + std::mutex exit_waiter_lock_; | |
| 332 | + std::condition_variable exit_waiter_; | |
| 333 | + | |
| 334 | + // Maps of each Command, fetchable by the unique ID number | |
| 335 | + // In C++14, member variable templates will replace all of these types | |
| 336 | + // with a single templated declaration | |
| 337 | + // --------- | |
| 338 | + // template<class ReplyT> | |
| 339 | + // std::unordered_map<long, Command<ReplyT>*> commands_; | |
| 340 | + // --------- | |
| 341 | + std::unordered_map<long, Command<redisReply*>*> commands_redis_reply_; | |
| 342 | + std::unordered_map<long, Command<std::string>*> commands_string_; | |
| 343 | + std::unordered_map<long, Command<char*>*> commands_char_p_; | |
| 344 | + std::unordered_map<long, Command<int>*> commands_int_; | |
| 345 | + std::unordered_map<long, Command<long long int>*> commands_long_long_int_; | |
| 346 | + std::unordered_map<long, Command<std::nullptr_t>*> commands_null_; | |
| 347 | + std::unordered_map<long, Command<std::vector<std::string>>*> commands_vector_string_; | |
| 348 | + std::unordered_map<long, Command<std::set<std::string>>*> commands_set_string_; | |
| 349 | + std::unordered_map<long, Command<std::unordered_set<std::string>>*> commands_unordered_set_string_; | |
| 350 | + std::mutex command_map_guard_; // Guards access to all of the above | |
| 351 | + | |
| 352 | + // Command IDs pending to be sent to the server | |
| 353 | + std::queue<long> command_queue_; | |
| 354 | + std::mutex queue_guard_; | |
| 355 | + | |
| 356 | + // Commands IDs pending to be freed by the event loop | |
| 357 | + std::queue<long> commands_to_free_; | |
| 358 | + std::mutex free_queue_guard_; | |
| 359 | + | |
| 360 | + // Commands use this method to deregister themselves from Redox, | |
| 361 | + // give it access to private members | |
| 362 | + template<class ReplyT> | |
| 363 | + friend void Command<ReplyT>::free(); | |
| 364 | +}; | |
| 365 | + | |
| 366 | +// ------------------------------------------------ | |
| 367 | +// Implementation of templated methods | |
| 368 | +// ------------------------------------------------ | |
| 369 | + | |
| 370 | +template<class ReplyT> | |
| 371 | +Command<ReplyT>& Redox::createCommand( | |
| 372 | + const std::string& cmd, | |
| 373 | + const std::function<void(Command<ReplyT>&)>& callback, | |
| 374 | + double repeat, | |
| 375 | + double after, | |
| 376 | + bool free_memory | |
| 377 | +) { | |
| 378 | + | |
| 379 | + if(!running_) { | |
| 380 | + throw std::runtime_error("[ERROR] Need to connect Redox before running commands!"); | |
| 381 | + } | |
| 382 | + | |
| 383 | + commands_created_ += 1; | |
| 384 | + auto* c = new Command<ReplyT>( | |
| 385 | + this, commands_created_, cmd, | |
| 386 | + callback, repeat, after, free_memory, logger_ | |
| 387 | + ); | |
| 388 | + | |
| 389 | + std::lock_guard<std::mutex> lg(queue_guard_); | |
| 390 | + std::lock_guard<std::mutex> lg2(command_map_guard_); | |
| 391 | + | |
| 392 | + getCommandMap<ReplyT>()[c->id_] = c; | |
| 393 | + command_queue_.push(c->id_); | |
| 394 | + | |
| 395 | + // Signal the event loop to process this command | |
| 396 | + ev_async_send(evloop_, &watcher_command_); | |
| 397 | + | |
| 398 | + return *c; | |
| 399 | +} | |
| 400 | + | |
| 401 | +template<class ReplyT> | |
| 402 | +void Redox::command( | |
| 403 | + const std::string& cmd, | |
| 404 | + const std::function<void(Command<ReplyT>&)>& callback | |
| 405 | +) { | |
| 406 | + createCommand(cmd, callback); | |
| 407 | +} | |
| 408 | + | |
| 409 | +template<class ReplyT> | |
| 410 | +Command<ReplyT>& Redox::commandLoop( | |
| 411 | + const std::string& cmd, | |
| 412 | + const std::function<void(Command<ReplyT>&)>& callback, | |
| 413 | + double repeat, | |
| 414 | + double after | |
| 415 | +) { | |
| 416 | + return createCommand(cmd, callback, repeat, after, false); | |
| 417 | +} | |
| 418 | + | |
| 419 | +template<class ReplyT> | |
| 420 | +void Redox::commandDelayed( | |
| 421 | + const std::string& cmd, | |
| 422 | + const std::function<void(Command<ReplyT>&)>& callback, | |
| 423 | + double after | |
| 424 | +) { | |
| 425 | + createCommand(cmd, callback, 0, after, true); | |
| 426 | +} | |
| 427 | + | |
| 428 | +template<class ReplyT> | |
| 429 | +Command<ReplyT>& Redox::commandSync(const std::string& cmd) { | |
| 430 | + auto& c = createCommand<ReplyT>(cmd, nullptr, 0, 0, false); | |
| 431 | + c.wait(); | |
| 432 | + return c; | |
| 433 | +} | |
| 434 | + | |
| 435 | +} // End namespace redis | ... | ... |
src/command.cpp
src/redox.hpp
| ... | ... | @@ -20,416 +20,6 @@ |
| 20 | 20 | |
| 21 | 21 | #pragma once |
| 22 | 22 | |
| 23 | -#include <iostream> | |
| 24 | -#include <functional> | |
| 25 | - | |
| 26 | -#include <thread> | |
| 27 | -#include <mutex> | |
| 28 | -#include <condition_variable> | |
| 29 | -#include <atomic> | |
| 30 | - | |
| 31 | -#include <string> | |
| 32 | -#include <queue> | |
| 33 | -#include <set> | |
| 34 | -#include <unordered_map> | |
| 35 | -#include <unordered_set> | |
| 36 | - | |
| 37 | -#include <hiredis/hiredis.h> | |
| 38 | -#include <hiredis/async.h> | |
| 39 | -#include <hiredis/adapters/libev.h> | |
| 40 | - | |
| 41 | -#include "utils/logger.hpp" | |
| 23 | +#include "client.hpp" | |
| 42 | 24 | #include "command.hpp" |
| 43 | - | |
| 44 | -namespace redox { | |
| 45 | - | |
| 46 | -static const std::string REDIS_DEFAULT_HOST = "localhost"; | |
| 47 | -static const int REDIS_DEFAULT_PORT = 6379; | |
| 48 | - | |
| 49 | -/** | |
| 50 | -* Redox intro here. | |
| 51 | -*/ | |
| 52 | -class Redox { | |
| 53 | - | |
| 54 | -public: | |
| 55 | - | |
| 56 | - // Connection states | |
| 57 | - static const int NOT_YET_CONNECTED = 0; | |
| 58 | - static const int CONNECTED = 1; | |
| 59 | - static const int DISCONNECTED = 2; | |
| 60 | - static const int CONNECT_ERROR = 3; | |
| 61 | - static const int DISCONNECT_ERROR = 4; | |
| 62 | - | |
| 63 | - // ------------------------------------------------ | |
| 64 | - // Core public API | |
| 65 | - // ------------------------------------------------ | |
| 66 | - | |
| 67 | - /** | |
| 68 | - * Initializes everything, connects over TCP to a Redis server. | |
| 69 | - */ | |
| 70 | - Redox( | |
| 71 | - const std::string& host = REDIS_DEFAULT_HOST, | |
| 72 | - const int port = REDIS_DEFAULT_PORT, | |
| 73 | - std::function<void(int)> connection_callback = nullptr, | |
| 74 | - std::ostream& log_stream = std::cout, | |
| 75 | - log::Level log_level = log::Info | |
| 76 | - ); | |
| 77 | - | |
| 78 | - /** | |
| 79 | - * Initializes everything, connects over unix sockets to a Redis server. | |
| 80 | - */ | |
| 81 | - Redox( | |
| 82 | - const std::string& path, | |
| 83 | - std::function<void(int)> connection_callback, | |
| 84 | - std::ostream& log_stream = std::cout, | |
| 85 | - log::Level log_level = log::Info | |
| 86 | - ); | |
| 87 | - | |
| 88 | - /** | |
| 89 | - * Disconnects from the Redis server, shuts down the event loop, and cleans up. | |
| 90 | - * Internally calls disconnect() and wait(). | |
| 91 | - */ | |
| 92 | - ~Redox(); | |
| 93 | - | |
| 94 | - /** | |
| 95 | - * Connects to Redis and starts an event loop in a separate thread. Returns | |
| 96 | - * true once everything is ready, or false on failure. | |
| 97 | - */ | |
| 98 | - bool connect(); | |
| 99 | - | |
| 100 | - /** | |
| 101 | - * Signal the event loop thread to disconnect from Redis and shut down. | |
| 102 | - */ | |
| 103 | - void disconnect(); | |
| 104 | - | |
| 105 | - /** | |
| 106 | - * Blocks until the event loop exits and disconnection is complete, then returns. | |
| 107 | - * Usually no need to call manually as it is handled in the destructor. | |
| 108 | - */ | |
| 109 | - void wait(); | |
| 110 | - | |
| 111 | - /** | |
| 112 | - * Asynchronously runs a command and invokes the callback when a reply is | |
| 113 | - * received or there is an error. The callback is guaranteed to be invoked | |
| 114 | - * exactly once. The Command object is provided to the callback, and the | |
| 115 | - * memory for it is automatically freed when the callback returns. | |
| 116 | - */ | |
| 117 | - template<class ReplyT> | |
| 118 | - void command( | |
| 119 | - const std::string& cmd, | |
| 120 | - const std::function<void(Command<ReplyT>&)>& callback = nullptr | |
| 121 | - ); | |
| 122 | - | |
| 123 | - /** | |
| 124 | - * Asynchronously runs a command and ignores any errors or replies. | |
| 125 | - */ | |
| 126 | - void command(const std::string& cmd); | |
| 127 | - | |
| 128 | - /** | |
| 129 | - * Synchronously runs a command, returning the Command object only once | |
| 130 | - * a reply is received or there is an error. The user is responsible for | |
| 131 | - * calling .free() on the returned Command object. | |
| 132 | - */ | |
| 133 | - template<class ReplyT> | |
| 134 | - Command<ReplyT>& commandSync(const std::string& cmd); | |
| 135 | - | |
| 136 | - /** | |
| 137 | - * Synchronously runs a command, returning only once a reply is received | |
| 138 | - * or there's an error. Returns true on successful reply, false on error. | |
| 139 | - */ | |
| 140 | - bool commandSync(const std::string& cmd); | |
| 141 | - | |
| 142 | - /** | |
| 143 | - * Creates an asynchronous command that is run every [repeat] seconds, | |
| 144 | - * with the first one run in [after] seconds. If [repeat] is 0, the | |
| 145 | - * command is run only once. The user is responsible for calling .free() | |
| 146 | - * on the returned Command object. | |
| 147 | - */ | |
| 148 | - template<class ReplyT> | |
| 149 | - Command<ReplyT>& commandLoop( | |
| 150 | - const std::string& cmd, | |
| 151 | - const std::function<void(Command<ReplyT>&)>& callback, | |
| 152 | - double repeat, | |
| 153 | - double after = 0.0 | |
| 154 | - ); | |
| 155 | - | |
| 156 | - /** | |
| 157 | - * Creates an asynchronous command that is run once after a given | |
| 158 | - * delay. The callback is invoked exactly once on a successful reply | |
| 159 | - * or error, and the Command object memory is automatically freed | |
| 160 | - * after the callback returns. | |
| 161 | - */ | |
| 162 | - template<class ReplyT> | |
| 163 | - void commandDelayed( | |
| 164 | - const std::string& cmd, | |
| 165 | - const std::function<void(Command<ReplyT>&)>& callback, | |
| 166 | - double after | |
| 167 | - ); | |
| 168 | - | |
| 169 | - // ------------------------------------------------ | |
| 170 | - // Wrapper methods for convenience only | |
| 171 | - // ------------------------------------------------ | |
| 172 | - | |
| 173 | - /** | |
| 174 | - * Redis GET command wrapper - return the value for the given key, or throw | |
| 175 | - * an exception if there is an error. Blocking call. | |
| 176 | - */ | |
| 177 | - std::string get(const std::string& key); | |
| 178 | - | |
| 179 | - /** | |
| 180 | - * Redis SET command wrapper - set the value for the given key. Return | |
| 181 | - * true if succeeded, false if error. Blocking call. | |
| 182 | - */ | |
| 183 | - bool set(const std::string& key, const std::string& value); | |
| 184 | - | |
| 185 | - /** | |
| 186 | - * Redis DEL command wrapper - delete the given key. Return true if succeeded, | |
| 187 | - * false if error. Blocking call. | |
| 188 | - */ | |
| 189 | - bool del(const std::string& key); | |
| 190 | - | |
| 191 | - /** | |
| 192 | - * Redis PUBLISH command wrapper - publish the given message to all subscribers. | |
| 193 | - * Non-blocking call. | |
| 194 | - */ | |
| 195 | - void publish(const std::string& topic, const std::string& msg); | |
| 196 | - | |
| 197 | - // ------------------------------------------------ | |
| 198 | - // Public members | |
| 199 | - // ------------------------------------------------ | |
| 200 | - | |
| 201 | - // Hiredis context, left public to allow low-level access | |
| 202 | - redisAsyncContext * ctx_; | |
| 203 | - | |
| 204 | - // Redox server over TCP | |
| 205 | - const std::string host_; | |
| 206 | - const int port_; | |
| 207 | - | |
| 208 | - // Redox server over unix | |
| 209 | - const std::string path_; | |
| 210 | - | |
| 211 | - // Logger | |
| 212 | - log::Logger logger_; | |
| 213 | - | |
| 214 | -private: | |
| 215 | - | |
| 216 | - // ------------------------------------------------ | |
| 217 | - // Private methods | |
| 218 | - // ------------------------------------------------ | |
| 219 | - | |
| 220 | - // One stop shop for creating commands. The base of all public | |
| 221 | - // methods that run commands. | |
| 222 | - template<class ReplyT> | |
| 223 | - Command<ReplyT>& createCommand( | |
| 224 | - const std::string& cmd, | |
| 225 | - const std::function<void(Command<ReplyT>&)>& callback = nullptr, | |
| 226 | - double repeat = 0.0, | |
| 227 | - double after = 0.0, | |
| 228 | - bool free_memory = true | |
| 229 | - ); | |
| 230 | - | |
| 231 | - // Setup code for the constructors | |
| 232 | - void init_ev(); | |
| 233 | - void init_hiredis(); | |
| 234 | - | |
| 235 | - // Callbacks invoked on server connection/disconnection | |
| 236 | - static void connectedCallback(const redisAsyncContext* c, int status); | |
| 237 | - static void disconnectedCallback(const redisAsyncContext* c, int status); | |
| 238 | - | |
| 239 | - // Main event loop, run in a separate thread | |
| 240 | - void runEventLoop(); | |
| 241 | - | |
| 242 | - // Return the command map corresponding to the templated reply type | |
| 243 | - template<class ReplyT> | |
| 244 | - std::unordered_map<long, Command<ReplyT>*>& getCommandMap(); | |
| 245 | - | |
| 246 | - // Return the given Command from the relevant command map, or nullptr if not there | |
| 247 | - template<class ReplyT> | |
| 248 | - Command<ReplyT>* findCommand(long id); | |
| 249 | - | |
| 250 | - // Send all commands in the command queue to the server | |
| 251 | - static void processQueuedCommands(struct ev_loop* loop, ev_async* async, int revents); | |
| 252 | - | |
| 253 | - // Process the command with the given ID. Return true if the command had the | |
| 254 | - // templated type, and false if it was not in the command map of that type. | |
| 255 | - template<class ReplyT> | |
| 256 | - bool processQueuedCommand(long id); | |
| 257 | - | |
| 258 | - // Callback given to libev for a Command's timer watcher, to be processed in | |
| 259 | - // a deferred or looping state | |
| 260 | - template<class ReplyT> | |
| 261 | - static void submitCommandCallback(struct ev_loop* loop, ev_timer* timer, int revents); | |
| 262 | - | |
| 263 | - // Submit an asynchronous command to the Redox server. Return | |
| 264 | - // true if succeeded, false otherwise. | |
| 265 | - template<class ReplyT> | |
| 266 | - static bool submitToServer(Command<ReplyT>* c); | |
| 267 | - | |
| 268 | - // Callback given to hiredis to invoke when a reply is received | |
| 269 | - template<class ReplyT> | |
| 270 | - static void commandCallback(redisAsyncContext* ctx, void* r, void* privdata); | |
| 271 | - | |
| 272 | - // Free all commands in the commands_to_free_ queue | |
| 273 | - static void freeQueuedCommands(struct ev_loop* loop, ev_async* async, int revents); | |
| 274 | - | |
| 275 | - // Free the command with the given ID. Return true if the command had the templated | |
| 276 | - // type, and false if it was not in the command map of that type. | |
| 277 | - template<class ReplyT> | |
| 278 | - bool freeQueuedCommand(long id); | |
| 279 | - | |
| 280 | - // Invoked by Command objects when they are completed. Removes them | |
| 281 | - // from the command map. | |
| 282 | - template<class ReplyT> | |
| 283 | - void deregisterCommand(const long id) { | |
| 284 | - std::lock_guard<std::mutex> lg1(command_map_guard_); | |
| 285 | - getCommandMap<ReplyT>().erase(id); | |
| 286 | - commands_deleted_ += 1; | |
| 287 | - } | |
| 288 | - | |
| 289 | - // Free all commands remaining in the command maps | |
| 290 | - long freeAllCommands(); | |
| 291 | - | |
| 292 | - // Helper function for freeAllCommands to access a specific command map | |
| 293 | - template<class ReplyT> | |
| 294 | - long freeAllCommandsOfType(); | |
| 295 | - | |
| 296 | - // ------------------------------------------------ | |
| 297 | - // Private members | |
| 298 | - // ------------------------------------------------ | |
| 299 | - | |
| 300 | - // Manage connection state | |
| 301 | - std::atomic_int connect_state_ = {NOT_YET_CONNECTED}; | |
| 302 | - std::mutex connect_lock_; | |
| 303 | - std::condition_variable connect_waiter_; | |
| 304 | - | |
| 305 | - // User connect/disconnect callbacks | |
| 306 | - std::function<void(int)> user_connection_callback_; | |
| 307 | - | |
| 308 | - // Dynamically allocated libev event loop | |
| 309 | - struct ev_loop* evloop_; | |
| 310 | - | |
| 311 | - // Asynchronous watchers | |
| 312 | - ev_async watcher_command_; // For processing commands | |
| 313 | - ev_async watcher_stop_; // For breaking the loop | |
| 314 | - ev_async watcher_free_; // For freeing commands | |
| 315 | - | |
| 316 | - // Track of Command objects allocated. Also provides unique Command IDs. | |
| 317 | - std::atomic_long commands_created_ = {0}; | |
| 318 | - std::atomic_long commands_deleted_ = {0}; | |
| 319 | - | |
| 320 | - // Separate thread to have a non-blocking event loop | |
| 321 | - std::thread event_loop_thread_; | |
| 322 | - | |
| 323 | - // Variable and CV to know when the event loop starts running | |
| 324 | - std::atomic_bool running_ = {false}; | |
| 325 | - std::mutex running_waiter_lock_; | |
| 326 | - std::condition_variable running_waiter_; | |
| 327 | - | |
| 328 | - // Variable and CV to know when the event loop stops running | |
| 329 | - std::atomic_bool to_exit_ = {false}; // Signal to exit | |
| 330 | - std::atomic_bool exited_ = {false}; // Event thread exited | |
| 331 | - std::mutex exit_waiter_lock_; | |
| 332 | - std::condition_variable exit_waiter_; | |
| 333 | - | |
| 334 | - // Maps of each Command, fetchable by the unique ID number | |
| 335 | - // In C++14, member variable templates will replace all of these types | |
| 336 | - // with a single templated declaration | |
| 337 | - // --------- | |
| 338 | - // template<class ReplyT> | |
| 339 | - // std::unordered_map<long, Command<ReplyT>*> commands_; | |
| 340 | - // --------- | |
| 341 | - std::unordered_map<long, Command<redisReply*>*> commands_redis_reply_; | |
| 342 | - std::unordered_map<long, Command<std::string>*> commands_string_; | |
| 343 | - std::unordered_map<long, Command<char*>*> commands_char_p_; | |
| 344 | - std::unordered_map<long, Command<int>*> commands_int_; | |
| 345 | - std::unordered_map<long, Command<long long int>*> commands_long_long_int_; | |
| 346 | - std::unordered_map<long, Command<std::nullptr_t>*> commands_null_; | |
| 347 | - std::unordered_map<long, Command<std::vector<std::string>>*> commands_vector_string_; | |
| 348 | - std::unordered_map<long, Command<std::set<std::string>>*> commands_set_string_; | |
| 349 | - std::unordered_map<long, Command<std::unordered_set<std::string>>*> commands_unordered_set_string_; | |
| 350 | - std::mutex command_map_guard_; // Guards access to all of the above | |
| 351 | - | |
| 352 | - // Command IDs pending to be sent to the server | |
| 353 | - std::queue<long> command_queue_; | |
| 354 | - std::mutex queue_guard_; | |
| 355 | - | |
| 356 | - // Commands IDs pending to be freed by the event loop | |
| 357 | - std::queue<long> commands_to_free_; | |
| 358 | - std::mutex free_queue_guard_; | |
| 359 | - | |
| 360 | - // Commands use this method to deregister themselves from Redox, | |
| 361 | - // give it access to private members | |
| 362 | - template<class ReplyT> | |
| 363 | - friend void Command<ReplyT>::free(); | |
| 364 | -}; | |
| 365 | - | |
| 366 | -// ------------------------------------------------ | |
| 367 | -// Implementation of templated methods | |
| 368 | -// ------------------------------------------------ | |
| 369 | - | |
| 370 | -template<class ReplyT> | |
| 371 | -Command<ReplyT>& Redox::createCommand( | |
| 372 | - const std::string& cmd, | |
| 373 | - const std::function<void(Command<ReplyT>&)>& callback, | |
| 374 | - double repeat, | |
| 375 | - double after, | |
| 376 | - bool free_memory | |
| 377 | -) { | |
| 378 | - | |
| 379 | - if(!running_) { | |
| 380 | - throw std::runtime_error("[ERROR] Need to connect Redox before running commands!"); | |
| 381 | - } | |
| 382 | - | |
| 383 | - commands_created_ += 1; | |
| 384 | - auto* c = new Command<ReplyT>( | |
| 385 | - this, commands_created_, cmd, | |
| 386 | - callback, repeat, after, free_memory, logger_ | |
| 387 | - ); | |
| 388 | - | |
| 389 | - std::lock_guard<std::mutex> lg(queue_guard_); | |
| 390 | - std::lock_guard<std::mutex> lg2(command_map_guard_); | |
| 391 | - | |
| 392 | - getCommandMap<ReplyT>()[c->id_] = c; | |
| 393 | - command_queue_.push(c->id_); | |
| 394 | - | |
| 395 | - // Signal the event loop to process this command | |
| 396 | - ev_async_send(evloop_, &watcher_command_); | |
| 397 | - | |
| 398 | - return *c; | |
| 399 | -} | |
| 400 | - | |
| 401 | -template<class ReplyT> | |
| 402 | -void Redox::command( | |
| 403 | - const std::string& cmd, | |
| 404 | - const std::function<void(Command<ReplyT>&)>& callback | |
| 405 | -) { | |
| 406 | - createCommand(cmd, callback); | |
| 407 | -} | |
| 408 | - | |
| 409 | -template<class ReplyT> | |
| 410 | -Command<ReplyT>& Redox::commandLoop( | |
| 411 | - const std::string& cmd, | |
| 412 | - const std::function<void(Command<ReplyT>&)>& callback, | |
| 413 | - double repeat, | |
| 414 | - double after | |
| 415 | -) { | |
| 416 | - return createCommand(cmd, callback, repeat, after, false); | |
| 417 | -} | |
| 418 | - | |
| 419 | -template<class ReplyT> | |
| 420 | -void Redox::commandDelayed( | |
| 421 | - const std::string& cmd, | |
| 422 | - const std::function<void(Command<ReplyT>&)>& callback, | |
| 423 | - double after | |
| 424 | -) { | |
| 425 | - createCommand(cmd, callback, 0, after, true); | |
| 426 | -} | |
| 427 | - | |
| 428 | -template<class ReplyT> | |
| 429 | -Command<ReplyT>& Redox::commandSync(const std::string& cmd) { | |
| 430 | - auto& c = createCommand<ReplyT>(cmd, nullptr, 0, 0, false); | |
| 431 | - c.wait(); | |
| 432 | - return c; | |
| 433 | -} | |
| 434 | - | |
| 435 | -} // End namespace redis | |
| 25 | +#include "subscriber.hpp" | ... | ... |
src/subscriber.hpp