Commit ca1d31710568ce91cdc761fa880e9f1541d967d6

Authored by Hayk Martirosyan
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.
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
... ... @@ -3,7 +3,7 @@
3 3 */
4 4  
5 5 #include <iostream>
6   -#include "../src/redox.hpp"
  6 +#include "redox.hpp"
7 7  
8 8 using namespace std;
9 9 using redox::Redox;
... ...
examples/basic_threaded.cpp
... ... @@ -5,7 +5,7 @@
5 5 #include <iostream>
6 6 #include <chrono>
7 7 #include <thread>
8   -#include "../src/redox.hpp"
  8 +#include "redox.hpp"
9 9  
10 10 using namespace std;
11 11 using redox::Redox;
... ...
examples/binary_data.cpp
... ... @@ -5,7 +5,7 @@
5 5 #include <iostream>
6 6 #include <algorithm>
7 7 #include <random>
8   -#include "../src/redox.hpp"
  8 +#include "redox.hpp"
9 9  
10 10 using namespace std;
11 11 using redox::Redox;
... ...
examples/data_types.cpp
... ... @@ -3,7 +3,7 @@
3 3 */
4 4  
5 5 #include <iostream>
6   -#include "../src/redox.hpp"
  6 +#include "redox.hpp"
7 7 #include <set>
8 8 #include <unordered_set>
9 9 #include <vector>
... ...
examples/lpush_benchmark.cpp
... ... @@ -3,7 +3,7 @@
3 3 */
4 4  
5 5 #include <iostream>
6   -#include "../src/redox.hpp"
  6 +#include "redox.hpp"
7 7  
8 8 using namespace std;
9 9 using redox::Redox;
... ...
examples/multi-client.cpp
... ... @@ -3,7 +3,7 @@
3 3 */
4 4  
5 5 #include <iostream>
6   -#include "../src/redox.hpp"
  6 +#include "redox.hpp"
7 7  
8 8 using namespace std;
9 9 using redox::Redox;
... ...
examples/pub_sub.cpp
... ... @@ -4,8 +4,7 @@
4 4  
5 5 #include <stdlib.h>
6 6 #include <iostream>
7   -#include "../src/redox.hpp"
8   -#include "../src/subscriber.hpp"
  7 +#include "redox.hpp"
9 8  
10 9 using namespace std;
11 10  
... ...
examples/speed_test_async.cpp
... ... @@ -5,7 +5,7 @@
5 5 */
6 6  
7 7 #include <iostream>
8   -#include "../src/redox.hpp"
  8 +#include "redox.hpp"
9 9  
10 10 using namespace std;
11 11 using redox::Redox;
... ...
examples/speed_test_async_multi.cpp
... ... @@ -6,7 +6,7 @@
6 6  
7 7 #include <iostream>
8 8 #include <vector>
9   -#include "../src/redox.hpp"
  9 +#include "redox.hpp"
10 10  
11 11 using namespace std;
12 12 using redox::Redox;
... ...
examples/speed_test_pubsub.cpp
1 1 #include <iostream>
2   -#include "../src/redox.hpp"
3   -#include "../src/subscriber.hpp"
  2 +#include "redox.hpp"
4 3  
5 4 using namespace std;
6 5 using redox::Redox;
... ...
examples/speed_test_sync.cpp
... ... @@ -5,7 +5,7 @@
5 5 */
6 6  
7 7 #include <iostream>
8   -#include "../src/redox.hpp"
  8 +#include "redox.hpp"
9 9  
10 10 using namespace std;
11 11 using namespace redox;
... ...
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
... ... @@ -23,7 +23,7 @@
23 23 #include <unordered_set>
24 24  
25 25 #include "command.hpp"
26   -#include "redox.hpp"
  26 +#include "client.hpp"
27 27  
28 28 using namespace std;
29 29  
... ...
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
... ... @@ -20,7 +20,7 @@
20 20  
21 21 #pragma once
22 22  
23   -#include "redox.hpp"
  23 +#include "client.hpp"
24 24  
25 25 namespace redox {
26 26  
... ...
test/test.cpp
... ... @@ -20,7 +20,7 @@
20 20  
21 21 #include <iostream>
22 22  
23   -#include "../src/redox.hpp"
  23 +#include "redox.hpp"
24 24 #include "gtest/gtest.h"
25 25  
26 26 namespace {
... ...