Commit 7858507963e633d14315205d7e973227f4420cb9
1 parent
916eafff
Fix memory management, concurrency issues
Several hours of multithreaded debugging to find a nasty extra ampersand. command_blocking() now returns a Command object with the value and status accessible through methods. Added the option to free memory or not for Command objects when command() is called. If free_memory = true, then the Command object is freed after the callback or error callback returns. If free_memory = false, then the user must call cmd->free(). To implement this, we have to pass in a blank function to the freeObject entry in the hiredis redisReader, because otherwise it automatically frees memory. This was not cool for the blocking case.
Showing
4 changed files
with
154 additions
and
136 deletions
examples/simple_loop.cpp
| @@ -6,6 +6,7 @@ | @@ -6,6 +6,7 @@ | ||
| 6 | #include "../src/redisx.hpp" | 6 | #include "../src/redisx.hpp" |
| 7 | 7 | ||
| 8 | using namespace std; | 8 | using namespace std; |
| 9 | +using namespace redisx; | ||
| 9 | 10 | ||
| 10 | double time_s() { | 11 | double time_s() { |
| 11 | unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::microseconds(1); | 12 | unsigned long ms = chrono::system_clock::now().time_since_epoch() / chrono::microseconds(1); |
| @@ -14,14 +15,22 @@ double time_s() { | @@ -14,14 +15,22 @@ double time_s() { | ||
| 14 | 15 | ||
| 15 | int main(int argc, char* argv[]) { | 16 | int main(int argc, char* argv[]) { |
| 16 | 17 | ||
| 17 | - redisx::Redis rdx = {"localhost", 6379}; | 18 | + Redis rdx = {"localhost", 6379}; |
| 18 | rdx.run(); | 19 | rdx.run(); |
| 19 | 20 | ||
| 20 | - rdx.command_blocking("DEL simple_loop:count"); | ||
| 21 | - rdx.command_blocking("SET simple_loop:count 0"); | 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(); | ||
| 22 | 24 | ||
| 23 | - cout << "At the start, simple_loop:count = " | ||
| 24 | - << rdx.command_blocking<string>("GET simple_loop:count") << endl; | 25 | + Command<char*>* set_cmd = rdx.command_blocking<char*>("SET simple_loop:count 0"); |
| 26 | + cout << "set key, reply: " << set_cmd->reply() << endl; | ||
| 27 | + set_cmd->free(); | ||
| 28 | + | ||
| 29 | + Command<char*>* count_cmd = rdx.command_blocking<char*>("GET simple_loop:count"); | ||
| 30 | + if(count_cmd->status() == REDISX_OK) { | ||
| 31 | + cout << "At the start, simple_loop:count = " << count_cmd->reply() << endl; | ||
| 32 | + } | ||
| 33 | + count_cmd->free(); | ||
| 25 | 34 | ||
| 26 | string cmd_str = "INCR simple_loop:count"; | 35 | string cmd_str = "INCR simple_loop:count"; |
| 27 | 36 | ||
| @@ -33,7 +42,7 @@ int main(int argc, char* argv[]) { | @@ -33,7 +42,7 @@ int main(int argc, char* argv[]) { | ||
| 33 | << "s for " << t << "s..." << endl; | 42 | << "s for " << t << "s..." << endl; |
| 34 | 43 | ||
| 35 | atomic_int count(0); | 44 | atomic_int count(0); |
| 36 | - redisx::Command<int>* c = rdx.command<int>( | 45 | + Command<int>* c = rdx.command<int>( |
| 37 | cmd_str, | 46 | cmd_str, |
| 38 | [&count](const string &cmd, const int& value) { count++; }, | 47 | [&count](const string &cmd, const int& value) { count++; }, |
| 39 | NULL, | 48 | NULL, |
| @@ -46,7 +55,7 @@ int main(int argc, char* argv[]) { | @@ -46,7 +55,7 @@ int main(int argc, char* argv[]) { | ||
| 46 | rdx.cancel(c); | 55 | rdx.cancel(c); |
| 47 | 56 | ||
| 48 | cout << "At the end, simple_loop:count = " | 57 | cout << "At the end, simple_loop:count = " |
| 49 | - << rdx.command_blocking<string>("GET simple_loop:count") << endl; | 58 | + << rdx.command_blocking<string>("GET simple_loop:count")->reply() << endl; |
| 50 | 59 | ||
| 51 | rdx.stop(); | 60 | rdx.stop(); |
| 52 | 61 |
src/command.hpp
| @@ -14,6 +14,14 @@ | @@ -14,6 +14,14 @@ | ||
| 14 | 14 | ||
| 15 | namespace redisx { | 15 | namespace redisx { |
| 16 | 16 | ||
| 17 | +static const int REDISX_UNINIT = -1; | ||
| 18 | +static const int REDISX_OK = 0; | ||
| 19 | +static const int REDISX_SEND_ERROR = 1; | ||
| 20 | +static const int REDISX_WRONG_TYPE = 2; | ||
| 21 | +static const int REDISX_NIL_REPLY = 3; | ||
| 22 | +static const int REDISX_ERROR_REPLY = 4; | ||
| 23 | +static const int REDISX_TIMEOUT = 5; | ||
| 24 | + | ||
| 17 | template<class ReplyT> | 25 | template<class ReplyT> |
| 18 | class Command { | 26 | class Command { |
| 19 | 27 | ||
| @@ -21,28 +29,48 @@ friend class Redis; | @@ -21,28 +29,48 @@ friend class Redis; | ||
| 21 | 29 | ||
| 22 | public: | 30 | public: |
| 23 | Command( | 31 | Command( |
| 24 | - redisAsyncContext* c, | ||
| 25 | - const std::string& cmd, | ||
| 26 | - const std::function<void(const std::string&, const ReplyT&)>& callback, | ||
| 27 | - const std::function<void(const std::string&, int status)>& error_callback, | ||
| 28 | - double repeat, double after | 32 | + redisAsyncContext* c, |
| 33 | + const std::string& cmd, | ||
| 34 | + const std::function<void(const std::string&, const ReplyT&)>& callback, | ||
| 35 | + const std::function<void(const std::string&, int status)>& error_callback, | ||
| 36 | + double repeat, double after, | ||
| 37 | + bool free_memory | ||
| 29 | ); | 38 | ); |
| 30 | 39 | ||
| 31 | const std::string cmd; | 40 | const std::string cmd; |
| 32 | const double repeat; | 41 | const double repeat; |
| 33 | const double after; | 42 | const double after; |
| 34 | 43 | ||
| 44 | + const bool free_memory; | ||
| 45 | + | ||
| 35 | redisAsyncContext* c; | 46 | redisAsyncContext* c; |
| 47 | + redisReply* reply_obj; | ||
| 36 | 48 | ||
| 37 | std::atomic_int pending; | 49 | std::atomic_int pending; |
| 38 | 50 | ||
| 39 | void invoke(const ReplyT& reply); | 51 | void invoke(const ReplyT& reply); |
| 40 | void invoke_error(int status); | 52 | void invoke_error(int status); |
| 41 | 53 | ||
| 54 | + const ReplyT& reply(); | ||
| 55 | + int status() { return reply_status; }; | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * Called by the user to free the redisReply object, when the free_memory | ||
| 59 | + * flag is set to false for a command. | ||
| 60 | + */ | ||
| 61 | + void free(); | ||
| 62 | + | ||
| 63 | + void free_reply_object(); | ||
| 64 | + | ||
| 42 | private: | 65 | private: |
| 43 | 66 | ||
| 44 | const std::function<void(const std::string&, const ReplyT&)> callback; | 67 | const std::function<void(const std::string&, const ReplyT&)> callback; |
| 45 | - const std::function<void(const std::string&, int status)>& error_callback; | 68 | + const std::function<void(const std::string&, int status)> error_callback; |
| 69 | + | ||
| 70 | + // Place to store the reply value and status. | ||
| 71 | + // ONLY for blocking commands | ||
| 72 | + ReplyT reply_val; | ||
| 73 | + int reply_status; | ||
| 46 | 74 | ||
| 47 | std::atomic_bool completed; | 75 | std::atomic_bool completed; |
| 48 | 76 | ||
| @@ -61,33 +89,70 @@ Command<ReplyT>::Command( | @@ -61,33 +89,70 @@ Command<ReplyT>::Command( | ||
| 61 | const std::string& cmd, | 89 | const std::string& cmd, |
| 62 | const std::function<void(const std::string&, const ReplyT&)>& callback, | 90 | const std::function<void(const std::string&, const ReplyT&)>& callback, |
| 63 | const std::function<void(const std::string&, int status)>& error_callback, | 91 | const std::function<void(const std::string&, int status)>& error_callback, |
| 64 | - double repeat, double after | ||
| 65 | -) : cmd(cmd), repeat(repeat), after(after), c(c), pending(0), | ||
| 66 | - callback(callback), error_callback(error_callback), completed(false) | 92 | + double repeat, double after, bool free_memory |
| 93 | +) : cmd(cmd), repeat(repeat), after(after), free_memory(free_memory), c(c), reply_obj(NULL), | ||
| 94 | + pending(0), callback(callback), error_callback(error_callback), completed(false) | ||
| 67 | { | 95 | { |
| 68 | timer_guard.lock(); | 96 | timer_guard.lock(); |
| 69 | } | 97 | } |
| 70 | 98 | ||
| 71 | template<class ReplyT> | 99 | template<class ReplyT> |
| 72 | -void Command<ReplyT>::invoke(const ReplyT& reply) { | 100 | +void Command<ReplyT>::invoke(const ReplyT& r) { |
| 101 | + | ||
| 102 | + if(callback) callback(cmd, r); | ||
| 73 | 103 | ||
| 74 | - if(callback != NULL) callback(cmd, reply); | ||
| 75 | pending--; | 104 | pending--; |
| 76 | - if((pending == 0) && (completed || (repeat == 0))) { | ||
| 77 | -// std::cout << "invoking success, reply: " << reply << std::endl; | ||
| 78 | -// std::cout << "Freeing cmd " << cmd << " in success invoke" << std::endl; | 105 | + if(!free_memory) return; |
| 106 | + if(pending != 0) return; | ||
| 107 | + if(completed || (repeat == 0)) { | ||
| 108 | +// std::cout << cmd << ": suicide!" << std::endl; | ||
| 79 | delete this; | 109 | delete this; |
| 80 | } | 110 | } |
| 81 | } | 111 | } |
| 82 | 112 | ||
| 83 | template<class ReplyT> | 113 | template<class ReplyT> |
| 84 | void Command<ReplyT>::invoke_error(int status) { | 114 | void Command<ReplyT>::invoke_error(int status) { |
| 85 | - if(error_callback != NULL) error_callback(cmd, status); | 115 | + |
| 116 | + if(error_callback) error_callback(cmd, status); | ||
| 117 | + | ||
| 86 | pending--; | 118 | pending--; |
| 87 | - if((pending == 0) && (completed || (repeat == 0))) { | ||
| 88 | -// std::cout << "Freeing cmd " << cmd << " in error invoke" << std::endl; | 119 | + if(!free_memory) return; |
| 120 | + if(pending != 0) return; | ||
| 121 | + if(completed || (repeat == 0)) { | ||
| 122 | +// std::cout << cmd << ": suicide!" << std::endl; | ||
| 89 | delete this; | 123 | delete this; |
| 90 | } | 124 | } |
| 91 | } | 125 | } |
| 92 | 126 | ||
| 127 | +template<class ReplyT> | ||
| 128 | +void Command<ReplyT>::free_reply_object() { | ||
| 129 | + | ||
| 130 | + if(reply_obj == NULL) { | ||
| 131 | + std::cerr << "[ERROR] " << cmd << ": Attempting to double free reply object!" << std::endl; | ||
| 132 | + return; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + freeReplyObject(reply_obj); | ||
| 136 | + reply_obj = NULL; | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +template<class ReplyT> | ||
| 140 | +void Command<ReplyT>::free() { | ||
| 141 | + | ||
| 142 | + free_reply_object(); | ||
| 143 | + | ||
| 144 | + // Commit suicide | ||
| 145 | +// std::cout << cmd << ": suicide, by calling free()!" << std::endl; | ||
| 146 | + delete this; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +template<class ReplyT> | ||
| 150 | +const ReplyT& Command<ReplyT>::reply() { | ||
| 151 | + if(reply_status != REDISX_OK) { | ||
| 152 | + std::cout << "[WARNING] " << cmd | ||
| 153 | + << ": Accessing value of reply with status != OK." << std::endl; | ||
| 154 | + } | ||
| 155 | + return reply_val; | ||
| 156 | +} | ||
| 157 | + | ||
| 93 | } // End namespace redis | 158 | } // End namespace redis |
src/redisx.cpp
| @@ -17,11 +17,19 @@ std::unordered_map<ev_timer*, void*> Redis::timer_callbacks; | @@ -17,11 +17,19 @@ std::unordered_map<ev_timer*, void*> Redis::timer_callbacks; | ||
| 17 | // Global mutex to manage waiting for connected state | 17 | // Global mutex to manage waiting for connected state |
| 18 | mutex connected_lock; | 18 | mutex connected_lock; |
| 19 | 19 | ||
| 20 | +/** | ||
| 21 | +* Dummy function given to hiredis to use for freeing reply | ||
| 22 | +* objects, so the memory can be managed here instead. | ||
| 23 | +*/ | ||
| 24 | +void dummy_free_reply(void *reply) {} | ||
| 25 | + | ||
| 20 | void connected(const redisAsyncContext *c, int status) { | 26 | void connected(const redisAsyncContext *c, int status) { |
| 21 | if (status != REDIS_OK) { | 27 | if (status != REDIS_OK) { |
| 22 | cerr << "[ERROR] Connecting to Redis: " << c->errstr << endl; | 28 | cerr << "[ERROR] Connecting to Redis: " << c->errstr << endl; |
| 23 | return; | 29 | return; |
| 24 | } | 30 | } |
| 31 | + | ||
| 32 | + c->c.reader->fn->freeObject = dummy_free_reply; | ||
| 25 | cout << "Connected to Redis." << endl; | 33 | cout << "Connected to Redis." << endl; |
| 26 | connected_lock.unlock(); | 34 | connected_lock.unlock(); |
| 27 | } | 35 | } |
| @@ -31,6 +39,7 @@ void disconnected(const redisAsyncContext *c, int status) { | @@ -31,6 +39,7 @@ void disconnected(const redisAsyncContext *c, int status) { | ||
| 31 | cerr << "[ERROR] Disconnecting from Redis: " << c->errstr << endl; | 39 | cerr << "[ERROR] Disconnecting from Redis: " << c->errstr << endl; |
| 32 | return; | 40 | return; |
| 33 | } | 41 | } |
| 42 | + c->c.reader->fn->freeObject = freeReplyObject; | ||
| 34 | cout << "Disconnected from Redis." << endl; | 43 | cout << "Disconnected from Redis." << endl; |
| 35 | connected_lock.lock(); | 44 | connected_lock.lock(); |
| 36 | } | 45 | } |
| @@ -101,7 +110,7 @@ template<class ReplyT> | @@ -101,7 +110,7 @@ template<class ReplyT> | ||
| 101 | bool submit_to_server(Command<ReplyT>* cmd_obj) { | 110 | bool submit_to_server(Command<ReplyT>* cmd_obj) { |
| 102 | cmd_obj->pending++; | 111 | cmd_obj->pending++; |
| 103 | if (redisAsyncCommand(cmd_obj->c, command_callback<ReplyT>, (void*)cmd_obj, cmd_obj->cmd.c_str()) != REDIS_OK) { | 112 | 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; | 113 | + cerr << "[ERROR] Could not send \"" << cmd_obj->cmd << "\": " << cmd_obj->c->errstr << endl; |
| 105 | cmd_obj->invoke_error(REDISX_SEND_ERROR); | 114 | cmd_obj->invoke_error(REDISX_SEND_ERROR); |
| 106 | return false; | 115 | return false; |
| 107 | } | 116 | } |
| @@ -134,6 +143,9 @@ bool Redis::process_queued_command(void* cmd_ptr) { | @@ -134,6 +143,9 @@ bool Redis::process_queued_command(void* cmd_ptr) { | ||
| 134 | } else { | 143 | } else { |
| 135 | // TODO manage memory somehow | 144 | // TODO manage memory somehow |
| 136 | cmd_obj->timer = new ev_timer(); | 145 | cmd_obj->timer = new ev_timer(); |
| 146 | + | ||
| 147 | + // TODO use cmd_obj->timer->data instead of timer callbacks!!!!! | ||
| 148 | + | ||
| 137 | timer_callbacks[cmd_obj->timer] = (void*)cmd_obj; | 149 | timer_callbacks[cmd_obj->timer] = (void*)cmd_obj; |
| 138 | ev_timer_init(cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat); | 150 | ev_timer_init(cmd_obj->timer, submit_command_callback<ReplyT>, cmd_obj->after, cmd_obj->repeat); |
| 139 | ev_timer_start(EV_DEFAULT_ cmd_obj->timer); | 151 | ev_timer_start(EV_DEFAULT_ cmd_obj->timer); |
| @@ -175,12 +187,6 @@ template<> | @@ -175,12 +187,6 @@ template<> | ||
| 175 | void invoke_callback(Command<redisReply*>* cmd_obj, redisReply* reply) { | 187 | void invoke_callback(Command<redisReply*>* cmd_obj, redisReply* reply) { |
| 176 | cmd_obj->invoke(reply); | 188 | cmd_obj->invoke(reply); |
| 177 | } | 189 | } |
| 178 | -template<> redisReply* Redis::copy_reply(const redisReply*& reply) { | ||
| 179 | - // TODO get rid of this it is dumb. | ||
| 180 | - auto* copy = new redisReply; | ||
| 181 | - *copy = *reply; | ||
| 182 | - return copy; | ||
| 183 | -} | ||
| 184 | 190 | ||
| 185 | template<> unordered_map<void*, Command<string>*>& Redis::get_command_map() { return commands_string_r; } | 191 | template<> unordered_map<void*, Command<string>*>& Redis::get_command_map() { return commands_string_r; } |
| 186 | template<> | 192 | template<> |
| @@ -194,7 +200,6 @@ void invoke_callback(Command<string>* cmd_obj, redisReply* reply) { | @@ -194,7 +200,6 @@ void invoke_callback(Command<string>* cmd_obj, redisReply* reply) { | ||
| 194 | string s = reply->str; | 200 | string s = reply->str; |
| 195 | cmd_obj->invoke(s); | 201 | cmd_obj->invoke(s); |
| 196 | } | 202 | } |
| 197 | -template<> string Redis::copy_reply(const string& reply) { return reply; } | ||
| 198 | 203 | ||
| 199 | template<> unordered_map<void*, Command<char*>*>& Redis::get_command_map() { return commands_char_p; } | 204 | template<> unordered_map<void*, Command<char*>*>& Redis::get_command_map() { return commands_char_p; } |
| 200 | template<> | 205 | template<> |
| @@ -206,13 +211,6 @@ void invoke_callback(Command<char*>* cmd_obj, redisReply* reply) { | @@ -206,13 +211,6 @@ void invoke_callback(Command<char*>* cmd_obj, redisReply* reply) { | ||
| 206 | } | 211 | } |
| 207 | cmd_obj->invoke(reply->str); | 212 | cmd_obj->invoke(reply->str); |
| 208 | } | 213 | } |
| 209 | -template<> char* Redis::copy_reply(const char*& reply) { | ||
| 210 | - // Here, reply MUST be null terminated! | ||
| 211 | - size_t len = strlen(reply); | ||
| 212 | - auto* copy = new char[len+1]; | ||
| 213 | - strcpy(copy, reply); | ||
| 214 | - return copy; | ||
| 215 | -} | ||
| 216 | 214 | ||
| 217 | template<> unordered_map<void*, Command<int>*>& Redis::get_command_map() { return commands_int; } | 215 | template<> unordered_map<void*, Command<int>*>& Redis::get_command_map() { return commands_int; } |
| 218 | template<> | 216 | template<> |
| @@ -236,9 +234,6 @@ void invoke_callback(Command<long long int>* cmd_obj, redisReply* reply) { | @@ -236,9 +234,6 @@ void invoke_callback(Command<long long int>* cmd_obj, redisReply* reply) { | ||
| 236 | cmd_obj->invoke(reply->integer); | 234 | cmd_obj->invoke(reply->integer); |
| 237 | } | 235 | } |
| 238 | 236 | ||
| 239 | - | ||
| 240 | - | ||
| 241 | - | ||
| 242 | // ---------------------------- | 237 | // ---------------------------- |
| 243 | // Helpers | 238 | // Helpers |
| 244 | // ---------------------------- | 239 | // ---------------------------- |
| @@ -251,35 +246,4 @@ void Redis::command_blocking(const string& cmd) { | @@ -251,35 +246,4 @@ void Redis::command_blocking(const string& cmd) { | ||
| 251 | command_blocking<redisReply*>(cmd); | 246 | command_blocking<redisReply*>(cmd); |
| 252 | } | 247 | } |
| 253 | 248 | ||
| 254 | -//void Redis::get(const char* key, function<void(const string&, const char*)> callback) { | ||
| 255 | -// string cmd = string("GET ") + key; | ||
| 256 | -// command<const char*>(cmd.c_str(), callback); | ||
| 257 | -//} | ||
| 258 | -// | ||
| 259 | -//void Redis::set(const char* key, const char* value) { | ||
| 260 | -// string cmd = string("SET ") + key + " " + value; | ||
| 261 | -// command<const char*>(cmd.c_str(), [](const string& command, const char* reply) { | ||
| 262 | -// if(strcmp(reply, "OK")) | ||
| 263 | -// cerr << "[ERROR] " << command << ": SET failed with reply " << reply << endl; | ||
| 264 | -// }); | ||
| 265 | -//} | ||
| 266 | -// | ||
| 267 | -//void Redis::set(const char* key, const char* value, function<void(const string&, const char*)> callback) { | ||
| 268 | -// string cmd = string("SET ") + key + " " + value; | ||
| 269 | -// command<const char*>(cmd.c_str(), callback); | ||
| 270 | -//} | ||
| 271 | -// | ||
| 272 | -//void Redis::del(const char* key) { | ||
| 273 | -// string cmd = string("DEL ") + key; | ||
| 274 | -// command<long long int>(cmd.c_str(), [](const string& command, long long int num_deleted) { | ||
| 275 | -// if(num_deleted != 1) | ||
| 276 | -// cerr << "[ERROR] " << command << ": Deleted " << num_deleted << " keys." << endl; | ||
| 277 | -// }); | ||
| 278 | -//} | ||
| 279 | -// | ||
| 280 | -//void Redis::del(const char* key, function<void(const string&, long long int)> callback) { | ||
| 281 | -// string cmd = string("DEL ") + key; | ||
| 282 | -// command<long long int>(cmd.c_str(), callback); | ||
| 283 | -//} | ||
| 284 | - | ||
| 285 | } // End namespace redis | 249 | } // End namespace redis |
src/redisx.hpp
| @@ -22,14 +22,6 @@ | @@ -22,14 +22,6 @@ | ||
| 22 | 22 | ||
| 23 | #include "command.hpp" | 23 | #include "command.hpp" |
| 24 | 24 | ||
| 25 | -static const int REDISX_UNINIT = -1; | ||
| 26 | -static const int REDISX_OK = 0; | ||
| 27 | -static const int REDISX_SEND_ERROR = 1; | ||
| 28 | -static const int REDISX_WRONG_TYPE = 2; | ||
| 29 | -static const int REDISX_NIL_REPLY = 3; | ||
| 30 | -static const int REDISX_ERROR_REPLY = 4; | ||
| 31 | -static const int REDISX_TIMEOUT = 5; | ||
| 32 | - | ||
| 33 | namespace redisx { | 25 | namespace redisx { |
| 34 | 26 | ||
| 35 | class Redis { | 27 | class Redis { |
| @@ -46,11 +38,12 @@ public: | @@ -46,11 +38,12 @@ public: | ||
| 46 | 38 | ||
| 47 | template<class ReplyT> | 39 | template<class ReplyT> |
| 48 | Command<ReplyT>* command( | 40 | Command<ReplyT>* command( |
| 49 | - const std::string& cmd, | ||
| 50 | - const std::function<void(const std::string&, const ReplyT&)>& callback = NULL, | ||
| 51 | - const std::function<void(const std::string&, int status)>& error_callback = NULL, | ||
| 52 | - double repeat = 0.0, | ||
| 53 | - double after = 0.0 | 41 | + const std::string& cmd, |
| 42 | + const std::function<void(const std::string&, const ReplyT&)>& callback = NULL, | ||
| 43 | + const std::function<void(const std::string&, int status)>& error_callback = NULL, | ||
| 44 | + double repeat = 0.0, | ||
| 45 | + double after = 0.0, | ||
| 46 | + bool free_memory = true | ||
| 54 | ); | 47 | ); |
| 55 | 48 | ||
| 56 | template<class ReplyT> | 49 | template<class ReplyT> |
| @@ -59,20 +52,12 @@ public: | @@ -59,20 +52,12 @@ public: | ||
| 59 | void command(const std::string& command); | 52 | void command(const std::string& command); |
| 60 | 53 | ||
| 61 | template<class ReplyT> | 54 | template<class ReplyT> |
| 62 | - ReplyT command_blocking(const std::string& cmd); | 55 | + Command<ReplyT>* command_blocking(const std::string& cmd); |
| 63 | 56 | ||
| 64 | void command_blocking(const std::string& command); | 57 | void command_blocking(const std::string& command); |
| 65 | 58 | ||
| 66 | long num_commands_processed(); | 59 | long num_commands_processed(); |
| 67 | 60 | ||
| 68 | -// void get(const char* key, std::function<void(const std::string&, const char*)> callback); | ||
| 69 | -// | ||
| 70 | -// void set(const char* key, const char* value); | ||
| 71 | -// void set(const char* key, const char* value, std::function<void(const std::string&, const char*)> callback); | ||
| 72 | -// | ||
| 73 | -// void del(const char* key); | ||
| 74 | -// void del(const char* key, std::function<void(const std::string&, long long int)> callback); | ||
| 75 | - | ||
| 76 | // void publish(std::string channel, std::string msg); | 61 | // void publish(std::string channel, std::string msg); |
| 77 | // void subscribe(std::string channel, std::function<void(std::string channel, std::string msg)> callback); | 62 | // void subscribe(std::string channel, std::function<void(std::string channel, std::string msg)> callback); |
| 78 | // void unsubscribe(std::string channel); | 63 | // void unsubscribe(std::string channel); |
| @@ -113,9 +98,6 @@ private: | @@ -113,9 +98,6 @@ private: | ||
| 113 | 98 | ||
| 114 | template<class ReplyT> | 99 | template<class ReplyT> |
| 115 | bool process_queued_command(void* cmd_ptr); | 100 | bool process_queued_command(void* cmd_ptr); |
| 116 | - | ||
| 117 | - template<class ReplyT> | ||
| 118 | - ReplyT copy_reply(const ReplyT& reply); | ||
| 119 | }; | 101 | }; |
| 120 | 102 | ||
| 121 | // --------------------------- | 103 | // --------------------------- |
| @@ -129,34 +111,36 @@ void invoke_callback( | @@ -129,34 +111,36 @@ void invoke_callback( | ||
| 129 | template<class ReplyT> | 111 | template<class ReplyT> |
| 130 | void command_callback(redisAsyncContext *c, void *r, void *privdata) { | 112 | void command_callback(redisAsyncContext *c, void *r, void *privdata) { |
| 131 | 113 | ||
| 132 | - redisReply *reply = (redisReply *) r; | ||
| 133 | auto *cmd_obj = (Command<ReplyT> *) privdata; | 114 | auto *cmd_obj = (Command<ReplyT> *) privdata; |
| 115 | + cmd_obj->reply_obj = (redisReply *) r; | ||
| 134 | 116 | ||
| 135 | - if (reply->type == REDIS_REPLY_ERROR) { | ||
| 136 | - std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl; | 117 | + if (cmd_obj->reply_obj->type == REDIS_REPLY_ERROR) { |
| 118 | + std::cerr << "[ERROR redisx.hpp:121] " << cmd_obj->cmd << ": " << cmd_obj->reply_obj->str << std::endl; | ||
| 137 | cmd_obj->invoke_error(REDISX_ERROR_REPLY); | 119 | cmd_obj->invoke_error(REDISX_ERROR_REPLY); |
| 138 | - return; | ||
| 139 | - } | ||
| 140 | 120 | ||
| 141 | - if(reply->type == REDIS_REPLY_NIL) { | 121 | + } else if(cmd_obj->reply_obj->type == REDIS_REPLY_NIL) { |
| 142 | std::cerr << "[WARNING] " << cmd_obj->cmd << ": Nil reply." << std::endl; | 122 | std::cerr << "[WARNING] " << cmd_obj->cmd << ": Nil reply." << std::endl; |
| 143 | cmd_obj->invoke_error(REDISX_NIL_REPLY); | 123 | cmd_obj->invoke_error(REDISX_NIL_REPLY); |
| 144 | - return; | 124 | + |
| 125 | + } else { | ||
| 126 | + invoke_callback<ReplyT>(cmd_obj, cmd_obj->reply_obj); | ||
| 145 | } | 127 | } |
| 146 | 128 | ||
| 147 | - invoke_callback<ReplyT>(cmd_obj, reply); | 129 | + // Free the reply object unless told not to |
| 130 | + if(cmd_obj->free_memory) cmd_obj->free_reply_object(); | ||
| 148 | } | 131 | } |
| 149 | 132 | ||
| 150 | template<class ReplyT> | 133 | template<class ReplyT> |
| 151 | Command<ReplyT>* Redis::command( | 134 | Command<ReplyT>* Redis::command( |
| 152 | - const std::string& cmd, | ||
| 153 | - const std::function<void(const std::string&, const ReplyT&)>& callback, | ||
| 154 | - const std::function<void(const std::string&, int status)>& error_callback, | ||
| 155 | - double repeat, | ||
| 156 | - double after | 135 | + const std::string& cmd, |
| 136 | + const std::function<void(const std::string&, const ReplyT&)>& callback, | ||
| 137 | + const std::function<void(const std::string&, int status)>& error_callback, | ||
| 138 | + double repeat, | ||
| 139 | + double after, | ||
| 140 | + bool free_memory | ||
| 157 | ) { | 141 | ) { |
| 158 | std::lock_guard<std::mutex> lg(queue_guard); | 142 | std::lock_guard<std::mutex> lg(queue_guard); |
| 159 | - auto* cmd_obj = new Command<ReplyT>(c, cmd, callback, error_callback, repeat, after); | 143 | + auto* cmd_obj = new Command<ReplyT>(c, cmd, callback, error_callback, repeat, after, free_memory); |
| 160 | get_command_map<ReplyT>()[(void*)cmd_obj] = cmd_obj; | 144 | get_command_map<ReplyT>()[(void*)cmd_obj] = cmd_obj; |
| 161 | command_queue.push((void*)cmd_obj); | 145 | command_queue.push((void*)cmd_obj); |
| 162 | return cmd_obj; | 146 | return cmd_obj; |
| @@ -183,43 +167,39 @@ bool Redis::cancel(Command<ReplyT>* cmd_obj) { | @@ -183,43 +167,39 @@ bool Redis::cancel(Command<ReplyT>* cmd_obj) { | ||
| 183 | } | 167 | } |
| 184 | 168 | ||
| 185 | template<class ReplyT> | 169 | template<class ReplyT> |
| 186 | -ReplyT Redis::command_blocking(const std::string& cmd) { | ||
| 187 | - std::mutex m; | ||
| 188 | - std::condition_variable cv; | 170 | +Command<ReplyT>* Redis::command_blocking(const std::string& cmd) { |
| 171 | + | ||
| 189 | ReplyT val; | 172 | ReplyT val; |
| 190 | std::atomic_int status(REDISX_UNINIT); | 173 | std::atomic_int status(REDISX_UNINIT); |
| 191 | 174 | ||
| 192 | - // There is a memory issue here, because after the callback returns | ||
| 193 | - // all memory is cleared. | ||
| 194 | - // TODO right now just don't use char* or redisReply* for blocking | ||
| 195 | - // Later, maybe specialize a function to copy the pointer types to | ||
| 196 | - // the heap | 175 | + std::condition_variable cv; |
| 176 | + std::mutex m; | ||
| 177 | + | ||
| 178 | + std::unique_lock<std::mutex> lk(m); | ||
| 197 | 179 | ||
| 198 | - command<ReplyT>(cmd, | ||
| 199 | - [&cv, &val, &status](const std::string& cmd_str, const ReplyT& reply) { | ||
| 200 | - std::cout << "cmd: " << cmd_str << std::endl; | ||
| 201 | - std::cout << "invoking success, reply: " << reply << std::endl; | 180 | + Command<ReplyT>* cmd_obj = command<ReplyT>(cmd, |
| 181 | + [&val, &status, &m, &cv](const std::string& cmd_str, const ReplyT& reply) { | ||
| 182 | + std::unique_lock<std::mutex> lk(m); | ||
| 202 | val = reply; | 183 | val = reply; |
| 203 | - std::cout << "invoking success, reply copied: " << val << std::endl; | ||
| 204 | status = REDISX_OK; | 184 | status = REDISX_OK; |
| 185 | + lk.unlock(); | ||
| 205 | cv.notify_one(); | 186 | cv.notify_one(); |
| 206 | }, | 187 | }, |
| 207 | - [&cv, &status](const std::string& cmd_str, int error) { | 188 | + [&status, &m, &cv](const std::string& cmd_str, int error) { |
| 189 | + std::unique_lock<std::mutex> lk(m); | ||
| 208 | status = error; | 190 | status = error; |
| 191 | + lk.unlock(); | ||
| 209 | cv.notify_one(); | 192 | cv.notify_one(); |
| 210 | - } | 193 | + }, |
| 194 | + 0, 0, false // No repeats, don't free memory | ||
| 211 | ); | 195 | ); |
| 212 | 196 | ||
| 213 | - std::unique_lock<std::mutex> ul(m); | ||
| 214 | - cv.wait(ul, [&status] { return status != REDISX_UNINIT; }); | 197 | + cv.wait(lk, [&status] { return status != REDISX_UNINIT; }); |
| 215 | 198 | ||
| 216 | - std::cout << "invoking success, after wait: " << val << std::endl; | ||
| 217 | - std::cout << "response: " << status << std::endl; | ||
| 218 | - if(status == REDISX_OK) return val; | ||
| 219 | - else if(status == REDISX_NIL_REPLY) return val; | ||
| 220 | - else throw std::runtime_error( | ||
| 221 | - "[ERROR] " + cmd + ": redisx error code " + std::to_string(status.load()) | ||
| 222 | - ); | 199 | + cmd_obj->reply_val = val; |
| 200 | + cmd_obj->reply_status = status; | ||
| 201 | + | ||
| 202 | + return cmd_obj; | ||
| 223 | } | 203 | } |
| 224 | 204 | ||
| 225 | } // End namespace redis | 205 | } // End namespace redis |