Skip to content

Commit 6a6b193

Browse files
committed
fix(recipe): prevent perpetual re-election if sequence node overflows
Once sequence node is incremented beyond 2147483647, the sequence overflows into the negative space. When getting predecessors, cast the node sequence from String to int before comparing. Closes #730
1 parent 92bd0c2 commit 6a6b193

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

kazoo/recipe/lock.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,15 @@ def _get_predecessor(self, node):
276276
(e.g. rlock), this and also edge cases where the lock's ephemeral node
277277
is gone.
278278
"""
279-
node_sequence = node[len(self.prefix) :]
279+
node_sequence = int(node[len(self.prefix) :])
280280
children = self.client.get_children(self.path)
281281
found_self = False
282282
# Filter out the contenders using the computed regex
283283
contender_matches = []
284284
for child in children:
285285
match = self._contenders_re.search(child)
286286
if match is not None:
287-
contender_sequence = match.group(1)
287+
contender_sequence = int(match.group(1))
288288
# Only consider contenders with a smaller sequence number.
289289
# A contender with a smaller sequence number has a higher
290290
# priority.

kazoo/tests/test_lock.py

+40
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,46 @@ def _thread(sem, event, timeout):
799799

800800
class TestSequence(unittest.TestCase):
801801
def test_get_predecessor(self):
802+
"""Validate selection of predecessors."""
803+
pyLock_prefix = "514e5a831836450cb1a56c741e990fd8__lock__"
804+
pyLock_predecessor = f"{pyLock_prefix}0000000030"
805+
pyLock = f"{pyLock_prefix}0000000031"
806+
pyLock_successor = f"{pyLock_prefix}0000000032"
807+
children = [
808+
"hello",
809+
pyLock_predecessor,
810+
"world",
811+
pyLock,
812+
pyLock_successor,
813+
]
814+
client = MagicMock()
815+
client.get_children.return_value = children
816+
lock = Lock(client, "test")
817+
assert lock._get_predecessor(pyLock) == pyLock_predecessor
818+
819+
def test_get_predecessor_with_overflowed_sequence(self):
820+
"""Validate selection of predecessors with negative sequence.
821+
822+
This can occur in case of an integer overflow, if the sequence counter is
823+
incremented beyond 2147483647.
824+
"""
825+
pyLock_prefix = "514e5a831836450cb1a56c741e990fd8__lock__"
826+
pyLock_predecessor = f"{pyLock_prefix}-0000000032"
827+
pyLock = f"{pyLock_prefix}-0000000031"
828+
pyLock_successor = f"{pyLock_prefix}-0000000030"
829+
children = [
830+
"hello",
831+
pyLock_predecessor,
832+
"world",
833+
pyLock,
834+
pyLock_successor,
835+
]
836+
client = MagicMock()
837+
client.get_children.return_value = children
838+
lock = Lock(client, "test")
839+
assert lock._get_predecessor(pyLock) == pyLock_predecessor
840+
841+
def test_get_predecessor_no_predecessor(self):
802842
"""Validate selection of predecessors."""
803843
goLock = "_c_8eb60557ba51e0da67eefc47467d3f34-lock-0000000031"
804844
pyLock = "514e5a831836450cb1a56c741e990fd8__lock__0000000032"

0 commit comments

Comments
 (0)