Commit 59d5404b authored by Richard Oudkerk's avatar Richard Oudkerk

Issue #14753: Make multiprocessing treat negative timeouts as it did in 3.2

In Python 3.2 and earlier, Process.join() and Connection.poll()
treated negative timeouts as zero timeouts.  Earlier versions from
the 3.3 line of development treat them as infinite timeouts.

The patch reverts to the old behaviour.
parent ca5f91b8
......@@ -928,6 +928,12 @@ object -- see :ref:`multiprocessing-managers`.
.. note::
The :meth:`acquire` and :meth:`wait` methods of each of these types
treat negative timeouts as zero timeouts. This differs from
:mod:`threading` where, since version 3.2, the equivalent
:meth:`acquire` methods treat negative timeouts as infinite
timeouts.
On Mac OS X, ``sem_timedwait`` is unsupported, so calling ``acquire()`` with
a timeout will emulate that function's behavior using a sleeping loop.
......@@ -1899,6 +1905,7 @@ multiple connections at the same time.
those objects in *object_list* which are ready. If *timeout* is a
float then the call blocks for at most that many seconds. If
*timeout* is ``None`` then it will block for an unlimited period.
A negative timeout is equivalent to a zero timeout.
For both Unix and Windows, an object can appear in *object_list* if
it is
......
......@@ -23,8 +23,7 @@ import itertools
import _multiprocessing
from multiprocessing import current_process, AuthenticationError, BufferTooShort
from multiprocessing.util import (
get_temp_dir, Finalize, sub_debug, debug, _eintr_retry)
from multiprocessing.util import get_temp_dir, Finalize, sub_debug, debug
from multiprocessing.forking import ForkingPickler
try:
import _winapi
......@@ -323,8 +322,6 @@ if _winapi:
if (self._got_empty_message or
_winapi.PeekNamedPipe(self._handle)[0] != 0):
return True
if timeout < 0:
timeout = None
return bool(wait([self], timeout))
def _get_more_data(self, ov, maxsize):
......@@ -402,8 +399,6 @@ class Connection(_ConnectionBase):
return self._recv(size)
def _poll(self, timeout):
if timeout < 0.0:
timeout = None
r = wait([self._handle], timeout)
return bool(r)
......
......@@ -75,12 +75,9 @@ else:
#
if sys.platform != 'win32':
import select
exit = os._exit
duplicate = os.dup
close = os.close
_select = util._eintr_retry(select.select)
#
# We define a Popen class similar to the one from subprocess, but
......@@ -130,10 +127,10 @@ if sys.platform != 'win32':
def wait(self, timeout=None):
if self.returncode is None:
if timeout is not None:
r = _select([self.sentinel], [], [], timeout)[0]
if not r:
from .connection import wait
if not wait([self.sentinel], timeout):
return None
# This shouldn't block if select() returned successfully.
# This shouldn't block if wait() returned successfully.
return self.poll(os.WNOHANG if timeout == 0.0 else 0)
return self.returncode
......
......@@ -295,18 +295,3 @@ class ForkAwareLocal(threading.local):
register_after_fork(self, lambda obj : obj.__dict__.clear())
def __reduce__(self):
return type(self), ()
#
# Automatic retry after EINTR
#
def _eintr_retry(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
while True:
try:
return func(*args, **kwargs)
except InterruptedError:
continue
return wrapped
......@@ -83,23 +83,13 @@ HAVE_GETVALUE = not getattr(_multiprocessing,
'HAVE_BROKEN_SEM_GETVALUE', False)
WIN32 = (sys.platform == "win32")
if WIN32:
from _winapi import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
def wait_for_handle(handle, timeout):
if timeout is None or timeout < 0.0:
timeout = INFINITE
else:
timeout = int(1000 * timeout)
return WaitForSingleObject(handle, timeout) == WAIT_OBJECT_0
else:
from select import select
_select = util._eintr_retry(select)
from multiprocessing.connection import wait
def wait_for_handle(handle, timeout):
if timeout is not None and timeout < 0.0:
timeout = None
return handle in _select([handle], [], [], timeout)[0]
def wait_for_handle(handle, timeout):
if timeout is not None and timeout < 0.0:
timeout = None
return wait([handle], timeout)
try:
MAXFD = os.sysconf("SC_OPEN_MAX")
......@@ -291,9 +281,18 @@ class _TestProcess(BaseTestCase):
self.assertIn(p, self.active_children())
self.assertEqual(p.exitcode, None)
join = TimingWrapper(p.join)
self.assertEqual(join(0), None)
self.assertTimingAlmostEqual(join.elapsed, 0.0)
self.assertEqual(p.is_alive(), True)
self.assertEqual(join(-1), None)
self.assertTimingAlmostEqual(join.elapsed, 0.0)
self.assertEqual(p.is_alive(), True)
p.terminate()
join = TimingWrapper(p.join)
self.assertEqual(join(), None)
self.assertTimingAlmostEqual(join.elapsed, 0.0)
......@@ -1664,6 +1663,9 @@ class _TestConnection(BaseTestCase):
self.assertEqual(poll(), False)
self.assertTimingAlmostEqual(poll.elapsed, 0)
self.assertEqual(poll(-1), False)
self.assertTimingAlmostEqual(poll.elapsed, 0)
self.assertEqual(poll(TIMEOUT1), False)
self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1)
......@@ -2785,6 +2787,16 @@ class TestWait(unittest.TestCase):
p.terminate()
p.join()
def test_neg_timeout(self):
from multiprocessing.connection import wait
a, b = multiprocessing.Pipe()
t = time.time()
res = wait([a], timeout=-1)
t = time.time() - t
self.assertEqual(res, [])
self.assertLess(t, 1)
a.close()
b.close()
#
# Issue 14151: Test invalid family on invalid environment
......
......@@ -23,6 +23,9 @@ Core and Builtins
Library
-------
- Issue #14753: Make multiprocessing's handling of negative timeouts
the same as it was in Python 3.2.
- Issue #14583: Fix importlib bug when a package's __init__.py would first
import one of its modules then raise an error.
......
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