Commit c21560d97a4a7df9dc0e1135c0082ff49984e6bf
1 parent
78585079
Removing timer_callbacks global, ev_timer allocation
Found out that ev_timer watchers have a void* data that allows currying information to the libev callbacks! That means I could stick the Command* in there and completely get rid of the global timer_callbacks map that was needed before. Additionally, we can move ev_timer into the Command object instead of separately allocating it on the heap. Simplifies management. Implemented a synchronous loop speed test based on the latest and greatest command_blocking(). Happy to see it is around 30k/s, which is 100% network delays.
Showing
6 changed files
with
82 additions
and
34 deletions
CMakeLists.txt
| ... | ... | @@ -42,3 +42,6 @@ set(LIB_ALL ${LIB_REDIS}) |
| 42 | 42 | |
| 43 | 43 | add_executable(simple_loop examples/simple_loop.cpp ${SRC_ALL}) |
| 44 | 44 | target_link_libraries(simple_loop ${LIB_REDIS}) |
| 45 | + | |
| 46 | +add_executable(simple_sync_loop examples/simple_sync_loop.cpp ${SRC_ALL}) | |
| 47 | +target_link_libraries(simple_sync_loop ${LIB_REDIS}) | ... | ... |
examples/simple_loop.cpp
| ... | ... | @@ -17,10 +17,12 @@ int main(int argc, char* argv[]) { |
| 17 | 17 | |
| 18 | 18 | Redis rdx = {"localhost", 6379}; |
| 19 | 19 | rdx.run(); |
| 20 | - | |
| 21 | - Command<int>* del_cmd = rdx.command_blocking<int>("DEL simple_loop:count"); | |
| 22 | - cout << "deleted key, reply: " << del_cmd->reply() << endl; | |
| 23 | - del_cmd->free(); | |
| 20 | +// | |
| 21 | +// Command<int>* del_cmd = rdx.command_blocking<int>("DEL simple_loop:count"); | |
| 22 | +// cout << "deleted key, reply: " << del_cmd->reply() << endl; | |
| 23 | +// del_cmd->free(); | |
| 24 | + if(rdx.command_blocking("DEL simple_loop:count")) cout << "Deleted simple_loop:count" << endl; | |
| 25 | + else cerr << "Failed to delete simple_loop:count" << endl; | |
| 24 | 26 | |
| 25 | 27 | Command<char*>* set_cmd = rdx.command_blocking<char*>("SET simple_loop:count 0"); |
| 26 | 28 | cout << "set key, reply: " << set_cmd->reply() << endl; | ... | ... |
examples/simple_sync_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 | +using namespace redisx; | |
| 10 | + | |
| 11 | +double time_s() { | |
| 12 | + unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::microseconds(1); | |
| 13 | + return (double)ms / 1e6; | |
| 14 | +} | |
| 15 | + | |
| 16 | +int main(int argc, char* argv[]) { | |
| 17 | + | |
| 18 | + Redis rdx = {"localhost", 6379}; | |
| 19 | + rdx.run(); | |
| 20 | + | |
| 21 | + if(rdx.command_blocking("DEL simple_loop:count")) cout << "Deleted simple_loop:count" << endl; | |
| 22 | + else cerr << "Failed to delete simple_loop:count" << endl; | |
| 23 | + | |
| 24 | + string cmd_str = "INCR simple_loop:count"; | |
| 25 | + | |
| 26 | + int count = 50000; | |
| 27 | + double t0 = time_s(); | |
| 28 | + | |
| 29 | + cout << "Running \"" << cmd_str << "\" " << count << " times." << endl; | |
| 30 | + | |
| 31 | + for(int i = 0; i < count; i++) { | |
| 32 | + Command<int>* c = rdx.command_blocking<int>(cmd_str); | |
| 33 | + if(c->status() != REDIS_OK) cerr << "Bad reply, code: " << c->status() << endl; | |
| 34 | + } | |
| 35 | + | |
| 36 | + cout << "At the end, simple_loop:count = " | |
| 37 | + << rdx.command_blocking<string>("GET simple_loop:count")->reply() << endl; | |
| 38 | + | |
| 39 | + rdx.stop(); | |
| 40 | + | |
| 41 | + double t_elapsed = time_s() - t0; | |
| 42 | + double actual_freq = (double)count / t_elapsed; | |
| 43 | + | |
| 44 | + cout << "Sent " << count << " commands in " << t_elapsed << "s, " | |
| 45 | + << "that's " << actual_freq << " commands/s." << endl; | |
| 46 | + | |
| 47 | + return 0; | |
| 48 | +} | ... | ... |
src/command.hpp
| ... | ... | @@ -74,12 +74,12 @@ private: |
| 74 | 74 | |
| 75 | 75 | std::atomic_bool completed; |
| 76 | 76 | |
| 77 | - ev_timer* timer; | |
| 77 | + ev_timer timer; | |
| 78 | 78 | std::mutex timer_guard; |
| 79 | 79 | |
| 80 | 80 | ev_timer* get_timer() { |
| 81 | 81 | std::lock_guard<std::mutex> lg(timer_guard); |
| 82 | - return timer; | |
| 82 | + return &timer; | |
| 83 | 83 | } |
| 84 | 84 | }; |
| 85 | 85 | ... | ... |
src/redisx.cpp
| ... | ... | @@ -11,10 +11,8 @@ using namespace std; |
| 11 | 11 | |
| 12 | 12 | namespace redisx { |
| 13 | 13 | |
| 14 | -// Default construct the static map | |
| 15 | -std::unordered_map<ev_timer*, void*> Redis::timer_callbacks; | |
| 16 | - | |
| 17 | 14 | // Global mutex to manage waiting for connected state |
| 15 | +// TODO get rid of this as the only global variable? | |
| 18 | 16 | mutex connected_lock; |
| 19 | 17 | |
| 20 | 18 | /** |
| ... | ... | @@ -119,12 +117,14 @@ bool submit_to_server(Command<ReplyT>* cmd_obj) { |
| 119 | 117 | |
| 120 | 118 | template<class ReplyT> |
| 121 | 119 | void submit_command_callback(struct ev_loop* loop, ev_timer* timer, int revents) { |
| 122 | - auto cmd_obj = (Command<ReplyT>*)Redis::timer_callbacks.at(timer); | |
| 123 | - if(cmd_obj == NULL) { | |
| 120 | + | |
| 121 | + // Check if canceled | |
| 122 | + if(timer->data == NULL) { | |
| 124 | 123 | cerr << "[WARNING] Skipping event, has been canceled." << endl; |
| 125 | - Redis::timer_callbacks.erase(timer); | |
| 126 | 124 | return; |
| 127 | 125 | } |
| 126 | + | |
| 127 | + auto cmd_obj = (Command<ReplyT>*)timer->data; | |
| 128 | 128 | submit_to_server<ReplyT>(cmd_obj); |
| 129 | 129 | } |
| 130 | 130 | |
| ... | ... | @@ -141,14 +141,11 @@ bool Redis::process_queued_command(void* cmd_ptr) { |
| 141 | 141 | if((cmd_obj->repeat == 0) && (cmd_obj->after == 0)) { |
| 142 | 142 | submit_to_server<ReplyT>(cmd_obj); |
| 143 | 143 | } else { |
| 144 | - // TODO manage memory somehow | |
| 145 | - cmd_obj->timer = new ev_timer(); | |
| 146 | 144 | |
| 147 | - // TODO use cmd_obj->timer->data instead of timer callbacks!!!!! | |
| 145 | + cmd_obj->timer.data = (void*)cmd_obj; | |
| 148 | 146 | |
| 149 | - timer_callbacks[cmd_obj->timer] = (void*)cmd_obj; | |
| 150 | - ev_timer_init(cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat); | |
| 151 | - ev_timer_start(EV_DEFAULT_ cmd_obj->timer); | |
| 147 | + ev_timer_init(&cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat); | |
| 148 | + ev_timer_start(EV_DEFAULT_ &cmd_obj->timer); | |
| 152 | 149 | |
| 153 | 150 | cmd_obj->timer_guard.unlock(); |
| 154 | 151 | } |
| ... | ... | @@ -242,8 +239,11 @@ void Redis::command(const string& cmd) { |
| 242 | 239 | command<redisReply*>(cmd, NULL); |
| 243 | 240 | } |
| 244 | 241 | |
| 245 | -void Redis::command_blocking(const string& cmd) { | |
| 246 | - command_blocking<redisReply*>(cmd); | |
| 242 | +bool Redis::command_blocking(const string& cmd) { | |
| 243 | + Command<redisReply*>* c = command_blocking<redisReply*>(cmd); | |
| 244 | + bool succeeded = (c->status() == REDISX_OK); | |
| 245 | + c->free(); | |
| 246 | + return succeeded; | |
| 247 | 247 | } |
| 248 | 248 | |
| 249 | 249 | } // End namespace redis | ... | ... |
src/redisx.hpp
| ... | ... | @@ -49,12 +49,11 @@ public: |
| 49 | 49 | template<class ReplyT> |
| 50 | 50 | bool cancel(Command<ReplyT>* cmd_obj); |
| 51 | 51 | |
| 52 | - void command(const std::string& command); | |
| 53 | - | |
| 54 | 52 | template<class ReplyT> |
| 55 | 53 | Command<ReplyT>* command_blocking(const std::string& cmd); |
| 56 | 54 | |
| 57 | - void command_blocking(const std::string& command); | |
| 55 | + void command(const std::string& command); | |
| 56 | + bool command_blocking(const std::string& command); | |
| 58 | 57 | |
| 59 | 58 | long num_commands_processed(); |
| 60 | 59 | |
| ... | ... | @@ -62,10 +61,6 @@ public: |
| 62 | 61 | // void subscribe(std::string channel, std::function<void(std::string channel, std::string msg)> callback); |
| 63 | 62 | // void unsubscribe(std::string channel); |
| 64 | 63 | |
| 65 | - // Map of ev_timer events to pointers to Command objects | |
| 66 | - // Used to get the object back from the timer watcher callback | |
| 67 | - static std::unordered_map<ev_timer*, void*> timer_callbacks; | |
| 68 | - | |
| 69 | 64 | private: |
| 70 | 65 | |
| 71 | 66 | // Redis server |
| ... | ... | @@ -154,13 +149,12 @@ bool Redis::cancel(Command<ReplyT>* cmd_obj) { |
| 154 | 149 | return false; |
| 155 | 150 | } |
| 156 | 151 | |
| 157 | - timer_callbacks.at(cmd_obj->timer) = NULL; | |
| 152 | + cmd_obj->timer.data = NULL; | |
| 158 | 153 | |
| 159 | 154 | std::lock_guard<std::mutex> lg(cmd_obj->timer_guard); |
| 160 | 155 | if((cmd_obj->repeat != 0) || (cmd_obj->after != 0)) |
| 161 | - ev_timer_stop(EV_DEFAULT_ cmd_obj->timer); | |
| 156 | + ev_timer_stop(EV_DEFAULT_ &cmd_obj->timer); | |
| 162 | 157 | |
| 163 | - delete cmd_obj->timer; | |
| 164 | 158 | cmd_obj->completed = true; |
| 165 | 159 | |
| 166 | 160 | return true; |
| ... | ... | @@ -179,21 +173,22 @@ Command<ReplyT>* Redis::command_blocking(const std::string& cmd) { |
| 179 | 173 | |
| 180 | 174 | Command<ReplyT>* cmd_obj = command<ReplyT>(cmd, |
| 181 | 175 | [&val, &status, &m, &cv](const std::string& cmd_str, const ReplyT& reply) { |
| 182 | - std::unique_lock<std::mutex> lk(m); | |
| 176 | + std::unique_lock<std::mutex> ul(m); | |
| 183 | 177 | val = reply; |
| 184 | 178 | status = REDISX_OK; |
| 185 | - lk.unlock(); | |
| 179 | + ul.unlock(); | |
| 186 | 180 | cv.notify_one(); |
| 187 | 181 | }, |
| 188 | 182 | [&status, &m, &cv](const std::string& cmd_str, int error) { |
| 189 | - std::unique_lock<std::mutex> lk(m); | |
| 183 | + std::unique_lock<std::mutex> ul(m); | |
| 190 | 184 | status = error; |
| 191 | - lk.unlock(); | |
| 185 | + ul.unlock(); | |
| 192 | 186 | cv.notify_one(); |
| 193 | 187 | }, |
| 194 | 188 | 0, 0, false // No repeats, don't free memory |
| 195 | 189 | ); |
| 196 | 190 | |
| 191 | + // Wait until a callback is invoked | |
| 197 | 192 | cv.wait(lk, [&status] { return status != REDISX_UNINIT; }); |
| 198 | 193 | |
| 199 | 194 | cmd_obj->reply_val = val; | ... | ... |