Commit df429ec2f7c57ef986e05d26be87b46578446448
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.
Showing
5 changed files
with
132 additions
and
69 deletions
README.md
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<ReplyT>* 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<const string&>* 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<const char*>* 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<int>* 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<class ReplyT> |
| 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 | ... | ... |