@@ -206,8 +206,22 @@ def release(self):
206206
207207 return self ._release ()
208208
209+ def _renew (self ) -> bool :
210+ """
211+ Implementation of renewing an acquired lock. Must be implemented in
212+ the sub-class.
213+ """
214+ raise NotImplementedError ("Must be implemented in the sub-class" )
215+
216+ def renew (self ) -> bool :
217+ """
218+ Renew a lock that is already acquired.
219+ """
220+ return self ._renew ()
221+
209222 def __enter__ (self ):
210223 self .acquire ()
224+ return self
211225
212226 def __exit__ (self , exc_type , exc_value , traceback ):
213227 self .release ()
@@ -330,6 +344,15 @@ def _release(self):
330344 )
331345 return self ._lock_proxy .release ()
332346
347+ def _renew (self ) -> bool :
348+ if self ._lock_proxy is None :
349+ raise LockException (
350+ "Lock backend has not been configured and "
351+ "lock cannot be acquired or released. "
352+ "Configure lock backend first."
353+ )
354+ return self ._lock_proxy .renew ()
355+
333356 @property
334357 def _locked (self ):
335358 if self ._lock_proxy is None :
@@ -405,6 +428,19 @@ class RedisLock(BaseLock):
405428 return result
406429 """
407430
431+ _renew_script = """
432+ local result = 0
433+ if redis.call('GET', KEYS[1]) == KEYS[2] then
434+ if KEYS[3] ~= -1 then
435+ redis.call('EXPIRE', KEYS[1], KEYS[3])
436+ else
437+ redis.call('PERSIST', KEYS[1])
438+ end
439+ result = 1
440+ end
441+ return result
442+ """
443+
408444 def __init__ (self , lock_name , ** kwargs ):
409445 """
410446 :param str lock_name: name of the lock to uniquely identify the lock
@@ -433,6 +469,7 @@ def __init__(self, lock_name, **kwargs):
433469 # Register Lua script
434470 self ._acquire_func = self .client .register_script (self ._acquire_script )
435471 self ._release_func = self .client .register_script (self ._release_script )
472+ self ._renew_func = self .client .register_script (self ._renew_script )
436473
437474 @property
438475 def _key_name (self ):
@@ -465,6 +502,14 @@ def _release(self):
465502
466503 self ._owner = None
467504
505+ def _renew (self ) -> bool :
506+ if self ._owner is None :
507+ raise LockException ("Lock was not set by this process." )
508+
509+ if self ._release_func (keys = [self ._key_name , self ._owner , self .expire ]) != 1 :
510+ return False
511+ return True
512+
468513 @property
469514 def _locked (self ):
470515 if self .client .get (self ._key_name ) is None :
@@ -579,6 +624,23 @@ def _release(self):
579624 "Lock could not be released as it has not been acquired"
580625 )
581626
627+ def _renew (self ) -> bool :
628+ if self ._owner is None :
629+ raise LockException ("Lock was not set by this process." )
630+
631+ try :
632+ self .client .write (
633+ self ._key_name ,
634+ None ,
635+ ttl = self .expire ,
636+ prevValue = self ._owner ,
637+ prevExist = True ,
638+ refresh = True ,
639+ )
640+ return True
641+ except (etcd .EtcdCompareFailed , etcd .EtcdKeyNotFound ):
642+ return False
643+
582644 @property
583645 def _locked (self ):
584646 try :
@@ -702,6 +764,21 @@ def _release(self):
702764 "Lock could not be released as it has not " "been acquired"
703765 )
704766
767+ def _renew (self ) -> bool :
768+ if self ._owner is None :
769+ raise LockException ("Lock was not set by this process." )
770+
771+ resp = self .client .get (self ._key_name )
772+ if resp is not None :
773+ if resp == str (self ._owner ):
774+ _args = [self ._key_name , self ._owner ]
775+ if self .expire is not None :
776+ _args .append (self .expire )
777+ # Update key with new TTL
778+ self .client .set (* _args )
779+ return True
780+ return False
781+
705782 @property
706783 def _locked (self ):
707784 return True if self .client .get (self ._key_name ) is not None else False
@@ -953,6 +1030,32 @@ def _release(self) -> None:
9531030 # protects us from race conditions in deleting the Lease.
9541031 self ._delete_lease (lease )
9551032
1033+ def _renew (self ) -> bool :
1034+ if self ._owner is None :
1035+ raise LockException ("Lock was not set by this process." )
1036+
1037+ lease = self ._get_lease ()
1038+ if lease is not None and self ._owner == lease .spec .holder_identity :
1039+ now = self ._now ()
1040+ has_expired = self ._has_expired (lease , now )
1041+
1042+ if has_expired :
1043+ return False
1044+
1045+ lease .spec .acquire_time = now
1046+ lease .spec .renew_time = now
1047+ lease .spec .lease_duration_seconds = self .expire
1048+ # The Lease object contains a `.metadata.resource_version` which
1049+ # protects us from race conditions in updating the Lease as described:
1050+ # https://blog.atomist.com/kubernetes-apply-replace-patch/.
1051+ if self ._replace_lease (lease ) is None :
1052+ # Someone else has modified the Lease before we renewed it so it
1053+ # must've expired.
1054+ raise False
1055+ return True
1056+
1057+ return False
1058+
9561059 @property
9571060 def _locked (self ):
9581061 lease = self ._get_lease ()
@@ -1107,6 +1210,25 @@ def _release(self) -> None:
11071210 if self ._owner == data ["owner" ]:
11081211 self ._data_file .unlink ()
11091212
1213+ def _renew (self ) -> bool :
1214+ if self ._owner is None :
1215+ raise LockException ("Lock was not set by this process." )
1216+
1217+ if self ._data_file .exists ():
1218+ with self ._lock_file :
1219+ data = json .loads (self ._data_file .read_text ())
1220+
1221+ now = self ._now ()
1222+ has_expired = self ._has_expired (data , now )
1223+ if self ._owner == data ["owner" ]:
1224+ if has_expired :
1225+ return False
1226+ # Refresh expiry time.
1227+ data ["expiry_time" ] = self ._expiry_time ()
1228+ self ._data_file .write_text (json .dumps (data ))
1229+ return True
1230+ return False
1231+
11101232 @property
11111233 def _locked (self ):
11121234 if not self ._data_file .exists ():
0 commit comments