Commit be5fad765d2dc312acf46f2c10dfe30c66733b2e

Authored by Hayk Martirosyan
1 parent 2833f2e8

Working loop command implementation

Uses libev timer watchers to queue commands on a regular
interval. Very fast. Still need to handle deactivation of
timers from the user side.
CMakeLists.txt
1 cmake_minimum_required(VERSION 2.8.4) 1 cmake_minimum_required(VERSION 2.8.4)
2 project(redisx) 2 project(redisx)
3 3
4 -#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall -O3")  
5 -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall -g -fno-omit-frame-pointer") 4 +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall -O3")
  5 +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall -g -fno-omit-frame-pointer")
6 # set(CMAKE_VERBOSE_MAKEFILE ON) 6 # set(CMAKE_VERBOSE_MAKEFILE ON)
7 7
8 # --------------------------------------------------------- 8 # ---------------------------------------------------------
@@ -34,8 +34,11 @@ set(LIB_ALL ${LIB_REDIS}) @@ -34,8 +34,11 @@ set(LIB_ALL ${LIB_REDIS})
34 #add_executable(progressive examples/progressive.cpp ${SRC_ALL}) 34 #add_executable(progressive examples/progressive.cpp ${SRC_ALL})
35 #target_link_libraries(progressive ${LIB_REDIS}) 35 #target_link_libraries(progressive ${LIB_REDIS})
36 36
37 -add_executable(basic_threaded examples/basic_threaded.cpp ${SRC_ALL})  
38 -target_link_libraries(basic_threaded ${LIB_REDIS}) 37 +#add_executable(basic_threaded examples/basic_threaded.cpp ${SRC_ALL})
  38 +#target_link_libraries(basic_threaded ${LIB_REDIS})
39 39
40 -add_executable(lpush_benchmark examples/lpush_benchmark.cpp ${SRC_ALL})  
41 -target_link_libraries(lpush_benchmark ${LIB_REDIS}) 40 +#add_executable(lpush_benchmark examples/lpush_benchmark.cpp ${SRC_ALL})
  41 +#target_link_libraries(lpush_benchmark ${LIB_REDIS})
  42 +
  43 +add_executable(simple_loop examples/simple_loop.cpp ${SRC_ALL})
  44 +target_link_libraries(simple_loop ${LIB_REDIS})
examples/simple_loop.cpp 0 → 100644
  1 +/**
  2 +* Basic asynchronous calls using redisx.
  3 +*/
  4 +
  5 +#include <iostream>
  6 +#include "../src/redisx.hpp"
  7 +
  8 +using namespace std;
  9 +
  10 +double time_s() {
  11 + unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::milliseconds(1);
  12 + return (double)ms / 1000;
  13 +}
  14 +
  15 +int main(int argc, char* argv[]) {
  16 +
  17 + redisx::Redis rdx = {"localhost", 6379};
  18 + rdx.run();
  19 +
  20 + string cmd_str = "SET alaska rules!";
  21 +
  22 + double freq = 10000; // Hz
  23 + double t_end = 5;
  24 +
  25 + double dt = 1 / freq;
  26 + double t0 = time_s();
  27 + int count = 0;
  28 +
  29 + cout << "Running \"" << cmd_str << "\" at dt = " << dt
  30 + << "s for " << t_end << "s..." << endl;
  31 +
  32 + rdx.command<const string &>(
  33 + cmd_str,
  34 + [&count, &rdx, t0, t_end](const string &cmd, const string &value) {
  35 + count++;
  36 + if(time_s() - t0 >= t_end) rdx.stop();
  37 + },
  38 + dt,
  39 + dt
  40 + );
  41 +
  42 + rdx.block_until_stopped();
  43 + double actual_freq = (double)count / t_end;
  44 + cout << "Sent " << count << " commands in " << t_end<< "s, "
  45 + << "that's " << actual_freq << " commands/s." << endl;
  46 +
  47 + return 0;
  48 +}
src/redisx.cpp
@@ -3,20 +3,20 @@ @@ -3,20 +3,20 @@
3 */ 3 */
4 4
5 #include <signal.h> 5 #include <signal.h>
6 -#include <iostream>  
7 -#include <thread>  
8 -#include <hiredis/adapters/libev.h>  
9 -#include <ev.h>  
10 -#include <event2/thread.h>  
11 -#include <vector> 6 +//#include <event2/thread.h>
12 #include "redisx.hpp" 7 #include "redisx.hpp"
13 8
14 using namespace std; 9 using namespace std;
15 10
16 namespace redisx { 11 namespace redisx {
17 12
  13 +// Global mutex to manage waiting for connected state
18 mutex connected_lock; 14 mutex connected_lock;
19 15
  16 +// Map of ev_timer events to pointers to CommandAsync objects
  17 +// Used to get the object back from the timer watcher callback
  18 +unordered_map<ev_timer*, void*> timer_callbacks;
  19 +
20 void connected(const redisAsyncContext *c, int status) { 20 void connected(const redisAsyncContext *c, int status) {
21 if (status != REDIS_OK) { 21 if (status != REDIS_OK) {
22 cerr << "[ERROR] Connecting to Redis: " << c->errstr << endl; 22 cerr << "[ERROR] Connecting to Redis: " << c->errstr << endl;
@@ -94,17 +94,27 @@ void Redis::block_until_stopped() { @@ -94,17 +94,27 @@ void Redis::block_until_stopped() {
94 exit_waiter.wait(ul, [this]() { return to_exit.load(); }); 94 exit_waiter.wait(ul, [this]() { return to_exit.load(); });
95 } 95 }
96 96
  97 +/**
  98 +* Submit an asynchronous command to the Redis server. Return
  99 +* true if succeeded, false otherwise.
  100 +*/
97 template<class ReplyT> 101 template<class ReplyT>
98 -bool Redis::submit_to_server(const CommandAsync<ReplyT>* cmd_obj) {  
99 - if (redisAsyncCommand(c, command_callback<ReplyT>, (void*)cmd_obj, cmd_obj->cmd.c_str()) != REDIS_OK) {  
100 - cerr << "[ERROR] Async command \"" << cmd_obj->cmd << "\": " << c->errstr << endl;  
101 - delete cmd_obj; 102 +bool submit_to_server(CommandAsync<ReplyT>* cmd_obj) {
  103 + if (redisAsyncCommand(cmd_obj->c, command_callback<ReplyT>, (void*)cmd_obj, cmd_obj->cmd.c_str()) != REDIS_OK) {
  104 + cerr << "[ERROR] Async command \"" << cmd_obj->cmd << "\": " << cmd_obj->c->errstr << endl;
  105 + cmd_obj->free_if_done();
102 return false; 106 return false;
103 } 107 }
104 return true; 108 return true;
105 } 109 }
106 110
107 template<class ReplyT> 111 template<class ReplyT>
  112 +void submit_command_callback(struct ev_loop* loop, ev_timer* timer, int revents) {
  113 + auto cmd_obj = (CommandAsync<ReplyT>*)timer_callbacks.at(timer);
  114 + submit_to_server<ReplyT>(cmd_obj);
  115 +}
  116 +
  117 +template<class ReplyT>
108 bool Redis::process_queued_command(void* cmd_ptr) { 118 bool Redis::process_queued_command(void* cmd_ptr) {
109 119
110 auto& command_map = get_command_map<ReplyT>(); 120 auto& command_map = get_command_map<ReplyT>();
@@ -114,7 +124,14 @@ bool Redis::process_queued_command(void* cmd_ptr) { @@ -114,7 +124,14 @@ bool Redis::process_queued_command(void* cmd_ptr) {
114 CommandAsync<ReplyT>* cmd_obj = it->second; 124 CommandAsync<ReplyT>* cmd_obj = it->second;
115 command_map.erase(cmd_ptr); 125 command_map.erase(cmd_ptr);
116 126
117 - submit_to_server<ReplyT>(cmd_obj); 127 + if((cmd_obj->repeat == 0) && (cmd_obj->after == 0)) {
  128 + submit_to_server<ReplyT>(cmd_obj);
  129 + } else {
  130 + cmd_obj->timer = new ev_timer();
  131 + timer_callbacks[cmd_obj->timer] = (void*)cmd_obj;
  132 + ev_timer_init(cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat);
  133 + ev_timer_start(EV_DEFAULT_ cmd_obj->timer);
  134 + }
118 135
119 return true; 136 return true;
120 } 137 }
@@ -147,13 +164,13 @@ long Redis::num_commands_processed() { @@ -147,13 +164,13 @@ long Redis::num_commands_processed() {
147 164
148 template<> unordered_map<void*, CommandAsync<const redisReply*>*>& Redis::get_command_map() { return commands_redis_reply; } 165 template<> unordered_map<void*, CommandAsync<const redisReply*>*>& Redis::get_command_map() { return commands_redis_reply; }
149 template<> 166 template<>
150 -void invoke_callback(const CommandAsync<const redisReply*>* cmd_obj, redisReply* reply) { 167 +void invoke_callback(CommandAsync<const redisReply*>* cmd_obj, redisReply* reply) {
151 cmd_obj->invoke(reply); 168 cmd_obj->invoke(reply);
152 } 169 }
153 170
154 template<> unordered_map<void*, CommandAsync<const string&>*>& Redis::get_command_map() { return commands_string_r; } 171 template<> unordered_map<void*, CommandAsync<const string&>*>& Redis::get_command_map() { return commands_string_r; }
155 template<> 172 template<>
156 -void invoke_callback(const CommandAsync<const string&>* cmd_obj, redisReply* reply) { 173 +void invoke_callback(CommandAsync<const string&>* cmd_obj, redisReply* reply) {
157 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { 174 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) {
158 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; 175 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl;
159 return; 176 return;
@@ -164,7 +181,7 @@ void invoke_callback(const CommandAsync&lt;const string&amp;&gt;* cmd_obj, redisReply* rep @@ -164,7 +181,7 @@ void invoke_callback(const CommandAsync&lt;const string&amp;&gt;* cmd_obj, redisReply* rep
164 181
165 template<> unordered_map<void*, CommandAsync<const char*>*>& Redis::get_command_map() { return commands_char_p; } 182 template<> unordered_map<void*, CommandAsync<const char*>*>& Redis::get_command_map() { return commands_char_p; }
166 template<> 183 template<>
167 -void invoke_callback(const CommandAsync<const char*>* cmd_obj, redisReply* reply) { 184 +void invoke_callback(CommandAsync<const char*>* cmd_obj, redisReply* reply) {
168 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { 185 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) {
169 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; 186 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl;
170 return; 187 return;
@@ -174,7 +191,7 @@ void invoke_callback(const CommandAsync&lt;const char*&gt;* cmd_obj, redisReply* reply @@ -174,7 +191,7 @@ void invoke_callback(const CommandAsync&lt;const char*&gt;* cmd_obj, redisReply* reply
174 191
175 template<> unordered_map<void*, CommandAsync<int>*>& Redis::get_command_map() { return commands_int; } 192 template<> unordered_map<void*, CommandAsync<int>*>& Redis::get_command_map() { return commands_int; }
176 template<> 193 template<>
177 -void invoke_callback(const CommandAsync<int>* cmd_obj, redisReply* reply) { 194 +void invoke_callback(CommandAsync<int>* cmd_obj, redisReply* reply) {
178 if(reply->type != REDIS_REPLY_INTEGER) { 195 if(reply->type != REDIS_REPLY_INTEGER) {
179 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; 196 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl;
180 return; 197 return;
@@ -184,7 +201,7 @@ void invoke_callback(const CommandAsync&lt;int&gt;* cmd_obj, redisReply* reply) { @@ -184,7 +201,7 @@ void invoke_callback(const CommandAsync&lt;int&gt;* cmd_obj, redisReply* reply) {
184 201
185 template<> unordered_map<void*, CommandAsync<long long int>*>& Redis::get_command_map() { return commands_long_long_int; } 202 template<> unordered_map<void*, CommandAsync<long long int>*>& Redis::get_command_map() { return commands_long_long_int; }
186 template<> 203 template<>
187 -void invoke_callback(const CommandAsync<long long int>* cmd_obj, redisReply* reply) { 204 +void invoke_callback(CommandAsync<long long int>* cmd_obj, redisReply* reply) {
188 if(reply->type != REDIS_REPLY_INTEGER) { 205 if(reply->type != REDIS_REPLY_INTEGER) {
189 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; 206 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl;
190 return; 207 return;
src/redisx.hpp
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 18
19 #include <hiredis/hiredis.h> 19 #include <hiredis/hiredis.h>
20 #include <hiredis/async.h> 20 #include <hiredis/async.h>
21 -#include <condition_variable> 21 +#include <hiredis/adapters/libev.h>
22 22
23 namespace redisx { 23 namespace redisx {
24 24
@@ -26,17 +26,30 @@ template&lt;class ReplyT&gt; @@ -26,17 +26,30 @@ template&lt;class ReplyT&gt;
26 class CommandAsync { 26 class CommandAsync {
27 public: 27 public:
28 CommandAsync( 28 CommandAsync(
  29 + redisAsyncContext* c,
29 const std::string& cmd, 30 const std::string& cmd,
30 const std::function<void(const std::string&, ReplyT)>& callback, 31 const std::function<void(const std::string&, ReplyT)>& callback,
31 double repeat, double after 32 double repeat, double after
32 - ) : cmd(cmd), callback(callback), repeat(repeat), after(after) {} 33 + ) : c(c), cmd(cmd), callback(callback), repeat(repeat), after(after), done(false) {}
33 34
  35 + redisAsyncContext* c;
34 const std::string cmd; 36 const std::string cmd;
35 const std::function<void(const std::string&, ReplyT)> callback; 37 const std::function<void(const std::string&, ReplyT)> callback;
36 double repeat; 38 double repeat;
37 double after; 39 double after;
  40 + bool done;
  41 + ev_timer* timer;
38 42
39 - void invoke(ReplyT reply) const {if(callback != NULL) callback(cmd, reply); } 43 + void invoke(ReplyT reply) {
  44 + if(callback != NULL) callback(cmd, reply);
  45 + if((repeat == 0)) done = true;
  46 + }
  47 + void free_if_done() {
  48 + if(done) {
  49 + std::cout << "Deleting CommandAsync: " << cmd << std::endl;
  50 + delete this;
  51 + };
  52 + }
40 }; 53 };
41 54
42 class Redis { 55 class Redis {
@@ -63,8 +76,6 @@ public: @@ -63,8 +76,6 @@ public:
63 76
64 long num_commands_processed(); 77 long num_commands_processed();
65 78
66 -// struct event* command_loop(const char* command, long interval_s, long interval_us);  
67 -  
68 // void get(const char* key, std::function<void(const std::string&, const char*)> callback); 79 // void get(const char* key, std::function<void(const std::string&, const char*)> callback);
69 // 80 //
70 // void set(const char* key, const char* value); 81 // void set(const char* key, const char* value);
@@ -109,20 +120,13 @@ private: @@ -109,20 +120,13 @@ private:
109 120
110 template<class ReplyT> 121 template<class ReplyT>
111 bool process_queued_command(void* cmd_ptr); 122 bool process_queued_command(void* cmd_ptr);
112 -  
113 - /**  
114 - * Submit an asynchronous command to the Redis server. Return  
115 - * true if succeeded, false otherwise.  
116 - */  
117 - template<class ReplyT>  
118 - bool submit_to_server(const CommandAsync<ReplyT>* cmd_obj);  
119 }; 123 };
120 124
121 // --------------------------- 125 // ---------------------------
122 126
123 template<class ReplyT> 127 template<class ReplyT>
124 void invoke_callback( 128 void invoke_callback(
125 - const CommandAsync<ReplyT>* cmd_obj, 129 + CommandAsync<ReplyT>* cmd_obj,
126 redisReply* reply 130 redisReply* reply
127 ); 131 );
128 132
@@ -134,18 +138,18 @@ void command_callback(redisAsyncContext *c, void *r, void *privdata) { @@ -134,18 +138,18 @@ void command_callback(redisAsyncContext *c, void *r, void *privdata) {
134 138
135 if (reply->type == REDIS_REPLY_ERROR) { 139 if (reply->type == REDIS_REPLY_ERROR) {
136 std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl; 140 std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl;
137 - delete cmd_obj; 141 + cmd_obj->free_if_done();
138 return; 142 return;
139 } 143 }
140 144
141 if(reply->type == REDIS_REPLY_NIL) { 145 if(reply->type == REDIS_REPLY_NIL) {
142 std::cerr << "[WARNING] " << cmd_obj->cmd << ": Nil reply." << std::endl; 146 std::cerr << "[WARNING] " << cmd_obj->cmd << ": Nil reply." << std::endl;
143 - delete cmd_obj; 147 + cmd_obj->free_if_done();
144 return; // cmd_obj->invoke(NULL); 148 return; // cmd_obj->invoke(NULL);
145 } 149 }
146 150
147 invoke_callback<ReplyT>(cmd_obj, reply); 151 invoke_callback<ReplyT>(cmd_obj, reply);
148 - delete cmd_obj; 152 + cmd_obj->free_if_done();
149 } 153 }
150 154
151 template<class ReplyT> 155 template<class ReplyT>
@@ -157,10 +161,9 @@ void Redis::command( @@ -157,10 +161,9 @@ void Redis::command(
157 ) { 161 ) {
158 162
159 std::lock_guard<std::mutex> lg(queue_guard); 163 std::lock_guard<std::mutex> lg(queue_guard);
160 - auto* cmd_obj = new CommandAsync<ReplyT>(cmd, callback, repeat, after); 164 + auto* cmd_obj = new CommandAsync<ReplyT>(c, cmd, callback, repeat, after);
161 get_command_map<ReplyT>()[(void*)cmd_obj] = cmd_obj; 165 get_command_map<ReplyT>()[(void*)cmd_obj] = cmd_obj;
162 command_queue.push((void*)cmd_obj); 166 command_queue.push((void*)cmd_obj);
163 } 167 }
164 168
165 -  
166 } // End namespace redis 169 } // End namespace redis