Skip to content

Commit 225eeec

Browse files
committed
feat(core): Support additionaal lock contenter patterns
Allows configurable multi-implementations cooperations in locks (e.g. Zookeeper python & go clients contending for the same lock).
1 parent a4efaac commit 225eeec

File tree

2 files changed

+125
-67
lines changed

2 files changed

+125
-67
lines changed

kazoo/recipe/lock.py

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
1616
"""
1717
import sys
18+
1819
try:
1920
from time import monotonic as now
2021
except ImportError:
@@ -27,13 +28,13 @@
2728
CancelledError,
2829
KazooException,
2930
LockTimeout,
30-
NoNodeError
31+
NoNodeError,
3132
)
3233
from kazoo.protocol.states import KazooState
3334
from kazoo.retry import (
3435
ForceRetryError,
3536
KazooRetry,
36-
RetryFailedError
37+
RetryFailedError,
3738
)
3839

3940

@@ -80,20 +81,33 @@ class Lock(object):
8081

8182
# Node names which exclude this contender when present at a lower
8283
# sequence number. Involved in read/write locks.
83-
_EXCLUDE_NAMES = ["__lock__", "-lock-"]
84+
_EXCLUDE_NAMES = ["__lock__"]
8485

85-
def __init__(self, client, path, identifier=None):
86+
def __init__(
87+
self, client, path, identifier=None, additional_lock_patterns=()
88+
):
8689
"""Create a Kazoo lock.
8790
8891
:param client: A :class:`~kazoo.client.KazooClient` instance.
8992
:param path: The lock path to use.
90-
:param identifier: Name to use for this lock contender. This
91-
can be useful for querying to see who the
92-
current lock contenders are.
93-
93+
:param identifier: Name to use for this lock contender. This can be
94+
useful for querying to see who the current lock
95+
contenders are.
96+
:param additional_lock_patterns: Strings that will be used to
97+
identify other znode in the path
98+
that should be considered contenders
99+
for this lock.
100+
Use this for cross-implementation
101+
compatibility.
102+
103+
.. versionadded:: 2.7.1
104+
The additional_lock_patterns option.
94105
"""
95106
self.client = client
96107
self.path = path
108+
self._exclude_names = set(
109+
self._EXCLUDE_NAMES + list(additional_lock_patterns)
110+
)
97111

98112
# some data is written to the node. this can be queried via
99113
# contenders() to see who is contending for the lock
@@ -113,8 +127,9 @@ def __init__(self, client, path, identifier=None):
113127
self.is_acquired = False
114128
self.assured_path = False
115129
self.cancelled = False
116-
self._retry = KazooRetry(max_tries=None,
117-
sleep_func=client.handler.sleep_func)
130+
self._retry = KazooRetry(
131+
max_tries=None, sleep_func=client.handler.sleep_func
132+
)
118133
self._lock = client.handler.lock_object()
119134

120135
def _ensure_path(self):
@@ -179,9 +194,12 @@ def _acquire_lock():
179194
try:
180195
gotten = False
181196
try:
182-
gotten = retry(self._inner_acquire,
183-
blocking=blocking, timeout=timeout,
184-
ephemeral=ephemeral)
197+
gotten = retry(
198+
self._inner_acquire,
199+
blocking=blocking,
200+
timeout=timeout,
201+
ephemeral=ephemeral,
202+
)
185203
except RetryFailedError:
186204
pass
187205
except KazooException:
@@ -222,8 +240,9 @@ def _inner_acquire(self, blocking, timeout, ephemeral=True):
222240
self.create_tried = True
223241

224242
if not node:
225-
node = self.client.create(self.create_path, self.data,
226-
ephemeral=ephemeral, sequence=True)
243+
node = self.client.create(
244+
self.create_path, self.data, ephemeral=ephemeral, sequence=True
245+
)
227246
# strip off path to node
228247
node = node[len(self.path) + 1:]
229248

@@ -263,14 +282,16 @@ def _inner_acquire(self, blocking, timeout, ephemeral=True):
263282
else:
264283
self.wake_event.wait(timeout)
265284
if not self.wake_event.isSet():
266-
raise LockTimeout("Failed to acquire lock on %s after "
267-
"%s seconds" % (self.path, timeout))
285+
raise LockTimeout(
286+
"Failed to acquire lock on %s after %s seconds"
287+
% (self.path, timeout)
288+
)
268289
finally:
269290
self.client.remove_listener(self._watch_session)
270291

271292
def predecessor(self, children, index):
272293
for c in reversed(children[:index]):
273-
if any(n in c for n in self._EXCLUDE_NAMES):
294+
if any(n in c for n in self._exclude_names):
274295
return c
275296
return None
276297

@@ -289,12 +310,13 @@ def _get_sorted_children(self):
289310
# (eg. in case of a lease), just sort them last ('~' sorts after all
290311
# ASCII digits).
291312
def _seq(c):
292-
for name in ["__lock__", "-lock-", "__rlock__"]:
313+
for name in self._exclude_names:
293314
idx = c.find(name)
294315
if idx != -1:
295316
return c[idx + len(name):]
296317
# Sort unknown node names eg. "lease_holder" last.
297318
return '~'
319+
298320
children.sort(key=_seq)
299321
return children
300322

@@ -391,8 +413,9 @@ class WriteLock(Lock):
391413
shared lock.
392414
393415
"""
416+
394417
_NODE_NAME = "__lock__"
395-
_EXCLUDE_NAMES = ["__lock__", "-lock-", "__rlock__"]
418+
_EXCLUDE_NAMES = ["__lock__", "__rlock__"]
396419

397420

398421
class ReadLock(Lock):
@@ -420,8 +443,9 @@ class ReadLock(Lock):
420443
shared lock.
421444
422445
"""
446+
423447
_NODE_NAME = "__rlock__"
424-
_EXCLUDE_NAMES = ["__lock__", "-lock-"]
448+
_EXCLUDE_NAMES = ["__lock__"]
425449

426450

427451
class Semaphore(object):
@@ -458,6 +482,7 @@ class Semaphore(object):
458482
The max_leases check.
459483
460484
"""
485+
461486
def __init__(self, client, path, identifier=None, max_leases=1):
462487
"""Create a Kazoo Lock
463488
@@ -509,8 +534,8 @@ def _ensure_path(self):
509534
else:
510535
if leases != self.max_leases:
511536
raise ValueError(
512-
"Inconsistent max leases: %s, expected: %s" %
513-
(leases, self.max_leases)
537+
"Inconsistent max leases: %s, expected: %s"
538+
% (leases, self.max_leases)
514539
)
515540
else:
516541
self.client.set(self.path, str(self.max_leases).encode('utf-8'))
@@ -548,7 +573,8 @@ def acquire(self, blocking=True, timeout=None):
548573

549574
try:
550575
self.is_acquired = self.client.retry(
551-
self._inner_acquire, blocking=blocking, timeout=timeout)
576+
self._inner_acquire, blocking=blocking, timeout=timeout
577+
)
552578
except KazooException:
553579
# if we did ultimately fail, attempt to clean up
554580
self._best_effort_cleanup()
@@ -590,8 +616,9 @@ def _inner_acquire(self, blocking, timeout=None):
590616
self.wake_event.wait(w.leftover())
591617
if not self.wake_event.isSet():
592618
raise LockTimeout(
593-
"Failed to acquire semaphore on %s "
594-
"after %s seconds" % (self.path, timeout))
619+
"Failed to acquire semaphore on %s"
620+
" after %s seconds" % (self.path, timeout)
621+
)
595622
else:
596623
return False
597624
finally:
@@ -612,8 +639,9 @@ def _get_lease(self, data=None):
612639
# Get a list of the current potential lock holders. If they change,
613640
# notify our wake_event object. This is used to unblock a blocking
614641
# self._inner_acquire call.
615-
children = self.client.get_children(self.path,
616-
self._watch_lease_change)
642+
children = self.client.get_children(
643+
self.path, self._watch_lease_change
644+
)
617645

618646
# If there are leases available, acquire one
619647
if len(children) < self.max_leases:

0 commit comments

Comments
 (0)