Skip to content

Commit

Permalink
fix(core): prevent perpetual re-election in case of sequence node ove…
Browse files Browse the repository at this point in the history
…rflow

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 python-zk#730
  • Loading branch information
angxiang committed Oct 9, 2023
1 parent 92bd0c2 commit 8351882
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 2 deletions.
4 changes: 2 additions & 2 deletions kazoo/recipe/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,15 +276,15 @@ def _get_predecessor(self, node):
(e.g. rlock), this and also edge cases where the lock's ephemeral node
is gone.
"""
node_sequence = node[len(self.prefix) :]
node_sequence = int(node[len(self.prefix) :])
children = self.client.get_children(self.path)
found_self = False
# Filter out the contenders using the computed regex
contender_matches = []
for child in children:
match = self._contenders_re.search(child)
if match is not None:
contender_sequence = match.group(1)
contender_sequence = int(match.group(1))
# Only consider contenders with a smaller sequence number.
# A contender with a smaller sequence number has a higher
# priority.
Expand Down
30 changes: 30 additions & 0 deletions kazoo/tests/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,10 +798,40 @@ def _thread(sem, event, timeout):


class TestSequence(unittest.TestCase):

def test_get_predecessor(self):
"""Validate selection of predecessors."""
pyLock_prefix = "514e5a831836450cb1a56c741e990fd8__lock__"
pyLock_predecessor = f"{pyLock_prefix}0000000030"
pyLock = f"{pyLock_prefix}0000000031"
pyLock_successor = f"{pyLock_prefix}0000000032"
children = ["hello", pyLock_predecessor, "world", pyLock, pyLock_successor]
client = MagicMock()
client.get_children.return_value = children
lock = Lock(client, "test")
assert lock._get_predecessor(pyLock) == pyLock_predecessor

def test_get_predecessor_with_overflowed_sequence(self):
"""Validate selection of predecessors with negative sequence.
This can occur in case of an integer overflow, if the sequence counter is
incremented beyond 2147483647.
"""
pyLock_prefix = "514e5a831836450cb1a56c741e990fd8__lock__"
pyLock_predecessor = f"{pyLock_prefix}-0000000032"
pyLock = f"{pyLock_prefix}-0000000031"
pyLock_successor = f"{pyLock_prefix}-0000000030"
children = ["hello", pyLock_predecessor, "world", pyLock, pyLock_successor]
client = MagicMock()
client.get_children.return_value = children
lock = Lock(client, "test")
assert lock._get_predecessor(pyLock) == pyLock_predecessor

def test_get_predecessor_no_predecessor(self):
"""Validate selection of predecessors."""
goLock = "_c_8eb60557ba51e0da67eefc47467d3f34-lock-0000000031"
pyLock = "514e5a831836450cb1a56c741e990fd8__lock__0000000032"
pyLock2 = "514e5a831836450cb1a56c741e990fd8__lock__0000000031"
children = ["hello", goLock, "world", pyLock]
client = MagicMock()
client.get_children.return_value = children
Expand Down

0 comments on commit 8351882

Please sign in to comment.