From 856ed96a8a0771b1f210addd664fc0f40dca0f47 Mon Sep 17 00:00:00 2001 From: sewenew Date: Wed, 30 Oct 2019 23:06:52 +0800 Subject: [PATCH] first commit for RedLock --- src/sw/redis++/recipes/redlock.cpp | 112 +++++++++++++++++++++++++++++ src/sw/redis++/recipes/redlock.h | 109 ++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 src/sw/redis++/recipes/redlock.cpp create mode 100644 src/sw/redis++/recipes/redlock.h diff --git a/src/sw/redis++/recipes/redlock.cpp b/src/sw/redis++/recipes/redlock.cpp new file mode 100644 index 00000000..4c5b3cbe --- /dev/null +++ b/src/sw/redis++/recipes/redlock.cpp @@ -0,0 +1,112 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "redlock.h" + +namespace sw { + +namespace redis { + +std::chrono::milliseconds RedMutex::try_lock(const std::string &val, const std::chrono::milliseconds &ttl) { + auto start = std::chrono::steady_clock::now(); + + if (!_redis.set(_resource, val, ttl, UpdateType::NOT_EXIST)) { + throw Error("failed to lock " + _resource); + } + + auto stop = std::chrono::steady_clock::now(); + auto elapse = stop - start; + + auto time_left = std::chrono::duration_cast(ttl - elapse); + + if (time_left < std::chrono::milliseconds(0)) { + // No time left for the lock. + try { + unlock(_resource); + } catch (const Error &err) { + throw Error("failed to lock " + _resource); + } + } + + return time_left; +} + +bool RedMutex::try_lock(const std::string &val, + const std::chrono::time_point &tp) { + try { + try_lock(val, _ttl(tp)); + } catch (const Error &err) { + return false; + } + + return true; +} + +bool RedMutex::extend_lock(const std::string &val, + const std::chrono::time_point &tp) { + auto tx = _redis.transaction(true); + auto r = tx.redis(); + try { + auto ttl = _ttl(tp); + + r.watch(_resource); + + auto id = r.get(_resource); + if (id && *id == val) { + auto reply = tx.pexpire(_resource, ttl).exec(); + if (!reply.get(0)) { + throw Error("this should not happen"); + } + } + } catch (const Error &err) { + // key has been modified or other error happens, failed to extend the lock. + return false; + } + + return true; +} + +void RedMutex::unlock(const std::string &val) { + auto tx = _redis.transaction(true); + auto r = tx.redis(); + try { + r.watch(_resource); + + auto id = r.get(_resource); + if (id && *id == val) { + auto reply = tx.del(_resource).exec(); + if (reply.get(0) != 1) { + throw Error("this should not happen"); + } + } + } catch(const WatchError &err) { + // key has been modified. Do nothing, just let it go. + } +} + +std::chrono::milliseconds RedMutex::_ttl(const SysTime &tp) const { + auto cur = std::chrono::system_clock::now(); + auto ttl = tp - cur; + if (ttl.count() < 0) { + throw Error("time already pasts"); + } + + return std::chrono::duration_cast(ttl); +} + +} + +} diff --git a/src/sw/redis++/recipes/redlock.h b/src/sw/redis++/recipes/redlock.h new file mode 100644 index 00000000..c8bed054 --- /dev/null +++ b/src/sw/redis++/recipes/redlock.h @@ -0,0 +1,109 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_RECIPES_REDLOCK_H +#define SEWENEW_REDISPLUSPLUS_RECIPES_REDLOCK_H + +#include +#include +#include +#include +//#include "../redis++.h" +#include + +namespace sw { + +namespace redis { + +class RedMutex { +public: + RedMutex(Redis &redis, const std::string &resource) : _redis(redis), _resource(resource) {} + + std::chrono::milliseconds try_lock(const std::string &val, const std::chrono::milliseconds &ttl); + + bool try_lock(const std::string &val, + const std::chrono::time_point &tp); + + bool extend_lock(const std::string &val, + const std::chrono::time_point &tp); + + void unlock(const std::string &val); + +private: + using SysTime = std::chrono::time_point; + + std::chrono::milliseconds _ttl(const SysTime &tp) const; + + Redis &_redis; + + std::string _resource; +}; + +template +class RedLock { +public: + RedLock(Mutex &mut, std::defer_lock_t) : _mut(mut), _lock_val(_lock_id()) {} + + ~RedLock() { + if (_owned) { + unlock(); + } + } + + std::chrono::milliseconds try_lock(const std::chrono::milliseconds &ttl) { + return _mut.try_lock(_lock_val, ttl); + } + + void unlock() { + _mut.unlock(_lock_val); + } + +private: + std::string _lock_id() { + std::random_device dev; + std::mt19937 random_gen(dev()); + int range = 10 + 26 + 26 - 1; + std::uniform_int_distribution<> dist(0, range); + std::string id; + id.reserve(20); + for (int i = 0; i != 20; ++i) { + auto idx = dist(random_gen); + if (idx < 10) { + id.push_back('0' + idx); + } else if (idx < 10 + 26) { + id.push_back('a' + idx - 10); + } else if (idx < 10 + 26 + 26) { + id.push_back('A' + idx - 10 - 26); + } else { + assert(false); + } + } + + return id; + } + + Mutex &_mut; + + bool _owned = false; + + std::string _lock_val; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_RECIPES_REDLOCK_H