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 +7,7 @@ option(examples "Build all examples." ON)
7 7
8 # Use RelWithDebInfo if no configuration specified 8 # Use RelWithDebInfo if no configuration specified
9 if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 9 if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
10 - set(CMAKE_BUILD_TYPE RelWithDebInfo) 10 + set(CMAKE_BUILD_TYPE Release)
11 endif(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 11 endif(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
12 12
13 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") 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,7 +22,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall")
22 set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) 22 set(SRC_DIR ${CMAKE_SOURCE_DIR}/src)
23 23
24 set(SRC_CORE 24 set(SRC_CORE
25 - ${SRC_DIR}/redox.cpp 25 + ${SRC_DIR}/client.cpp
26 ${SRC_DIR}/command.cpp 26 ${SRC_DIR}/command.cpp
27 ${SRC_DIR}/subscriber.cpp 27 ${SRC_DIR}/subscriber.cpp
28 ) 28 )
@@ -31,11 +31,24 @@ set(SRC_LOGGER ${SRC_DIR}/utils/logger.cpp) @@ -31,11 +31,24 @@ set(SRC_LOGGER ${SRC_DIR}/utils/logger.cpp)
31 31
32 set(SRC_ALL ${SRC_CORE} ${SRC_LOGGER}) 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 # Test suite 54 # Test suite
@@ -44,11 +57,11 @@ if (tests) @@ -44,11 +57,11 @@ if (tests)
44 57
45 enable_testing() 58 enable_testing()
46 find_package(GTest REQUIRED) 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 # So that we can run 'make test' 66 # So that we can run 'make test'
54 add_test(test_redox test_redox) 67 add_test(test_redox test_redox)
@@ -60,37 +73,37 @@ endif() @@ -60,37 +73,37 @@ endif()
60 # --------------------------------------------------------- 73 # ---------------------------------------------------------
61 if (examples) 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 add_executable(speed_test_pubsub examples/speed_test_pubsub ${SRC_ALL}) 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 endif() 109 endif()
examples/basic.cpp
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 */ 3 */
4 4
5 #include <iostream> 5 #include <iostream>
6 -#include "../src/redox.hpp" 6 +#include "redox.hpp"
7 7
8 using namespace std; 8 using namespace std;
9 using redox::Redox; 9 using redox::Redox;
examples/basic_threaded.cpp
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 #include <iostream> 5 #include <iostream>
6 #include <chrono> 6 #include <chrono>
7 #include <thread> 7 #include <thread>
8 -#include "../src/redox.hpp" 8 +#include "redox.hpp"
9 9
10 using namespace std; 10 using namespace std;
11 using redox::Redox; 11 using redox::Redox;
examples/binary_data.cpp
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 #include <iostream> 5 #include <iostream>
6 #include <algorithm> 6 #include <algorithm>
7 #include <random> 7 #include <random>
8 -#include "../src/redox.hpp" 8 +#include "redox.hpp"
9 9
10 using namespace std; 10 using namespace std;
11 using redox::Redox; 11 using redox::Redox;
examples/data_types.cpp
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 */ 3 */
4 4
5 #include <iostream> 5 #include <iostream>
6 -#include "../src/redox.hpp" 6 +#include "redox.hpp"
7 #include <set> 7 #include <set>
8 #include <unordered_set> 8 #include <unordered_set>
9 #include <vector> 9 #include <vector>
examples/lpush_benchmark.cpp
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 */ 3 */
4 4
5 #include <iostream> 5 #include <iostream>
6 -#include "../src/redox.hpp" 6 +#include "redox.hpp"
7 7
8 using namespace std; 8 using namespace std;
9 using redox::Redox; 9 using redox::Redox;
examples/multi-client.cpp
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 */ 3 */
4 4
5 #include <iostream> 5 #include <iostream>
6 -#include "../src/redox.hpp" 6 +#include "redox.hpp"
7 7
8 using namespace std; 8 using namespace std;
9 using redox::Redox; 9 using redox::Redox;
examples/pub_sub.cpp
@@ -4,8 +4,7 @@ @@ -4,8 +4,7 @@
4 4
5 #include <stdlib.h> 5 #include <stdlib.h>
6 #include <iostream> 6 #include <iostream>
7 -#include "../src/redox.hpp"  
8 -#include "../src/subscriber.hpp" 7 +#include "redox.hpp"
9 8
10 using namespace std; 9 using namespace std;
11 10
examples/speed_test_async.cpp
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 */ 5 */
6 6
7 #include <iostream> 7 #include <iostream>
8 -#include "../src/redox.hpp" 8 +#include "redox.hpp"
9 9
10 using namespace std; 10 using namespace std;
11 using redox::Redox; 11 using redox::Redox;
examples/speed_test_async_multi.cpp
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 6
7 #include <iostream> 7 #include <iostream>
8 #include <vector> 8 #include <vector>
9 -#include "../src/redox.hpp" 9 +#include "redox.hpp"
10 10
11 using namespace std; 11 using namespace std;
12 using redox::Redox; 12 using redox::Redox;
examples/speed_test_pubsub.cpp
1 #include <iostream> 1 #include <iostream>
2 -#include "../src/redox.hpp"  
3 -#include "../src/subscriber.hpp" 2 +#include "redox.hpp"
4 3
5 using namespace std; 4 using namespace std;
6 using redox::Redox; 5 using redox::Redox;
examples/speed_test_sync.cpp
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 */ 5 */
6 6
7 #include <iostream> 7 #include <iostream>
8 -#include "../src/redox.hpp" 8 +#include "redox.hpp"
9 9
10 using namespace std; 10 using namespace std;
11 using namespace redox; 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,7 +23,7 @@
23 #include <unordered_set> 23 #include <unordered_set>
24 24
25 #include "command.hpp" 25 #include "command.hpp"
26 -#include "redox.hpp" 26 +#include "client.hpp"
27 27
28 using namespace std; 28 using namespace std;
29 29
src/redox.hpp
@@ -20,416 +20,6 @@ @@ -20,416 +20,6 @@
20 20
21 #pragma once 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 #include "command.hpp" 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,7 +20,7 @@
20 20
21 #pragma once 21 #pragma once
22 22
23 -#include "redox.hpp" 23 +#include "client.hpp"
24 24
25 namespace redox { 25 namespace redox {
26 26
test/test.cpp
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 20
21 #include <iostream> 21 #include <iostream>
22 22
23 -#include "../src/redox.hpp" 23 +#include "redox.hpp"
24 #include "gtest/gtest.h" 24 #include "gtest/gtest.h"
25 25
26 namespace { 26 namespace {