Handle EINTR from poll with zero timeout
Closes
MODIFY
CONTRIBUTORS.txt
CONTRIBUTORS.txt
@@ -259,5 +259,8 @@ In chronological order:
* Improve contribution guide
* Add ``HTTPResponse.geturl`` method to provide ``urllib2.urlopen().geturl()`` behavior
+* Bruce Merry <http://www.brucemerry.org.za>
+ * Fix leaking exceptions when system calls are interrupted with zero timeout
+
* [Your name or handle] <[email or website]>
* [Brief summary of your changes]

MODIFY
test/test_wait.py
test/test_wait.py
@@ -137,6 +137,40 @@ def test_eintr(wfs, spair):
assert interrupt_count[0] > 0
+@pytest.mark.skipif(
+ not hasattr(signal, "setitimer"),
+ reason="need setitimer() support"
+)
+@pytest.mark.parametrize("wfs", variants)
+def test_eintr_zero_timeout(wfs, spair):
+ a, b = spair
+ interrupt_count = [0]
+
+ def handler(sig, frame):
+ assert sig == signal.SIGALRM
+ interrupt_count[0] += 1
+
+ old_handler = signal.signal(signal.SIGALRM, handler)
+ try:
+ assert not wfs(a, read=True, timeout=0)
+ try:
+ # Start delivering SIGALRM 1000 times per second,
+ # to trigger race conditions such as
+ # https://github.com/urllib3/urllib3/issues/1396.
+ signal.setitimer(signal.ITIMER_REAL, 0.001, 0.001)
+ # Hammer the system call for a while to trigger the
+ # race.
+ for i in range(100000):
+ wfs(a, read=True, timeout=0)
+ finally:
+ # Stop delivering SIGALRM
+ signal.setitimer(signal.ITIMER_REAL, 0)
+ finally:
+ signal.signal(signal.SIGALRM, old_handler)
+
+ assert interrupt_count[0] > 0
+
+
@pytest.mark.skipif(
not hasattr(signal, "setitimer"),
reason="need setitimer() support"

MODIFY
urllib3/util/wait.py
urllib3/util/wait.py
@@ -43,9 +43,6 @@ if sys.version_info >= (3, 5):
else:
# Old and broken Pythons.
def _retry_on_intr(fn, timeout):
- if timeout is not None and timeout <= 0:
- return fn(timeout)
-
if timeout is None:
deadline = float("inf")
else:
@@ -117,7 +114,7 @@ def _have_working_poll():
# from libraries like eventlet/greenlet.
try:
poll_obj = select.poll()
- poll_obj.poll(0)
+ _retry_on_intr(poll_obj.poll, 0)
except (AttributeError, OSError):
return False
else:

Handle EINTR from poll with zero timeout (#1399)

Closes #1396