1 /*
2  * Hunt - A redis client library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.redis.RedisLock;
13 
14 import std.array;
15 import std.string;
16 import std.random;
17 import std.conv;
18 import std.stdio;
19 import std.uuid;
20 
21 import core.time;
22 import core.thread;
23 import core.stdc.stdlib;
24 
25 import hunt.logging.ConsoleLogger;
26 import hunt.redis.Redis;
27 import hunt.redis.params.SetParams;
28 
29 
30 struct LockedObject {
31     string key;
32     string uniqueId;
33     size_t validTime;
34 }
35 
36 /**
37     https://redis.io/topics/distlock
38     https://github.com/abelaska/jedis-lock
39 */
40 class RedisLock {
41 
42     private Redis[] _redisClients;
43     private LockedObject _lock;
44     private string _lockKey;
45 
46 
47     this(Redis client, string lockKey, int delaytime = 10, float clockFactor = 0.01f) {
48         this([client], lockKey, delaytime, clockFactor);
49     }
50 
51     this(Redis[] client, string lockKey, int delaytime = 10, float clockFactor = 0.01f) {
52         _redisClients = client;
53         _lockKey = lockKey;
54 
55         _quornum = _redisClients.length / 2 + 1;
56         _delaytime = delaytime;
57         _clockFactor = clockFactor;        
58     }
59 
60     bool lock(uint timeout = uint.max, uint ttl = 60000) {
61         string key = _lockKey;
62         string val = to!string(randomUUID());
63         auto end_tick = nsecsToTicks(cast(long) timeout * 1000 * 1000) + MonoTime.currTime.ticks();
64         synchronized (this) {
65             _lock.key = key;
66             _lock.uniqueId = val;
67 
68             do {
69                 size_t n = 0;
70                 auto t1 = MonoTime.currTime.ticks();
71                 foreach (c; _redisClients) {
72                     if (lockInstance(c, key, val, ttl))
73                         ++n;
74                 }
75 
76                 auto t2 = MonoTime.currTime.ticks();
77                 auto clockdrat = cast(size_t)(_clockFactor * ttl) + 2;
78                 auto validtime = ttl - ticksToNSecs(t2 - t1) / 1000 - clockdrat;
79 
80                 version(HUNT_REDIS_DEBUG) {
81                     tracef("validtime=%d, n=%d, _quornum=%d", validtime, n, _quornum);
82                 }
83 
84 
85                 if (validtime > 0 && n >= _quornum) {
86                     _lock.validTime = cast(size_t)validtime;
87                     return true;
88                 } else {
89                     unlock();
90                 }
91                 size_t delay = rand() % _delaytime + _delaytime / 2;
92                 Thread.sleep(dur!"msecs"(delay));
93             } while (MonoTime.currTime.ticks() < end_tick);
94 
95             return false;
96         }
97 
98     }
99 
100     void unlock() {
101         synchronized (this) {
102             foreach (c; _redisClients) {
103                 unlockInstance(c, _lock.key, _lock.uniqueId);
104             }
105         }
106     }
107 
108     private static bool lockInstance(Redis redis, string key, string value, long ttl) {
109         try {
110             SetParams para = new SetParams();
111             string r = redis.set(_prefix ~ key, value, para.nx().px(ttl));
112             return r == "OK";
113         } catch (Throwable e) {
114             warning(e);
115             return false;
116         }
117     }
118 
119     private static void unlockInstance(Redis redis, string key, string value) {
120         try {
121             Object r = redis.eval(`if redis.call('get', KEYS[1]) == ARGV[1] 
122                             then return redis.call('del', KEYS[1])
123                         else 
124                             return 0 
125                         end`,
126                     [_prefix ~ key], [value]);
127         } catch (Throwable e) {
128             warning(e);
129         }
130 
131     }
132 
133 
134     immutable size_t _quornum;
135     immutable size_t _delaytime = 10;
136     immutable float _clockFactor = 0.01;
137 
138     static immutable string _prefix = "hunt_redlock_";
139 }
140