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.
This commit is contained in:
parent
ca5f91b888
commit
59d5404bc7
@ -928,6 +928,12 @@ object -- see :ref:`multiprocessing-managers`.
|
|||||||
|
|
||||||
.. note::
|
.. 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
|
On Mac OS X, ``sem_timedwait`` is unsupported, so calling ``acquire()`` with
|
||||||
a timeout will emulate that function's behavior using a sleeping loop.
|
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
|
those objects in *object_list* which are ready. If *timeout* is a
|
||||||
float then the call blocks for at most that many seconds. If
|
float then the call blocks for at most that many seconds. If
|
||||||
*timeout* is ``None`` then it will block for an unlimited period.
|
*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
|
For both Unix and Windows, an object can appear in *object_list* if
|
||||||
it is
|
it is
|
||||||
|
@ -23,8 +23,7 @@ import itertools
|
|||||||
|
|
||||||
import _multiprocessing
|
import _multiprocessing
|
||||||
from multiprocessing import current_process, AuthenticationError, BufferTooShort
|
from multiprocessing import current_process, AuthenticationError, BufferTooShort
|
||||||
from multiprocessing.util import (
|
from multiprocessing.util import get_temp_dir, Finalize, sub_debug, debug
|
||||||
get_temp_dir, Finalize, sub_debug, debug, _eintr_retry)
|
|
||||||
from multiprocessing.forking import ForkingPickler
|
from multiprocessing.forking import ForkingPickler
|
||||||
try:
|
try:
|
||||||
import _winapi
|
import _winapi
|
||||||
@ -323,8 +322,6 @@ if _winapi:
|
|||||||
if (self._got_empty_message or
|
if (self._got_empty_message or
|
||||||
_winapi.PeekNamedPipe(self._handle)[0] != 0):
|
_winapi.PeekNamedPipe(self._handle)[0] != 0):
|
||||||
return True
|
return True
|
||||||
if timeout < 0:
|
|
||||||
timeout = None
|
|
||||||
return bool(wait([self], timeout))
|
return bool(wait([self], timeout))
|
||||||
|
|
||||||
def _get_more_data(self, ov, maxsize):
|
def _get_more_data(self, ov, maxsize):
|
||||||
@ -402,8 +399,6 @@ class Connection(_ConnectionBase):
|
|||||||
return self._recv(size)
|
return self._recv(size)
|
||||||
|
|
||||||
def _poll(self, timeout):
|
def _poll(self, timeout):
|
||||||
if timeout < 0.0:
|
|
||||||
timeout = None
|
|
||||||
r = wait([self._handle], timeout)
|
r = wait([self._handle], timeout)
|
||||||
return bool(r)
|
return bool(r)
|
||||||
|
|
||||||
|
@ -75,12 +75,9 @@ else:
|
|||||||
#
|
#
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
import select
|
|
||||||
|
|
||||||
exit = os._exit
|
exit = os._exit
|
||||||
duplicate = os.dup
|
duplicate = os.dup
|
||||||
close = os.close
|
close = os.close
|
||||||
_select = util._eintr_retry(select.select)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# We define a Popen class similar to the one from subprocess, but
|
# 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):
|
def wait(self, timeout=None):
|
||||||
if self.returncode is None:
|
if self.returncode is None:
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
r = _select([self.sentinel], [], [], timeout)[0]
|
from .connection import wait
|
||||||
if not r:
|
if not wait([self.sentinel], timeout):
|
||||||
return None
|
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.poll(os.WNOHANG if timeout == 0.0 else 0)
|
||||||
return self.returncode
|
return self.returncode
|
||||||
|
|
||||||
|
@ -295,18 +295,3 @@ class ForkAwareLocal(threading.local):
|
|||||||
register_after_fork(self, lambda obj : obj.__dict__.clear())
|
register_after_fork(self, lambda obj : obj.__dict__.clear())
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return type(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)
|
'HAVE_BROKEN_SEM_GETVALUE', False)
|
||||||
|
|
||||||
WIN32 = (sys.platform == "win32")
|
WIN32 = (sys.platform == "win32")
|
||||||
if WIN32:
|
|
||||||
from _winapi import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
|
|
||||||
|
|
||||||
def wait_for_handle(handle, timeout):
|
from multiprocessing.connection import wait
|
||||||
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)
|
|
||||||
|
|
||||||
def wait_for_handle(handle, timeout):
|
def wait_for_handle(handle, timeout):
|
||||||
if timeout is not None and timeout < 0.0:
|
if timeout is not None and timeout < 0.0:
|
||||||
timeout = None
|
timeout = None
|
||||||
return handle in _select([handle], [], [], timeout)[0]
|
return wait([handle], timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
MAXFD = os.sysconf("SC_OPEN_MAX")
|
MAXFD = os.sysconf("SC_OPEN_MAX")
|
||||||
@ -291,9 +281,18 @@ class _TestProcess(BaseTestCase):
|
|||||||
self.assertIn(p, self.active_children())
|
self.assertIn(p, self.active_children())
|
||||||
self.assertEqual(p.exitcode, None)
|
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()
|
p.terminate()
|
||||||
|
|
||||||
join = TimingWrapper(p.join)
|
|
||||||
self.assertEqual(join(), None)
|
self.assertEqual(join(), None)
|
||||||
self.assertTimingAlmostEqual(join.elapsed, 0.0)
|
self.assertTimingAlmostEqual(join.elapsed, 0.0)
|
||||||
|
|
||||||
@ -1664,6 +1663,9 @@ class _TestConnection(BaseTestCase):
|
|||||||
self.assertEqual(poll(), False)
|
self.assertEqual(poll(), False)
|
||||||
self.assertTimingAlmostEqual(poll.elapsed, 0)
|
self.assertTimingAlmostEqual(poll.elapsed, 0)
|
||||||
|
|
||||||
|
self.assertEqual(poll(-1), False)
|
||||||
|
self.assertTimingAlmostEqual(poll.elapsed, 0)
|
||||||
|
|
||||||
self.assertEqual(poll(TIMEOUT1), False)
|
self.assertEqual(poll(TIMEOUT1), False)
|
||||||
self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1)
|
self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1)
|
||||||
|
|
||||||
@ -2785,6 +2787,16 @@ class TestWait(unittest.TestCase):
|
|||||||
p.terminate()
|
p.terminate()
|
||||||
p.join()
|
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
|
# Issue 14151: Test invalid family on invalid environment
|
||||||
|
@ -23,6 +23,9 @@ Core and Builtins
|
|||||||
Library
|
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
|
- Issue #14583: Fix importlib bug when a package's __init__.py would first
|
||||||
import one of its modules then raise an error.
|
import one of its modules then raise an error.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user