Commit 3e1fa843e601bb81affee2032e3561f4ddc1f70e
1 parent
2e3a71b8
Cleanup of synchronization fixes
* Use lock_guard instead of unique_lock when possible * Make a few types non-atomic since they are already guarded by a mutex * Create setVariable helpers that lock and notify
Showing
2 changed files
with
78 additions
and
104 deletions
include/redox/client.hpp
| @@ -309,12 +309,20 @@ private: | @@ -309,12 +309,20 @@ private: | ||
| 309 | // Helper function for freeAllCommands to access a specific command map | 309 | // Helper function for freeAllCommands to access a specific command map |
| 310 | template <class ReplyT> long freeAllCommandsOfType(); | 310 | template <class ReplyT> long freeAllCommandsOfType(); |
| 311 | 311 | ||
| 312 | + // Helper functions to get/set variables with synchronization. | ||
| 313 | + int getConnectState(); | ||
| 314 | + void setConnectState(int connect_state); | ||
| 315 | + int getRunning(); | ||
| 316 | + void setRunning(bool running); | ||
| 317 | + int getExited(); | ||
| 318 | + void setExited(bool exited); | ||
| 319 | + | ||
| 312 | // ------------------------------------------------ | 320 | // ------------------------------------------------ |
| 313 | // Private members | 321 | // Private members |
| 314 | // ------------------------------------------------ | 322 | // ------------------------------------------------ |
| 315 | 323 | ||
| 316 | // Manage connection state | 324 | // Manage connection state |
| 317 | - std::atomic_int connect_state_ = {NOT_YET_CONNECTED}; | 325 | + int connect_state_ = NOT_YET_CONNECTED; |
| 318 | std::mutex connect_lock_; | 326 | std::mutex connect_lock_; |
| 319 | std::condition_variable connect_waiter_; | 327 | std::condition_variable connect_waiter_; |
| 320 | 328 | ||
| @@ -340,13 +348,13 @@ private: | @@ -340,13 +348,13 @@ private: | ||
| 340 | std::thread event_loop_thread_; | 348 | std::thread event_loop_thread_; |
| 341 | 349 | ||
| 342 | // Variable and CV to know when the event loop starts running | 350 | // Variable and CV to know when the event loop starts running |
| 343 | - std::atomic_bool running_ = {false}; | 351 | + bool running_ = false; |
| 344 | std::mutex running_lock_; | 352 | std::mutex running_lock_; |
| 345 | std::condition_variable running_waiter_; | 353 | std::condition_variable running_waiter_; |
| 346 | 354 | ||
| 347 | // Variable and CV to know when the event loop stops running | 355 | // Variable and CV to know when the event loop stops running |
| 348 | std::atomic_bool to_exit_ = {false}; // Signal to exit | 356 | std::atomic_bool to_exit_ = {false}; // Signal to exit |
| 349 | - std::atomic_bool exited_ = {false}; // Event thread exited | 357 | + bool exited_ = false; // Event thread exited |
| 350 | std::mutex exit_lock_; | 358 | std::mutex exit_lock_; |
| 351 | std::condition_variable exit_waiter_; | 359 | std::condition_variable exit_waiter_; |
| 352 | 360 |
src/client.cpp
| @@ -51,18 +51,14 @@ bool Redox::connect(const string &host, const int port, | @@ -51,18 +51,14 @@ bool Redox::connect(const string &host, const int port, | ||
| 51 | // a connection error happens and the event loop exits | 51 | // a connection error happens and the event loop exits |
| 52 | { | 52 | { |
| 53 | unique_lock<mutex> ul(running_lock_); | 53 | unique_lock<mutex> ul(running_lock_); |
| 54 | - running_waiter_.wait(ul, [this] | ||
| 55 | - { | ||
| 56 | - unique_lock<mutex>(connect_lock_); | ||
| 57 | - return running_.load() || connect_state_ == CONNECT_ERROR; | 54 | + running_waiter_.wait(ul, [this] { |
| 55 | + lock_guard<mutex> lg(connect_lock_); | ||
| 56 | + return running_ || connect_state_ == CONNECT_ERROR; | ||
| 58 | }); | 57 | }); |
| 59 | } | 58 | } |
| 60 | 59 | ||
| 61 | // Return if succeeded | 60 | // Return if succeeded |
| 62 | - { | ||
| 63 | - unique_lock<mutex> ul(connect_lock_); | ||
| 64 | - return connect_state_ == CONNECTED; | ||
| 65 | - } | 61 | + return getConnectState() == CONNECTED; |
| 66 | } | 62 | } |
| 67 | 63 | ||
| 68 | bool Redox::connectUnix(const string &path, function<void(int)> connection_callback) { | 64 | bool Redox::connectUnix(const string &path, function<void(int)> connection_callback) { |
| @@ -85,18 +81,14 @@ bool Redox::connectUnix(const string &path, function<void(int)> connection_callb | @@ -85,18 +81,14 @@ bool Redox::connectUnix(const string &path, function<void(int)> connection_callb | ||
| 85 | // a connection error happens and the event loop exits | 81 | // a connection error happens and the event loop exits |
| 86 | { | 82 | { |
| 87 | unique_lock<mutex> ul(running_lock_); | 83 | unique_lock<mutex> ul(running_lock_); |
| 88 | - running_waiter_.wait(ul, [this] | ||
| 89 | - { | ||
| 90 | - unique_lock<mutex> ul(connect_lock_); | ||
| 91 | - return running_.load() || connect_state_ == CONNECT_ERROR; | 84 | + running_waiter_.wait(ul, [this] { |
| 85 | + lock_guard<mutex> lg(connect_lock_); | ||
| 86 | + return running_ || connect_state_ == CONNECT_ERROR; | ||
| 92 | }); | 87 | }); |
| 93 | } | 88 | } |
| 94 | 89 | ||
| 95 | // Return if succeeded | 90 | // Return if succeeded |
| 96 | - { | ||
| 97 | - unique_lock<mutex> ul(connect_lock_); | ||
| 98 | - return connect_state_ == CONNECTED; | ||
| 99 | - } | 91 | + return getConnectState() == CONNECTED; |
| 100 | } | 92 | } |
| 101 | 93 | ||
| 102 | void Redox::disconnect() { | 94 | void Redox::disconnect() { |
| @@ -112,13 +104,13 @@ void Redox::stop() { | @@ -112,13 +104,13 @@ void Redox::stop() { | ||
| 112 | 104 | ||
| 113 | void Redox::wait() { | 105 | void Redox::wait() { |
| 114 | unique_lock<mutex> ul(exit_lock_); | 106 | unique_lock<mutex> ul(exit_lock_); |
| 115 | - exit_waiter_.wait(ul, [this] { return exited_.load(); }); | 107 | + exit_waiter_.wait(ul, [this] { return exited_; }); |
| 116 | } | 108 | } |
| 117 | 109 | ||
| 118 | Redox::~Redox() { | 110 | Redox::~Redox() { |
| 119 | 111 | ||
| 120 | // Bring down the event loop | 112 | // Bring down the event loop |
| 121 | - if (running_ == true) { | 113 | + if (getRunning()) { |
| 122 | stop(); | 114 | stop(); |
| 123 | } | 115 | } |
| 124 | 116 | ||
| @@ -135,25 +127,18 @@ void Redox::connectedCallback(const redisAsyncContext *ctx, int status) { | @@ -135,25 +127,18 @@ void Redox::connectedCallback(const redisAsyncContext *ctx, int status) { | ||
| 135 | if (status != REDIS_OK) { | 127 | if (status != REDIS_OK) { |
| 136 | rdx->logger_.fatal() << "Could not connect to Redis: " << ctx->errstr; | 128 | rdx->logger_.fatal() << "Could not connect to Redis: " << ctx->errstr; |
| 137 | rdx->logger_.fatal() << "Status: " << status; | 129 | rdx->logger_.fatal() << "Status: " << status; |
| 138 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 139 | - rdx->connect_state_ = CONNECT_ERROR; | 130 | + rdx->setConnectState(CONNECT_ERROR); |
| 140 | 131 | ||
| 141 | } else { | 132 | } else { |
| 142 | rdx->logger_.info() << "Connected to Redis."; | 133 | rdx->logger_.info() << "Connected to Redis."; |
| 143 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 144 | // Disable hiredis automatically freeing reply objects | 134 | // Disable hiredis automatically freeing reply objects |
| 145 | ctx->c.reader->fn->freeObject = [](void *reply) {}; | 135 | ctx->c.reader->fn->freeObject = [](void *reply) {}; |
| 146 | - rdx->connect_state_ = CONNECTED; | 136 | + rdx->setConnectState(CONNECTED); |
| 147 | } | 137 | } |
| 148 | 138 | ||
| 149 | - int state; | ||
| 150 | - { | ||
| 151 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 152 | - state = rdx->connect_state_; | 139 | + if (rdx->user_connection_callback_) { |
| 140 | + rdx->user_connection_callback_(rdx->getConnectState()); | ||
| 153 | } | 141 | } |
| 154 | - rdx->connect_waiter_.notify_all(); | ||
| 155 | - if (rdx->user_connection_callback_) | ||
| 156 | - rdx->user_connection_callback_(state); | ||
| 157 | } | 142 | } |
| 158 | 143 | ||
| 159 | void Redox::disconnectedCallback(const redisAsyncContext *ctx, int status) { | 144 | void Redox::disconnectedCallback(const redisAsyncContext *ctx, int status) { |
| @@ -162,23 +147,16 @@ void Redox::disconnectedCallback(const redisAsyncContext *ctx, int status) { | @@ -162,23 +147,16 @@ void Redox::disconnectedCallback(const redisAsyncContext *ctx, int status) { | ||
| 162 | 147 | ||
| 163 | if (status != REDIS_OK) { | 148 | if (status != REDIS_OK) { |
| 164 | rdx->logger_.error() << "Disconnected from Redis on error: " << ctx->errstr; | 149 | rdx->logger_.error() << "Disconnected from Redis on error: " << ctx->errstr; |
| 165 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 166 | - rdx->connect_state_ = DISCONNECT_ERROR; | 150 | + rdx->setConnectState(DISCONNECT_ERROR); |
| 167 | } else { | 151 | } else { |
| 168 | rdx->logger_.info() << "Disconnected from Redis as planned."; | 152 | rdx->logger_.info() << "Disconnected from Redis as planned."; |
| 169 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 170 | - rdx->connect_state_ = DISCONNECTED; | 153 | + rdx->setConnectState(DISCONNECTED); |
| 171 | } | 154 | } |
| 172 | 155 | ||
| 173 | rdx->stop(); | 156 | rdx->stop(); |
| 174 | - int state; | ||
| 175 | - { | ||
| 176 | - unique_lock<mutex> lk(rdx->connect_lock_); | ||
| 177 | - state = rdx->connect_state_; | 157 | + if (rdx->user_connection_callback_) { |
| 158 | + rdx->user_connection_callback_(rdx->getConnectState()); | ||
| 178 | } | 159 | } |
| 179 | - rdx->connect_waiter_.notify_all(); | ||
| 180 | - if (rdx->user_connection_callback_) | ||
| 181 | - rdx->user_connection_callback_(state); | ||
| 182 | } | 160 | } |
| 183 | 161 | ||
| 184 | bool Redox::initEv() { | 162 | bool Redox::initEv() { |
| @@ -186,11 +164,7 @@ bool Redox::initEv() { | @@ -186,11 +164,7 @@ bool Redox::initEv() { | ||
| 186 | evloop_ = ev_loop_new(EVFLAG_AUTO); | 164 | evloop_ = ev_loop_new(EVFLAG_AUTO); |
| 187 | if (evloop_ == nullptr) { | 165 | if (evloop_ == nullptr) { |
| 188 | logger_.fatal() << "Could not create a libev event loop."; | 166 | logger_.fatal() << "Could not create a libev event loop."; |
| 189 | - { | ||
| 190 | - unique_lock<mutex> lk(connect_lock_); | ||
| 191 | - connect_state_ = INIT_ERROR; | ||
| 192 | - } | ||
| 193 | - connect_waiter_.notify_all(); | 167 | + setConnectState(INIT_ERROR); |
| 194 | return false; | 168 | return false; |
| 195 | } | 169 | } |
| 196 | ev_set_userdata(evloop_, (void *)this); // Back-reference | 170 | ev_set_userdata(evloop_, (void *)this); // Back-reference |
| @@ -203,43 +177,27 @@ bool Redox::initHiredis() { | @@ -203,43 +177,27 @@ bool Redox::initHiredis() { | ||
| 203 | 177 | ||
| 204 | if (ctx_->err) { | 178 | if (ctx_->err) { |
| 205 | logger_.fatal() << "Could not create a hiredis context: " << ctx_->errstr; | 179 | logger_.fatal() << "Could not create a hiredis context: " << ctx_->errstr; |
| 206 | - { | ||
| 207 | - unique_lock<mutex> lk(connect_lock_); | ||
| 208 | - connect_state_ = INIT_ERROR; | ||
| 209 | - } | ||
| 210 | - connect_waiter_.notify_all(); | 180 | + setConnectState(INIT_ERROR); |
| 211 | return false; | 181 | return false; |
| 212 | } | 182 | } |
| 213 | 183 | ||
| 214 | // Attach event loop to hiredis | 184 | // Attach event loop to hiredis |
| 215 | if (redisLibevAttach(evloop_, ctx_) != REDIS_OK) { | 185 | if (redisLibevAttach(evloop_, ctx_) != REDIS_OK) { |
| 216 | logger_.fatal() << "Could not attach libev event loop to hiredis."; | 186 | logger_.fatal() << "Could not attach libev event loop to hiredis."; |
| 217 | - { | ||
| 218 | - unique_lock<mutex> lk(connect_lock_); | ||
| 219 | - connect_state_ = INIT_ERROR; | ||
| 220 | - } | ||
| 221 | - connect_waiter_.notify_all(); | 187 | + setConnectState(INIT_ERROR); |
| 222 | return false; | 188 | return false; |
| 223 | } | 189 | } |
| 224 | 190 | ||
| 225 | // Set the callbacks to be invoked on server connection/disconnection | 191 | // Set the callbacks to be invoked on server connection/disconnection |
| 226 | if (redisAsyncSetConnectCallback(ctx_, Redox::connectedCallback) != REDIS_OK) { | 192 | if (redisAsyncSetConnectCallback(ctx_, Redox::connectedCallback) != REDIS_OK) { |
| 227 | logger_.fatal() << "Could not attach connect callback to hiredis."; | 193 | logger_.fatal() << "Could not attach connect callback to hiredis."; |
| 228 | - { | ||
| 229 | - unique_lock<mutex> lk(connect_lock_); | ||
| 230 | - connect_state_ = INIT_ERROR; | ||
| 231 | - } | ||
| 232 | - connect_waiter_.notify_all(); | 194 | + setConnectState(INIT_ERROR); |
| 233 | return false; | 195 | return false; |
| 234 | } | 196 | } |
| 235 | 197 | ||
| 236 | if (redisAsyncSetDisconnectCallback(ctx_, Redox::disconnectedCallback) != REDIS_OK) { | 198 | if (redisAsyncSetDisconnectCallback(ctx_, Redox::disconnectedCallback) != REDIS_OK) { |
| 237 | logger_.fatal() << "Could not attach disconnect callback to hiredis."; | 199 | logger_.fatal() << "Could not attach disconnect callback to hiredis."; |
| 238 | - { | ||
| 239 | - unique_lock<mutex> lk(connect_lock_); | ||
| 240 | - connect_state_ = INIT_ERROR; | ||
| 241 | - } | ||
| 242 | - connect_waiter_.notify_all(); | 200 | + setConnectState(INIT_ERROR); |
| 243 | return false; | 201 | return false; |
| 244 | } | 202 | } |
| 245 | 203 | ||
| @@ -258,6 +216,43 @@ void breakEventLoop(struct ev_loop *loop, ev_async *async, int revents) { | @@ -258,6 +216,43 @@ void breakEventLoop(struct ev_loop *loop, ev_async *async, int revents) { | ||
| 258 | ev_break(loop, EVBREAK_ALL); | 216 | ev_break(loop, EVBREAK_ALL); |
| 259 | } | 217 | } |
| 260 | 218 | ||
| 219 | +int Redox::getConnectState() { | ||
| 220 | + lock_guard<mutex> lk(connect_lock_); | ||
| 221 | + return connect_state_; | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +void Redox::setConnectState(int connect_state) { | ||
| 225 | + { | ||
| 226 | + lock_guard<mutex> lk(connect_lock_); | ||
| 227 | + connect_state_ = connect_state; | ||
| 228 | + } | ||
| 229 | + connect_waiter_.notify_all(); | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +int Redox::getRunning() { | ||
| 233 | + lock_guard<mutex> lg(running_lock_); | ||
| 234 | + return running_; | ||
| 235 | +} | ||
| 236 | +void Redox::setRunning(bool running) { | ||
| 237 | + { | ||
| 238 | + lock_guard<mutex> lg(running_lock_); | ||
| 239 | + running_ = running; | ||
| 240 | + } | ||
| 241 | + running_waiter_.notify_one(); | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +int Redox::getExited() { | ||
| 245 | + lock_guard<mutex> lg(exit_lock_); | ||
| 246 | + return exited_; | ||
| 247 | +} | ||
| 248 | +void Redox::setExited(bool exited) { | ||
| 249 | + { | ||
| 250 | + lock_guard<mutex> lg(exit_lock_); | ||
| 251 | + exited_ = exited; | ||
| 252 | + } | ||
| 253 | + exit_waiter_.notify_one(); | ||
| 254 | +} | ||
| 255 | + | ||
| 261 | void Redox::runEventLoop() { | 256 | void Redox::runEventLoop() { |
| 262 | 257 | ||
| 263 | // Events to connect to Redox | 258 | // Events to connect to Redox |
| @@ -272,18 +267,8 @@ void Redox::runEventLoop() { | @@ -272,18 +267,8 @@ void Redox::runEventLoop() { | ||
| 272 | // Handle connection error | 267 | // Handle connection error |
| 273 | if (connect_state_ != CONNECTED) { | 268 | if (connect_state_ != CONNECTED) { |
| 274 | logger_.warning() << "Did not connect, event loop exiting."; | 269 | logger_.warning() << "Did not connect, event loop exiting."; |
| 275 | - { | ||
| 276 | - unique_lock<mutex> ul(exit_lock_); | ||
| 277 | - exited_ = true; | ||
| 278 | - } | ||
| 279 | - | ||
| 280 | - { | ||
| 281 | - unique_lock<mutex> ul(running_lock_); | ||
| 282 | - running_ = false; | ||
| 283 | - } | ||
| 284 | - | ||
| 285 | - running_waiter_.notify_one(); | ||
| 286 | - exit_waiter_.notify_one(); | 270 | + setExited(true); |
| 271 | + setRunning(false); | ||
| 287 | return; | 272 | return; |
| 288 | } | 273 | } |
| 289 | } | 274 | } |
| @@ -302,11 +287,7 @@ void Redox::runEventLoop() { | @@ -302,11 +287,7 @@ void Redox::runEventLoop() { | ||
| 302 | ev_async_init(&watcher_free_, freeQueuedCommands); | 287 | ev_async_init(&watcher_free_, freeQueuedCommands); |
| 303 | ev_async_start(evloop_, &watcher_free_); | 288 | ev_async_start(evloop_, &watcher_free_); |
| 304 | 289 | ||
| 305 | - { | ||
| 306 | - unique_lock<mutex> ul(running_lock_); | ||
| 307 | - running_ = true; | ||
| 308 | - running_waiter_.notify_one(); | ||
| 309 | - } | 290 | + setRunning(true); |
| 310 | 291 | ||
| 311 | // Run the event loop, using NOWAIT if enabled for maximum | 292 | // Run the event loop, using NOWAIT if enabled for maximum |
| 312 | // throughput by avoiding any sleeping | 293 | // throughput by avoiding any sleeping |
| @@ -326,15 +307,10 @@ void Redox::runEventLoop() { | @@ -326,15 +307,10 @@ void Redox::runEventLoop() { | ||
| 326 | // Wait to receive server replies for clean hiredis disconnect | 307 | // Wait to receive server replies for clean hiredis disconnect |
| 327 | this_thread::sleep_for(chrono::milliseconds(10)); | 308 | this_thread::sleep_for(chrono::milliseconds(10)); |
| 328 | ev_run(evloop_, EVRUN_NOWAIT); | 309 | ev_run(evloop_, EVRUN_NOWAIT); |
| 329 | - | ||
| 330 | - int state; | ||
| 331 | - { | ||
| 332 | - unique_lock<mutex> ul(connect_lock_); | ||
| 333 | - state = connect_state_; | ||
| 334 | - } | ||
| 335 | 310 | ||
| 336 | - if (connect_state_ == CONNECTED) | 311 | + if (getConnectState() == CONNECTED) { |
| 337 | redisAsyncDisconnect(ctx_); | 312 | redisAsyncDisconnect(ctx_); |
| 313 | + } | ||
| 338 | 314 | ||
| 339 | // Run once more to disconnect | 315 | // Run once more to disconnect |
| 340 | ev_run(evloop_, EVRUN_NOWAIT); | 316 | ev_run(evloop_, EVRUN_NOWAIT); |
| @@ -346,19 +322,9 @@ void Redox::runEventLoop() { | @@ -346,19 +322,9 @@ void Redox::runEventLoop() { | ||
| 346 | << created; | 322 | << created; |
| 347 | } | 323 | } |
| 348 | 324 | ||
| 349 | - { | ||
| 350 | - unique_lock<mutex> ul(exit_lock_); | ||
| 351 | - exited_ = true; | ||
| 352 | - } | ||
| 353 | - | ||
| 354 | - { | ||
| 355 | - unique_lock<mutex> ul(running_lock_); | ||
| 356 | - running_ = false; | ||
| 357 | - } | ||
| 358 | - | ||
| 359 | // Let go for block_until_stopped method | 325 | // Let go for block_until_stopped method |
| 360 | - running_waiter_.notify_one(); | ||
| 361 | - exit_waiter_.notify_one(); | 326 | + setExited(true); |
| 327 | + setRunning(false); | ||
| 362 | 328 | ||
| 363 | logger_.info() << "Event thread exited."; | 329 | logger_.info() << "Event thread exited."; |
| 364 | } | 330 | } |