Commit b5a218a7 authored by Jason Madden's avatar Jason Madden

Python 2: socket.sendall on a non-blocking socket no longer immediately fails with a timeout

Instead, we only start checking the timer after the first attempted
send. This is what the C implementation in CPython does.

Fixes benoitc/gunicorn#1282.
parent da6f214b
......@@ -74,6 +74,8 @@ Stdlib Compatibility
would tend to report both read and write events.
- Python 2: ``reload(site)`` no longer fails with a ``TypeError`` if
gevent has been imported. Reported in :issue:`805` by Jake Hilton.
- Python 2: ``sendall`` on a non-blocking socket could spuriously fail
with a timeout.
Other Changes
-------------
......
......@@ -348,17 +348,21 @@ class socket(object):
"""
data_sent = 0
len_data_memory = len(data_memory)
started_timer = 0
while data_sent < len_data_memory:
chunk = data_memory[data_sent:]
if timeleft is None:
data_sent += self.send(chunk, flags)
elif timeleft <= 0:
elif started_timer and timeleft <= 0:
# Check before sending to guarantee a check
# happens even if each chunk successfully sends its data
# (especially important for SSL sockets since they have large
# buffers)
# buffers). But only do this if we've actually tried to
# send something once to avoid spurious timeouts on non-blocking
# sockets.
raise timeout('timed out')
else:
started_timer = 1
data_sent += self.send(chunk, flags, timeout=timeleft)
timeleft = end - time.time()
......
......@@ -58,17 +58,21 @@ class TestTCP(greentest.TestCase):
pass
del self.listener
def create_connection(self, host='127.0.0.1', port=None, timeout=None):
def create_connection(self, host='127.0.0.1', port=None, timeout=None,
blocking=None):
sock = socket.socket()
sock.connect((host, port or self.port))
if timeout is not None:
sock.settimeout(timeout)
if blocking is not None:
sock.setblocking(blocking)
return self._close_on_teardown(sock)
def _test_sendall(self, data, match_data=None, client_method='sendall',
**client_args):
read_data = []
server_exc_info = []
def accept_and_read():
try:
......@@ -78,8 +82,7 @@ class TestTCP(greentest.TestCase):
r.close()
conn.close()
except:
traceback.print_exc()
os._exit(1)
server_exc_info.append(sys.exc_info())
server = Thread(target=accept_and_read)
client = self.create_connection(**client_args)
......@@ -95,6 +98,9 @@ class TestTCP(greentest.TestCase):
match_data = self.long_data
self.assertEqual(read_data[0], match_data)
if server_exc_info:
six.reraise(*server_exc_info[0])
def test_sendall_str(self):
self._test_sendall(self.long_data)
......@@ -115,6 +121,14 @@ class TestTCP(greentest.TestCase):
data = b''
self._test_sendall(data, data, timeout=10)
def test_sendall_nonblocking(self):
# https://github.com/benoitc/gunicorn/issues/1282
# Even if the socket is non-blocking, we make at least
# one attempt to send data. Under Py2 before this fix, we
# would incorrectly immediately raise a timeout error
data = b'hi\n'
self._test_sendall(data, data, blocking=False)
def test_empty_send(self):
# Issue 719
data = b''
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment