Skip to content

Commit 8c5ce11

Browse files
cdonatiStephenSorriaux
authored andcommitted
fix(core): ensure timeout argument is positive (#534)
Previously, a gap between calls to `time.time()` could lead to a situation where the current time was less than `end` during the `while` condition, but it was greater than `end` when assigning a value to `timeout_at`. Add tests to ensure a socket.error is raised instead of passing a nonpositive value as a timeout to socket.create_connection.
1 parent 7935a3a commit 8c5ce11

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

kazoo/handlers/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,13 @@ def create_tcp_connection(module, address, timeout=None,
198198
end = time.time() + timeout
199199
sock = None
200200

201-
while end is None or time.time() < end:
201+
while True:
202202
timeout_at = end if end is None else end - time.time()
203+
# The condition is not '< 0' here because socket.settimeout treats 0 as
204+
# a special case to put the socket in non-blocking mode.
205+
if timeout_at is not None and timeout_at <= 0:
206+
break
207+
203208
if use_ssl:
204209
# Disallow use of SSLv2 and V3 (meaning we require TLSv1.0+)
205210
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)

kazoo/tests/test_utils.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import unittest
2+
3+
from mock import patch
4+
from nose import SkipTest
5+
6+
try:
7+
from kazoo.handlers.eventlet import green_socket as socket
8+
EVENTLET_HANDLER_AVAILABLE = True
9+
except ImportError:
10+
EVENTLET_HANDLER_AVAILABLE = False
11+
12+
13+
class TestCreateTCPConnection(unittest.TestCase):
14+
def test_timeout_arg(self):
15+
from kazoo.handlers import utils
16+
from kazoo.handlers.utils import create_tcp_connection, socket, time
17+
18+
with patch.object(socket, 'create_connection') as create_connection:
19+
with patch.object(utils, '_set_default_tcpsock_options'):
20+
# Ensure a gap between calls to time.time() does not result in
21+
# create_connection being called with a negative timeout
22+
# argument.
23+
with patch.object(time, 'time', side_effect=range(10)):
24+
create_tcp_connection(socket, ('127.0.0.1', 2181),
25+
timeout=1.5)
26+
27+
for call_args in create_connection.call_args_list:
28+
timeout = call_args[0][1]
29+
assert timeout >= 0, 'socket timeout must be nonnegative'
30+
31+
def test_timeout_arg_eventlet(self):
32+
if not EVENTLET_HANDLER_AVAILABLE:
33+
raise SkipTest('eventlet handler not available.')
34+
35+
from kazoo.handlers import utils
36+
from kazoo.handlers.utils import create_tcp_connection, time
37+
38+
with patch.object(socket, 'create_connection') as create_connection:
39+
with patch.object(utils, '_set_default_tcpsock_options'):
40+
# Ensure a gap between calls to time.time() does not result in
41+
# create_connection being called with a negative timeout
42+
# argument.
43+
with patch.object(time, 'time', side_effect=range(10)):
44+
create_tcp_connection(socket, ('127.0.0.1', 2181),
45+
timeout=1.5)
46+
47+
for call_args in create_connection.call_args_list:
48+
timeout = call_args[0][1]
49+
assert timeout >= 0, 'socket timeout must be nonnegative'
50+
51+
def test_slow_connect(self):
52+
# Currently, create_tcp_connection will raise a socket timeout if it
53+
# takes longer than the specified "timeout" to create a connection.
54+
# In the future, "timeout" might affect only the created socket and not
55+
# the time it takes to create it.
56+
from kazoo.handlers.utils import create_tcp_connection, socket, time
57+
58+
# Simulate a second passing between calls to check the current time.
59+
with patch.object(time, 'time', side_effect=range(10)):
60+
with self.assertRaises(socket.error):
61+
create_tcp_connection(socket, ('127.0.0.1', 2181), timeout=0.5)
62+
63+
def test_negative_timeout(self):
64+
from kazoo.handlers.utils import create_tcp_connection, socket
65+
with self.assertRaises(socket.error):
66+
create_tcp_connection(socket, ('127.0.0.1', 2181), timeout=-1)
67+
68+
def test_zero_timeout(self):
69+
# Rather than pass '0' through as a timeout to
70+
# socket.create_connection, create_tcp_connection should raise
71+
# socket.error. This is because the socket library treats '0' as an
72+
# indicator to create a non-blocking socket.
73+
from kazoo.handlers.utils import create_tcp_connection, socket, time
74+
75+
# Simulate no time passing between calls to check the current time.
76+
with patch.object(time, 'time', return_value=time.time()):
77+
with self.assertRaises(socket.error):
78+
create_tcp_connection(socket, ('127.0.0.1', 2181), timeout=0)

0 commit comments

Comments
 (0)