##// END OF EJS Templates
feat(locks): added no-blocking option for redis blocks
super-admin -
r5380:6a5d4eae default
parent child Browse files
Show More
@@ -1,394 +1,402 b''
1
1
2 import threading
2 import threading
3 import weakref
3 import weakref
4 from base64 import b64encode
4 from base64 import b64encode
5 from logging import getLogger
5 from logging import getLogger
6 from os import urandom
6 from os import urandom
7 from typing import Union
7 from typing import Union
8
8
9 from redis import StrictRedis
9 from redis import StrictRedis
10
10
11 __version__ = '4.0.0'
11 __version__ = '4.0.0'
12
12
13 loggers = {
13 loggers = {
14 k: getLogger("rhodecode." + ".".join((__name__, k)))
14 k: getLogger("rhodecode." + ".".join((__name__, k)))
15 for k in [
15 for k in [
16 "acquire",
16 "acquire",
17 "refresh.thread.start",
17 "refresh.thread.start",
18 "refresh.thread.stop",
18 "refresh.thread.stop",
19 "refresh.thread.exit",
19 "refresh.thread.exit",
20 "refresh.start",
20 "refresh.start",
21 "refresh.shutdown",
21 "refresh.shutdown",
22 "refresh.exit",
22 "refresh.exit",
23 "release",
23 "release",
24 ]
24 ]
25 }
25 }
26
26
27 text_type = str
27 text_type = str
28 binary_type = bytes
28 binary_type = bytes
29
29
30
30
31 # Check if the id match. If not, return an error code.
31 # Check if the id match. If not, return an error code.
32 UNLOCK_SCRIPT = b"""
32 UNLOCK_SCRIPT = b"""
33 if redis.call("get", KEYS[1]) ~= ARGV[1] then
33 if redis.call("get", KEYS[1]) ~= ARGV[1] then
34 return 1
34 return 1
35 else
35 else
36 redis.call("del", KEYS[2])
36 redis.call("del", KEYS[2])
37 redis.call("lpush", KEYS[2], 1)
37 redis.call("lpush", KEYS[2], 1)
38 redis.call("pexpire", KEYS[2], ARGV[2])
38 redis.call("pexpire", KEYS[2], ARGV[2])
39 redis.call("del", KEYS[1])
39 redis.call("del", KEYS[1])
40 return 0
40 return 0
41 end
41 end
42 """
42 """
43
43
44 # Covers both cases when key doesn't exist and doesn't equal to lock's id
44 # Covers both cases when key doesn't exist and doesn't equal to lock's id
45 EXTEND_SCRIPT = b"""
45 EXTEND_SCRIPT = b"""
46 if redis.call("get", KEYS[1]) ~= ARGV[1] then
46 if redis.call("get", KEYS[1]) ~= ARGV[1] then
47 return 1
47 return 1
48 elseif redis.call("ttl", KEYS[1]) < 0 then
48 elseif redis.call("ttl", KEYS[1]) < 0 then
49 return 2
49 return 2
50 else
50 else
51 redis.call("expire", KEYS[1], ARGV[2])
51 redis.call("expire", KEYS[1], ARGV[2])
52 return 0
52 return 0
53 end
53 end
54 """
54 """
55
55
56 RESET_SCRIPT = b"""
56 RESET_SCRIPT = b"""
57 redis.call('del', KEYS[2])
57 redis.call('del', KEYS[2])
58 redis.call('lpush', KEYS[2], 1)
58 redis.call('lpush', KEYS[2], 1)
59 redis.call('pexpire', KEYS[2], ARGV[2])
59 redis.call('pexpire', KEYS[2], ARGV[2])
60 return redis.call('del', KEYS[1])
60 return redis.call('del', KEYS[1])
61 """
61 """
62
62
63 RESET_ALL_SCRIPT = b"""
63 RESET_ALL_SCRIPT = b"""
64 local locks = redis.call('keys', 'lock:*')
64 local locks = redis.call('keys', 'lock:*')
65 local signal
65 local signal
66 for _, lock in pairs(locks) do
66 for _, lock in pairs(locks) do
67 signal = 'lock-signal:' .. string.sub(lock, 6)
67 signal = 'lock-signal:' .. string.sub(lock, 6)
68 redis.call('del', signal)
68 redis.call('del', signal)
69 redis.call('lpush', signal, 1)
69 redis.call('lpush', signal, 1)
70 redis.call('expire', signal, 1)
70 redis.call('expire', signal, 1)
71 redis.call('del', lock)
71 redis.call('del', lock)
72 end
72 end
73 return #locks
73 return #locks
74 """
74 """
75
75
76
76
77 class AlreadyAcquired(RuntimeError):
77 class AlreadyAcquired(RuntimeError):
78 pass
78 pass
79
79
80
80
81 class NotAcquired(RuntimeError):
81 class NotAcquired(RuntimeError):
82 pass
82 pass
83
83
84
84
85 class AlreadyStarted(RuntimeError):
85 class AlreadyStarted(RuntimeError):
86 pass
86 pass
87
87
88
88
89 class TimeoutNotUsable(RuntimeError):
89 class TimeoutNotUsable(RuntimeError):
90 pass
90 pass
91
91
92
92
93 class InvalidTimeout(RuntimeError):
93 class InvalidTimeout(RuntimeError):
94 pass
94 pass
95
95
96
96
97 class TimeoutTooLarge(RuntimeError):
97 class TimeoutTooLarge(RuntimeError):
98 pass
98 pass
99
99
100
100
101 class NotExpirable(RuntimeError):
101 class NotExpirable(RuntimeError):
102 pass
102 pass
103
103
104
104
105 class Lock(object):
105 class Lock(object):
106 """
106 """
107 A Lock context manager implemented via redis SETNX/BLPOP.
107 A Lock context manager implemented via redis SETNX/BLPOP.
108 """
108 """
109
109
110 unlock_script = None
110 unlock_script = None
111 extend_script = None
111 extend_script = None
112 reset_script = None
112 reset_script = None
113 reset_all_script = None
113 reset_all_script = None
114 blocking = None
114
115
115 _lock_renewal_interval: float
116 _lock_renewal_interval: float
116 _lock_renewal_thread: Union[threading.Thread, None]
117 _lock_renewal_thread: Union[threading.Thread, None]
117
118
118 def __init__(self, redis_client, name, expire=None, id=None, auto_renewal=False, strict=True, signal_expire=1000):
119 def __init__(self, redis_client, name, expire=None, id=None, auto_renewal=False, strict=True, signal_expire=1000, blocking=True):
119 """
120 """
120 :param redis_client:
121 :param redis_client:
121 An instance of :class:`~StrictRedis`.
122 An instance of :class:`~StrictRedis`.
122 :param name:
123 :param name:
123 The name (redis key) the lock should have.
124 The name (redis key) the lock should have.
124 :param expire:
125 :param expire:
125 The lock expiry time in seconds. If left at the default (None)
126 The lock expiry time in seconds. If left at the default (None)
126 the lock will not expire.
127 the lock will not expire.
127 :param id:
128 :param id:
128 The ID (redis value) the lock should have. A random value is
129 The ID (redis value) the lock should have. A random value is
129 generated when left at the default.
130 generated when left at the default.
130
131
131 Note that if you specify this then the lock is marked as "held". Acquires
132 Note that if you specify this then the lock is marked as "held". Acquires
132 won't be possible.
133 won't be possible.
133 :param auto_renewal:
134 :param auto_renewal:
134 If set to ``True``, Lock will automatically renew the lock so that it
135 If set to ``True``, Lock will automatically renew the lock so that it
135 doesn't expire for as long as the lock is held (acquire() called
136 doesn't expire for as long as the lock is held (acquire() called
136 or running in a context manager).
137 or running in a context manager).
137
138
138 Implementation note: Renewal will happen using a daemon thread with
139 Implementation note: Renewal will happen using a daemon thread with
139 an interval of ``expire*2/3``. If wishing to use a different renewal
140 an interval of ``expire*2/3``. If wishing to use a different renewal
140 time, subclass Lock, call ``super().__init__()`` then set
141 time, subclass Lock, call ``super().__init__()`` then set
141 ``self._lock_renewal_interval`` to your desired interval.
142 ``self._lock_renewal_interval`` to your desired interval.
142 :param strict:
143 :param strict:
143 If set ``True`` then the ``redis_client`` needs to be an instance of ``redis.StrictRedis``.
144 If set ``True`` then the ``redis_client`` needs to be an instance of ``redis.StrictRedis``.
144 :param signal_expire:
145 :param signal_expire:
145 Advanced option to override signal list expiration in milliseconds. Increase it for very slow clients. Default: ``1000``.
146 Advanced option to override signal list expiration in milliseconds. Increase it for very slow clients. Default: ``1000``.
147 :param blocking:
148 Boolean value specifying whether lock should be blocking or not.
149 Used in `__enter__` method.
146 """
150 """
147 if strict and not isinstance(redis_client, StrictRedis):
151 if strict and not isinstance(redis_client, StrictRedis):
148 raise ValueError("redis_client must be instance of StrictRedis. "
152 raise ValueError("redis_client must be instance of StrictRedis. "
149 "Use strict=False if you know what you're doing.")
153 "Use strict=False if you know what you're doing.")
150 if auto_renewal and expire is None:
154 if auto_renewal and expire is None:
151 raise ValueError("Expire may not be None when auto_renewal is set")
155 raise ValueError("Expire may not be None when auto_renewal is set")
152
156
153 self._client = redis_client
157 self._client = redis_client
154
158
155 if expire:
159 if expire:
156 expire = int(expire)
160 expire = int(expire)
157 if expire < 0:
161 if expire < 0:
158 raise ValueError("A negative expire is not acceptable.")
162 raise ValueError("A negative expire is not acceptable.")
159 else:
163 else:
160 expire = None
164 expire = None
161 self._expire = expire
165 self._expire = expire
162
166
163 self._signal_expire = signal_expire
167 self._signal_expire = signal_expire
164 if id is None:
168 if id is None:
165 self._id = b64encode(urandom(18)).decode('ascii')
169 self._id = b64encode(urandom(18)).decode('ascii')
166 elif isinstance(id, binary_type):
170 elif isinstance(id, binary_type):
167 try:
171 try:
168 self._id = id.decode('ascii')
172 self._id = id.decode('ascii')
169 except UnicodeDecodeError:
173 except UnicodeDecodeError:
170 self._id = b64encode(id).decode('ascii')
174 self._id = b64encode(id).decode('ascii')
171 elif isinstance(id, text_type):
175 elif isinstance(id, text_type):
172 self._id = id
176 self._id = id
173 else:
177 else:
174 raise TypeError(f"Incorrect type for `id`. Must be bytes/str not {type(id)}.")
178 raise TypeError(f"Incorrect type for `id`. Must be bytes/str not {type(id)}.")
175 self._name = 'lock:' + name
179 self._name = 'lock:' + name
176 self._signal = 'lock-signal:' + name
180 self._signal = 'lock-signal:' + name
177 self._lock_renewal_interval = (float(expire) * 2 / 3
181 self._lock_renewal_interval = (float(expire) * 2 / 3
178 if auto_renewal
182 if auto_renewal
179 else None)
183 else None)
180 self._lock_renewal_thread = None
184 self._lock_renewal_thread = None
181
185
186 self.blocking = blocking
187
182 self.register_scripts(redis_client)
188 self.register_scripts(redis_client)
183
189
184 @classmethod
190 @classmethod
185 def register_scripts(cls, redis_client):
191 def register_scripts(cls, redis_client):
186 global reset_all_script
192 global reset_all_script
187 if reset_all_script is None:
193 if reset_all_script is None:
188 cls.unlock_script = redis_client.register_script(UNLOCK_SCRIPT)
194 cls.unlock_script = redis_client.register_script(UNLOCK_SCRIPT)
189 cls.extend_script = redis_client.register_script(EXTEND_SCRIPT)
195 cls.extend_script = redis_client.register_script(EXTEND_SCRIPT)
190 cls.reset_script = redis_client.register_script(RESET_SCRIPT)
196 cls.reset_script = redis_client.register_script(RESET_SCRIPT)
191 cls.reset_all_script = redis_client.register_script(RESET_ALL_SCRIPT)
197 cls.reset_all_script = redis_client.register_script(RESET_ALL_SCRIPT)
192 reset_all_script = redis_client.register_script(RESET_ALL_SCRIPT)
198 reset_all_script = redis_client.register_script(RESET_ALL_SCRIPT)
193
199
194 @property
200 @property
195 def _held(self):
201 def _held(self):
196 return self.id == self.get_owner_id()
202 return self.id == self.get_owner_id()
197
203
198 def reset(self):
204 def reset(self):
199 """
205 """
200 Forcibly deletes the lock. Use this with care.
206 Forcibly deletes the lock. Use this with care.
201 """
207 """
202 self.reset_script(client=self._client, keys=(self._name, self._signal), args=(self.id, self._signal_expire))
208 self.reset_script(client=self._client, keys=(self._name, self._signal), args=(self.id, self._signal_expire))
203
209
204 @property
210 @property
205 def id(self):
211 def id(self):
206 return self._id
212 return self._id
207
213
208 def get_owner_id(self):
214 def get_owner_id(self):
209 owner_id = self._client.get(self._name)
215 owner_id = self._client.get(self._name)
210 if isinstance(owner_id, binary_type):
216 if isinstance(owner_id, binary_type):
211 owner_id = owner_id.decode('ascii', 'replace')
217 owner_id = owner_id.decode('ascii', 'replace')
212 return owner_id
218 return owner_id
213
219
214 def acquire(self, blocking=True, timeout=None):
220 def acquire(self, blocking=True, timeout=None):
215 """
221 """
216 :param blocking:
222 :param blocking:
217 Boolean value specifying whether lock should be blocking or not.
223 Boolean value specifying whether lock should be blocking or not.
218 :param timeout:
224 :param timeout:
219 An integer value specifying the maximum number of seconds to block.
225 An integer value specifying the maximum number of seconds to block.
220 """
226 """
221 logger = loggers["acquire"]
227 logger = loggers["acquire"]
222
228
223 logger.debug("Getting blocking: %s acquire on %r ...", blocking, self._name)
229 logger.debug("Getting blocking: %s acquire on %r ...", blocking, self._name)
224
230
225 if self._held:
231 if self._held:
226 owner_id = self.get_owner_id()
232 owner_id = self.get_owner_id()
227 raise AlreadyAcquired("Already acquired from this Lock instance. Lock id: {}".format(owner_id))
233 raise AlreadyAcquired("Already acquired from this Lock instance. Lock id: {}".format(owner_id))
228
234
229 if not blocking and timeout is not None:
235 if not blocking and timeout is not None:
230 raise TimeoutNotUsable("Timeout cannot be used if blocking=False")
236 raise TimeoutNotUsable("Timeout cannot be used if blocking=False")
231
237
232 if timeout:
238 if timeout:
233 timeout = int(timeout)
239 timeout = int(timeout)
234 if timeout < 0:
240 if timeout < 0:
235 raise InvalidTimeout(f"Timeout ({timeout}) cannot be less than or equal to 0")
241 raise InvalidTimeout(f"Timeout ({timeout}) cannot be less than or equal to 0")
236
242
237 if self._expire and not self._lock_renewal_interval and timeout > self._expire:
243 if self._expire and not self._lock_renewal_interval and timeout > self._expire:
238 raise TimeoutTooLarge(f"Timeout ({timeout}) cannot be greater than expire ({self._expire})")
244 raise TimeoutTooLarge(f"Timeout ({timeout}) cannot be greater than expire ({self._expire})")
239
245
240 busy = True
246 busy = True
241 blpop_timeout = timeout or self._expire or 0
247 blpop_timeout = timeout or self._expire or 0
242 timed_out = False
248 timed_out = False
243 while busy:
249 while busy:
244 busy = not self._client.set(self._name, self._id, nx=True, ex=self._expire)
250 busy = not self._client.set(self._name, self._id, nx=True, ex=self._expire)
245 if busy:
251 if busy:
246 if timed_out:
252 if timed_out:
247 return False
253 return False
248 elif blocking:
254 elif blocking:
249 timed_out = not self._client.blpop(self._signal, blpop_timeout) and timeout
255 timed_out = not self._client.blpop(self._signal, blpop_timeout) and timeout
250 else:
256 else:
251 logger.warning("Failed to acquire Lock(%r).", self._name)
257 logger.warning("Failed to acquire Lock(%r).", self._name)
252 return False
258 return False
253
259
254 logger.debug("Acquired Lock(%r).", self._name)
260 logger.debug("Acquired Lock(%r).", self._name)
255 if self._lock_renewal_interval is not None:
261 if self._lock_renewal_interval is not None:
256 self._start_lock_renewer()
262 self._start_lock_renewer()
257 return True
263 return True
258
264
259 def extend(self, expire=None):
265 def extend(self, expire=None):
260 """
266 """
261 Extends expiration time of the lock.
267 Extends expiration time of the lock.
262
268
263 :param expire:
269 :param expire:
264 New expiration time. If ``None`` - `expire` provided during
270 New expiration time. If ``None`` - `expire` provided during
265 lock initialization will be taken.
271 lock initialization will be taken.
266 """
272 """
267 if expire:
273 if expire:
268 expire = int(expire)
274 expire = int(expire)
269 if expire < 0:
275 if expire < 0:
270 raise ValueError("A negative expire is not acceptable.")
276 raise ValueError("A negative expire is not acceptable.")
271 elif self._expire is not None:
277 elif self._expire is not None:
272 expire = self._expire
278 expire = self._expire
273 else:
279 else:
274 raise TypeError(
280 raise TypeError(
275 "To extend a lock 'expire' must be provided as an "
281 "To extend a lock 'expire' must be provided as an "
276 "argument to extend() method or at initialization time."
282 "argument to extend() method or at initialization time."
277 )
283 )
278
284
279 error = self.extend_script(client=self._client, keys=(self._name, self._signal), args=(self._id, expire))
285 error = self.extend_script(client=self._client, keys=(self._name, self._signal), args=(self._id, expire))
280 if error == 1:
286 if error == 1:
281 raise NotAcquired(f"Lock {self._name} is not acquired or it already expired.")
287 raise NotAcquired(f"Lock {self._name} is not acquired or it already expired.")
282 elif error == 2:
288 elif error == 2:
283 raise NotExpirable(f"Lock {self._name} has no assigned expiration time")
289 raise NotExpirable(f"Lock {self._name} has no assigned expiration time")
284 elif error:
290 elif error:
285 raise RuntimeError(f"Unsupported error code {error} from EXTEND script")
291 raise RuntimeError(f"Unsupported error code {error} from EXTEND script")
286
292
287 @staticmethod
293 @staticmethod
288 def _lock_renewer(name, lockref, interval, stop):
294 def _lock_renewer(name, lockref, interval, stop):
289 """
295 """
290 Renew the lock key in redis every `interval` seconds for as long
296 Renew the lock key in redis every `interval` seconds for as long
291 as `self._lock_renewal_thread.should_exit` is False.
297 as `self._lock_renewal_thread.should_exit` is False.
292 """
298 """
293 while not stop.wait(timeout=interval):
299 while not stop.wait(timeout=interval):
294 loggers["refresh.thread.start"].debug("Refreshing Lock(%r).", name)
300 loggers["refresh.thread.start"].debug("Refreshing Lock(%r).", name)
295 lock: "Lock" = lockref()
301 lock: "Lock" = lockref()
296 if lock is None:
302 if lock is None:
297 loggers["refresh.thread.stop"].debug(
303 loggers["refresh.thread.stop"].debug(
298 "Stopping loop because Lock(%r) was garbage collected.", name
304 "Stopping loop because Lock(%r) was garbage collected.", name
299 )
305 )
300 break
306 break
301 lock.extend(expire=lock._expire)
307 lock.extend(expire=lock._expire)
302 del lock
308 del lock
303 loggers["refresh.thread.exit"].debug("Exiting renewal thread for Lock(%r).", name)
309 loggers["refresh.thread.exit"].debug("Exiting renewal thread for Lock(%r).", name)
304
310
305 def _start_lock_renewer(self):
311 def _start_lock_renewer(self):
306 """
312 """
307 Starts the lock refresher thread.
313 Starts the lock refresher thread.
308 """
314 """
309 if self._lock_renewal_thread is not None:
315 if self._lock_renewal_thread is not None:
310 raise AlreadyStarted("Lock refresh thread already started")
316 raise AlreadyStarted("Lock refresh thread already started")
311
317
312 loggers["refresh.start"].debug(
318 loggers["refresh.start"].debug(
313 "Starting renewal thread for Lock(%r). Refresh interval: %s seconds.",
319 "Starting renewal thread for Lock(%r). Refresh interval: %s seconds.",
314 self._name, self._lock_renewal_interval
320 self._name, self._lock_renewal_interval
315 )
321 )
316 self._lock_renewal_stop = threading.Event()
322 self._lock_renewal_stop = threading.Event()
317 self._lock_renewal_thread = threading.Thread(
323 self._lock_renewal_thread = threading.Thread(
318 group=None,
324 group=None,
319 target=self._lock_renewer,
325 target=self._lock_renewer,
320 kwargs={
326 kwargs={
321 'name': self._name,
327 'name': self._name,
322 'lockref': weakref.ref(self),
328 'lockref': weakref.ref(self),
323 'interval': self._lock_renewal_interval,
329 'interval': self._lock_renewal_interval,
324 'stop': self._lock_renewal_stop,
330 'stop': self._lock_renewal_stop,
325 },
331 },
326 )
332 )
327 self._lock_renewal_thread.daemon = True
333 self._lock_renewal_thread.daemon = True
328 self._lock_renewal_thread.start()
334 self._lock_renewal_thread.start()
329
335
330 def _stop_lock_renewer(self):
336 def _stop_lock_renewer(self):
331 """
337 """
332 Stop the lock renewer.
338 Stop the lock renewer.
333
339
334 This signals the renewal thread and waits for its exit.
340 This signals the renewal thread and waits for its exit.
335 """
341 """
336 if self._lock_renewal_thread is None or not self._lock_renewal_thread.is_alive():
342 if self._lock_renewal_thread is None or not self._lock_renewal_thread.is_alive():
337 return
343 return
338 loggers["refresh.shutdown"].debug("Signaling renewal thread for Lock(%r) to exit.", self._name)
344 loggers["refresh.shutdown"].debug("Signaling renewal thread for Lock(%r) to exit.", self._name)
339 self._lock_renewal_stop.set()
345 self._lock_renewal_stop.set()
340 self._lock_renewal_thread.join()
346 self._lock_renewal_thread.join()
341 self._lock_renewal_thread = None
347 self._lock_renewal_thread = None
342 loggers["refresh.exit"].debug("Renewal thread for Lock(%r) exited.", self._name)
348 loggers["refresh.exit"].debug("Renewal thread for Lock(%r) exited.", self._name)
343
349
344 def __enter__(self):
350 def __enter__(self):
345 acquired = self.acquire(blocking=True)
351 acquired = self.acquire(blocking=self.blocking)
346 if not acquired:
352 if not acquired:
347 raise AssertionError(f"Lock({self._name}) wasn't acquired, but blocking=True was used!")
353 if self.blocking:
354 raise AssertionError(f"Lock({self._name}) wasn't acquired, but blocking=True was used!")
355 raise NotAcquired(f"Lock({self._name}) is not acquired or it already expired.")
348 return self
356 return self
349
357
350 def __exit__(self, exc_type=None, exc_value=None, traceback=None):
358 def __exit__(self, exc_type=None, exc_value=None, traceback=None):
351 self.release()
359 self.release()
352
360
353 def release(self):
361 def release(self):
354 """Releases the lock, that was acquired with the same object.
362 """Releases the lock, that was acquired with the same object.
355
363
356 .. note::
364 .. note::
357
365
358 If you want to release a lock that you acquired in a different place you have two choices:
366 If you want to release a lock that you acquired in a different place you have two choices:
359
367
360 * Use ``Lock("name", id=id_from_other_place).release()``
368 * Use ``Lock("name", id=id_from_other_place).release()``
361 * Use ``Lock("name").reset()``
369 * Use ``Lock("name").reset()``
362 """
370 """
363 if self._lock_renewal_thread is not None:
371 if self._lock_renewal_thread is not None:
364 self._stop_lock_renewer()
372 self._stop_lock_renewer()
365 loggers["release"].debug("Releasing Lock(%r).", self._name)
373 loggers["release"].debug("Releasing Lock(%r).", self._name)
366 error = self.unlock_script(client=self._client, keys=(self._name, self._signal), args=(self._id, self._signal_expire))
374 error = self.unlock_script(client=self._client, keys=(self._name, self._signal), args=(self._id, self._signal_expire))
367 if error == 1:
375 if error == 1:
368 raise NotAcquired(f"Lock({self._name}) is not acquired or it already expired.")
376 raise NotAcquired(f"Lock({self._name}) is not acquired or it already expired.")
369 elif error:
377 elif error:
370 raise RuntimeError(f"Unsupported error code {error} from EXTEND script.")
378 raise RuntimeError(f"Unsupported error code {error} from EXTEND script.")
371
379
372 def locked(self):
380 def locked(self):
373 """
381 """
374 Return true if the lock is acquired.
382 Return true if the lock is acquired.
375
383
376 Checks that lock with same name already exists. This method returns true, even if
384 Checks that lock with same name already exists. This method returns true, even if
377 lock have another id.
385 lock have another id.
378 """
386 """
379 return self._client.exists(self._name) == 1
387 return self._client.exists(self._name) == 1
380
388
381
389
382 reset_all_script = None
390 reset_all_script = None
383
391
384
392
385 def reset_all(redis_client):
393 def reset_all(redis_client):
386 """
394 """
387 Forcibly deletes all locks if its remains (like a crash reason). Use this with care.
395 Forcibly deletes all locks if its remains (like a crash reason). Use this with care.
388
396
389 :param redis_client:
397 :param redis_client:
390 An instance of :class:`~StrictRedis`.
398 An instance of :class:`~StrictRedis`.
391 """
399 """
392 Lock.register_scripts(redis_client)
400 Lock.register_scripts(redis_client)
393
401
394 reset_all_script(client=redis_client) # noqa
402 reset_all_script(client=redis_client) # noqa
General Comments 0
You need to be logged in to leave comments. Login now