Commit df429ec2f7c57ef986e05d26be87b46578446448

Authored by Hayk Martirosyan
1 parent be5fad76

Rename CommandAsync->Command, cancel functionality

Renamed CommandAsync to Command to make things simpler, and
refactored into its own file.

command() now returns a pointer to the created Command object,
which the client can pass into cancel() to stop any delayed
or repeating calls. This is very important for an asynchronous
client.
README.md
... ... @@ -2,3 +2,6 @@ redisx
2 2 ======
3 3  
4 4 Asynchronous, minimalistic, and highly effective C++11 bindings for Redis
  5 +Asynchronous, minimalistic, and wicked fast C++11 bindings for Redis
  6 +
  7 +Work in progress, details coming soon.
... ...
examples/simple_loop.cpp
... ... @@ -8,8 +8,8 @@
8 8 using namespace std;
9 9  
10 10 double time_s() {
11   - unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::milliseconds(1);
12   - return (double)ms / 1000;
  11 + unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::microseconds(1);
  12 + return (double)ms / 1e6;
13 13 }
14 14  
15 15 int main(int argc, char* argv[]) {
... ... @@ -20,28 +20,31 @@ int main(int argc, char* argv[]) {
20 20 string cmd_str = "SET alaska rules!";
21 21  
22 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;
  23 + double dt = 1 / freq; // s
  24 + double t = 5; // s
28 25  
29 26 cout << "Running \"" << cmd_str << "\" at dt = " << dt
30   - << "s for " << t_end << "s..." << endl;
  27 + << "s for " << t << "s..." << endl;
31 28  
32   - rdx.command<const string &>(
  29 + int count = 0;
  30 + redisx::Command<const string&>* c = rdx.command<const string&>(
33 31 cmd_str,
34   - [&count, &rdx, t0, t_end](const string &cmd, const string &value) {
  32 + [&count](const string &cmd, const string &value) {
35 33 count++;
36   - if(time_s() - t0 >= t_end) rdx.stop();
37 34 },
38 35 dt,
39 36 dt
40 37 );
41 38  
42   - rdx.block_until_stopped();
43   - double actual_freq = (double)count / t_end;
44   - cout << "Sent " << count << " commands in " << t_end<< "s, "
  39 + double t0 = time_s();
  40 + this_thread::sleep_for(chrono::microseconds((int)(t*1e6)));
  41 + rdx.cancel<const string&>(c);
  42 + rdx.stop();
  43 +
  44 + double t_elapsed = time_s() - t0;
  45 + double actual_freq = (double)count / t_elapsed;
  46 +
  47 + cout << "Sent " << count << " commands in " << t_elapsed << "s, "
45 48 << "that's " << actual_freq << " commands/s." << endl;
46 49  
47 50 return 0;
... ...
src/command.hpp 0 → 100644
  1 +/**
  2 +* Redis C++11 wrapper.
  3 +*/
  4 +
  5 +#pragma once
  6 +
  7 +#include <iostream>
  8 +#include <string>
  9 +#include <functional>
  10 +
  11 +#include <hiredis/adapters/libev.h>
  12 +#include <hiredis/async.h>
  13 +
  14 +namespace redisx {
  15 +
  16 +template<class ReplyT>
  17 +class Command {
  18 +public:
  19 + Command(
  20 + redisAsyncContext* c,
  21 + const std::string& cmd,
  22 + const std::function<void(const std::string&, ReplyT)>& callback,
  23 + double repeat, double after
  24 + );
  25 +
  26 + redisAsyncContext* c;
  27 + const std::string cmd;
  28 + const std::function<void(const std::string&, ReplyT)> callback;
  29 + double repeat;
  30 + double after;
  31 + bool done;
  32 + ev_timer* timer;
  33 + std::mutex timer_guard;
  34 +
  35 + void invoke(ReplyT reply);
  36 +
  37 + void free_if_done();
  38 + ev_timer* get_timer() {
  39 + std::lock_guard<std::mutex> lg(timer_guard);
  40 + return timer;
  41 + }
  42 +};
  43 +
  44 +template<class ReplyT>
  45 +Command<ReplyT>::Command(
  46 + redisAsyncContext* c,
  47 + const std::string& cmd,
  48 + const std::function<void(const std::string&, ReplyT)>& callback,
  49 + double repeat, double after
  50 +) : c(c), cmd(cmd), callback(callback), repeat(repeat), after(after), done(false) {
  51 + timer_guard.lock();
  52 +}
  53 +
  54 +template<class ReplyT>
  55 +void Command<ReplyT>::invoke(ReplyT reply) {
  56 + if(callback != NULL) callback(cmd, reply);
  57 + if((repeat == 0)) done = true;
  58 +}
  59 +
  60 +template<class ReplyT>
  61 +void Command<ReplyT>::free_if_done() {
  62 + if(done) {
  63 + std::cout << "Deleting Command: " << cmd << std::endl;
  64 + delete this;
  65 + };
  66 +}
  67 +} // End namespace redis
... ...
src/redisx.cpp
... ... @@ -13,7 +13,7 @@ namespace redisx {
13 13 // Global mutex to manage waiting for connected state
14 14 mutex connected_lock;
15 15  
16   -// Map of ev_timer events to pointers to CommandAsync objects
  16 +// Map of ev_timer events to pointers to Command objects
17 17 // Used to get the object back from the timer watcher callback
18 18 unordered_map<ev_timer*, void*> timer_callbacks;
19 19  
... ... @@ -99,7 +99,7 @@ void Redis::block_until_stopped() {
99 99 * true if succeeded, false otherwise.
100 100 */
101 101 template<class ReplyT>
102   -bool submit_to_server(CommandAsync<ReplyT>* cmd_obj) {
  102 +bool submit_to_server(Command<ReplyT>* cmd_obj) {
103 103 if (redisAsyncCommand(cmd_obj->c, command_callback<ReplyT>, (void*)cmd_obj, cmd_obj->cmd.c_str()) != REDIS_OK) {
104 104 cerr << "[ERROR] Async command \"" << cmd_obj->cmd << "\": " << cmd_obj->c->errstr << endl;
105 105 cmd_obj->free_if_done();
... ... @@ -110,7 +110,7 @@ bool submit_to_server(CommandAsync&lt;ReplyT&gt;* cmd_obj) {
110 110  
111 111 template<class ReplyT>
112 112 void submit_command_callback(struct ev_loop* loop, ev_timer* timer, int revents) {
113   - auto cmd_obj = (CommandAsync<ReplyT>*)timer_callbacks.at(timer);
  113 + auto cmd_obj = (Command<ReplyT>*)timer_callbacks.at(timer);
114 114 submit_to_server<ReplyT>(cmd_obj);
115 115 }
116 116  
... ... @@ -121,7 +121,7 @@ bool Redis::process_queued_command(void* cmd_ptr) {
121 121  
122 122 auto it = command_map.find(cmd_ptr);
123 123 if(it == command_map.end()) return false;
124   - CommandAsync<ReplyT>* cmd_obj = it->second;
  124 + Command<ReplyT>* cmd_obj = it->second;
125 125 command_map.erase(cmd_ptr);
126 126  
127 127 if((cmd_obj->repeat == 0) && (cmd_obj->after == 0)) {
... ... @@ -131,11 +131,14 @@ bool Redis::process_queued_command(void* cmd_ptr) {
131 131 timer_callbacks[cmd_obj->timer] = (void*)cmd_obj;
132 132 ev_timer_init(cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat);
133 133 ev_timer_start(EV_DEFAULT_ cmd_obj->timer);
  134 +
  135 + cmd_obj->timer_guard.unlock();
134 136 }
135 137  
136 138 return true;
137 139 }
138 140  
  141 +
139 142 void Redis::process_queued_commands() {
140 143  
141 144 lock_guard<mutex> lg(queue_guard);
... ... @@ -162,15 +165,15 @@ long Redis::num_commands_processed() {
162 165  
163 166 // ----------------------------
164 167  
165   -template<> unordered_map<void*, CommandAsync<const redisReply*>*>& Redis::get_command_map() { return commands_redis_reply; }
  168 +template<> unordered_map<void*, Command<const redisReply*>*>& Redis::get_command_map() { return commands_redis_reply; }
166 169 template<>
167   -void invoke_callback(CommandAsync<const redisReply*>* cmd_obj, redisReply* reply) {
  170 +void invoke_callback(Command<const redisReply*>* cmd_obj, redisReply* reply) {
168 171 cmd_obj->invoke(reply);
169 172 }
170 173  
171   -template<> unordered_map<void*, CommandAsync<const string&>*>& Redis::get_command_map() { return commands_string_r; }
  174 +template<> unordered_map<void*, Command<const string&>*>& Redis::get_command_map() { return commands_string_r; }
172 175 template<>
173   -void invoke_callback(CommandAsync<const string&>* cmd_obj, redisReply* reply) {
  176 +void invoke_callback(Command<const string&>* cmd_obj, redisReply* reply) {
174 177 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) {
175 178 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl;
176 179 return;
... ... @@ -179,9 +182,9 @@ void invoke_callback(CommandAsync&lt;const string&amp;&gt;* cmd_obj, redisReply* reply) {
179 182 cmd_obj->invoke(reply->str);
180 183 }
181 184  
182   -template<> unordered_map<void*, CommandAsync<const char*>*>& Redis::get_command_map() { return commands_char_p; }
  185 +template<> unordered_map<void*, Command<const char*>*>& Redis::get_command_map() { return commands_char_p; }
183 186 template<>
184   -void invoke_callback(CommandAsync<const char*>* cmd_obj, redisReply* reply) {
  187 +void invoke_callback(Command<const char*>* cmd_obj, redisReply* reply) {
185 188 if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) {
186 189 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl;
187 190 return;
... ... @@ -189,9 +192,9 @@ void invoke_callback(CommandAsync&lt;const char*&gt;* cmd_obj, redisReply* reply) {
189 192 cmd_obj->invoke(reply->str);
190 193 }
191 194  
192   -template<> unordered_map<void*, CommandAsync<int>*>& Redis::get_command_map() { return commands_int; }
  195 +template<> unordered_map<void*, Command<int>*>& Redis::get_command_map() { return commands_int; }
193 196 template<>
194   -void invoke_callback(CommandAsync<int>* cmd_obj, redisReply* reply) {
  197 +void invoke_callback(Command<int>* cmd_obj, redisReply* reply) {
195 198 if(reply->type != REDIS_REPLY_INTEGER) {
196 199 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl;
197 200 return;
... ... @@ -199,9 +202,9 @@ void invoke_callback(CommandAsync&lt;int&gt;* cmd_obj, redisReply* reply) {
199 202 cmd_obj->invoke((int)reply->integer);
200 203 }
201 204  
202   -template<> unordered_map<void*, CommandAsync<long long int>*>& Redis::get_command_map() { return commands_long_long_int; }
  205 +template<> unordered_map<void*, Command<long long int>*>& Redis::get_command_map() { return commands_long_long_int; }
203 206 template<>
204   -void invoke_callback(CommandAsync<long long int>* cmd_obj, redisReply* reply) {
  207 +void invoke_callback(Command<long long int>* cmd_obj, redisReply* reply) {
205 208 if(reply->type != REDIS_REPLY_INTEGER) {
206 209 cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl;
207 210 return;
... ...
src/redisx.hpp
... ... @@ -20,37 +20,9 @@
20 20 #include <hiredis/async.h>
21 21 #include <hiredis/adapters/libev.h>
22 22  
23   -namespace redisx {
  23 +#include "command.hpp"
24 24  
25   -template<class ReplyT>
26   -class CommandAsync {
27   -public:
28   - CommandAsync(
29   - redisAsyncContext* c,
30   - const std::string& cmd,
31   - const std::function<void(const std::string&, ReplyT)>& callback,
32   - double repeat, double after
33   - ) : c(c), cmd(cmd), callback(callback), repeat(repeat), after(after), done(false) {}
34   -
35   - redisAsyncContext* c;
36   - const std::string cmd;
37   - const std::function<void(const std::string&, ReplyT)> callback;
38   - double repeat;
39   - double after;
40   - bool done;
41   - ev_timer* timer;
42   -
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   - }
53   -};
  25 +namespace redisx {
54 26  
55 27 class Redis {
56 28  
... ... @@ -65,7 +37,7 @@ public:
65 37 void block_until_stopped();
66 38  
67 39 template<class ReplyT>
68   - void command(
  40 + Command<ReplyT>* command(
69 41 const std::string& cmd,
70 42 const std::function<void(const std::string&, ReplyT)>& callback = NULL,
71 43 double repeat = 0.0,
... ... @@ -74,6 +46,9 @@ public:
74 46  
75 47 void command(const char* command);
76 48  
  49 + template<class ReplyT>
  50 + bool cancel(Command<ReplyT>* cmd_obj);
  51 +
77 52 long num_commands_processed();
78 53  
79 54 // void get(const char* key, std::function<void(const std::string&, const char*)> callback);
... ... @@ -105,14 +80,14 @@ private:
105 80  
106 81 std::thread event_loop_thread;
107 82  
108   - std::unordered_map<void*, CommandAsync<const redisReply*>*> commands_redis_reply;
109   - std::unordered_map<void*, CommandAsync<const std::string&>*> commands_string_r;
110   - std::unordered_map<void*, CommandAsync<const char*>*> commands_char_p;
111   - std::unordered_map<void*, CommandAsync<int>*> commands_int;
112   - std::unordered_map<void*, CommandAsync<long long int>*> commands_long_long_int;
  83 + std::unordered_map<void*, Command<const redisReply*>*> commands_redis_reply;
  84 + std::unordered_map<void*, Command<const std::string&>*> commands_string_r;
  85 + std::unordered_map<void*, Command<const char*>*> commands_char_p;
  86 + std::unordered_map<void*, Command<int>*> commands_int;
  87 + std::unordered_map<void*, Command<long long int>*> commands_long_long_int;
113 88  
114 89 template<class ReplyT>
115   - std::unordered_map<void*, CommandAsync<ReplyT>*>& get_command_map();
  90 + std::unordered_map<void*, Command<ReplyT>*>& get_command_map();
116 91  
117 92 std::queue<void*> command_queue;
118 93 std::mutex queue_guard;
... ... @@ -126,7 +101,7 @@ private:
126 101  
127 102 template<class ReplyT>
128 103 void invoke_callback(
129   - CommandAsync<ReplyT>* cmd_obj,
  104 + Command<ReplyT>* cmd_obj,
130 105 redisReply* reply
131 106 );
132 107  
... ... @@ -134,7 +109,7 @@ template&lt;class ReplyT&gt;
134 109 void command_callback(redisAsyncContext *c, void *r, void *privdata) {
135 110  
136 111 redisReply *reply = (redisReply *) r;
137   - auto *cmd_obj = (CommandAsync<ReplyT> *) privdata;
  112 + auto *cmd_obj = (Command<ReplyT> *) privdata;
138 113  
139 114 if (reply->type == REDIS_REPLY_ERROR) {
140 115 std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl;
... ... @@ -153,7 +128,7 @@ void command_callback(redisAsyncContext *c, void *r, void *privdata) {
153 128 }
154 129  
155 130 template<class ReplyT>
156   -void Redis::command(
  131 +Command<ReplyT>* Redis::command(
157 132 const std::string& cmd,
158 133 const std::function<void(const std::string&, ReplyT)>& callback,
159 134 double repeat,
... ... @@ -161,9 +136,21 @@ void Redis::command(
161 136 ) {
162 137  
163 138 std::lock_guard<std::mutex> lg(queue_guard);
164   - auto* cmd_obj = new CommandAsync<ReplyT>(c, cmd, callback, repeat, after);
  139 + auto* cmd_obj = new Command<ReplyT>(c, cmd, callback, repeat, after);
165 140 get_command_map<ReplyT>()[(void*)cmd_obj] = cmd_obj;
166 141 command_queue.push((void*)cmd_obj);
  142 + return cmd_obj;
  143 +}
  144 +
  145 +template<class ReplyT>
  146 +bool Redis::cancel(Command<ReplyT>* cmd_obj) {
  147 +
  148 + // TODO erase from global timer_callbacks
  149 +
  150 + if((cmd_obj->repeat != 0) || (cmd_obj->after != 0))
  151 + ev_timer_stop(EV_DEFAULT_ cmd_obj->get_timer());
  152 +
  153 + return true;
167 154 }
168 155  
169 156 } // End namespace redis
... ...