Branch merge: packaging fixes
diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index e1f4382..0e220d6 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -411,6 +411,20 @@
See :ref:`multiprocessing-auth-keys`.
+ .. attribute:: sentinel
+
+ A numeric handle of a system object which will become "ready" when
+ the process ends.
+
+ On Windows, this is an OS handle usable with the ``WaitForSingleObject``
+ and ``WaitForMultipleObjects`` family of API calls. On Unix, this is
+ a file descriptor usable with primitives from the :mod:`select` module.
+
+ You can use this value if you want to wait on several events at once.
+ Otherwise calling :meth:`join()` is simpler.
+
+ .. versionadded:: 3.3
+
.. method:: terminate()
Terminate the process. On Unix this is done using the ``SIGTERM`` signal;
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index c62a00d..06f6b7f 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -1019,11 +1019,11 @@
Availability: Unix, Windows.
-.. function:: pipe2(flags=0)
+.. function:: pipe2(flags)
Create a pipe with *flags* set atomically.
- *flags* is optional and can be constructed by ORing together zero or more of
- these values: :data:`O_NONBLOCK`, :data:`O_CLOEXEC`.
+ *flags* can be constructed by ORing together one or more of these values:
+ :data:`O_NONBLOCK`, :data:`O_CLOEXEC`.
Return a pair of file descriptors ``(r, w)`` usable for reading and writing,
respectively.
diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def
index 5ddd098..9546e2b 100644
--- a/Lib/idlelib/config-main.def
+++ b/Lib/idlelib/config-main.def
@@ -46,8 +46,8 @@
[General]
editor-on-startup= 0
autosave= 0
-print-command-posix=lpr %s
-print-command-win=start /min notepad /p %s
+print-command-posix=lpr %%s
+print-command-win=start /min notepad /p %%s
delete-exitfunc= 1
[EditorWindow]
diff --git a/Lib/multiprocessing/forking.py b/Lib/multiprocessing/forking.py
index 3d95557..3c359cb 100644
--- a/Lib/multiprocessing/forking.py
+++ b/Lib/multiprocessing/forking.py
@@ -101,10 +101,12 @@
if sys.platform != 'win32':
import time
+ 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
@@ -118,8 +120,12 @@
sys.stderr.flush()
self.returncode = None
+ r, w = os.pipe()
+ self.sentinel = r
+
self.pid = os.fork()
if self.pid == 0:
+ os.close(r)
if 'random' in sys.modules:
import random
random.seed()
@@ -128,6 +134,11 @@
sys.stderr.flush()
os._exit(code)
+ # `w` will be closed when the child exits, at which point `r`
+ # will become ready for reading (using e.g. select()).
+ os.close(w)
+ util.Finalize(self, os.close, (r,))
+
def poll(self, flag=os.WNOHANG):
if self.returncode is None:
try:
@@ -145,20 +156,14 @@
return self.returncode
def wait(self, timeout=None):
- if timeout is None:
- return self.poll(0)
- deadline = time.time() + timeout
- delay = 0.0005
- while 1:
- res = self.poll()
- if res is not None:
- break
- remaining = deadline - time.time()
- if remaining <= 0:
- break
- delay = min(delay * 2, remaining, 0.05)
- time.sleep(delay)
- return res
+ if self.returncode is None:
+ if timeout is not None:
+ r = _select([self.sentinel], [], [], timeout)[0]
+ if not r:
+ return None
+ # This shouldn't block if select() returned successfully.
+ return self.poll(os.WNOHANG if timeout == 0.0 else 0)
+ return self.returncode
def terminate(self):
if self.returncode is None:
@@ -258,6 +263,7 @@
self.pid = pid
self.returncode = None
self._handle = hp
+ self.sentinel = int(hp)
# send information to child
prep_data = get_preparation_data(process_obj._name)
diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py
index 3fb9ff6..99ee532 100644
--- a/Lib/multiprocessing/process.py
+++ b/Lib/multiprocessing/process.py
@@ -132,6 +132,7 @@
else:
from .forking import Popen
self._popen = Popen(self)
+ self._sentinel = self._popen.sentinel
_current_process._children.add(self)
def terminate(self):
@@ -218,6 +219,17 @@
pid = ident
+ @property
+ def sentinel(self):
+ '''
+ Return a file descriptor (Unix) or handle (Windows) suitable for
+ waiting for process termination.
+ '''
+ try:
+ return self._sentinel
+ except AttributeError:
+ raise ValueError("process not started")
+
def __repr__(self):
if self is _current_process:
status = 'started'
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index 30b7a85..b59ac9f 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -32,9 +32,11 @@
# SUCH DAMAGE.
#
+import functools
import itertools
import weakref
import atexit
+import select
import threading # we want threading to install it's
# cleanup function before multiprocessing does
@@ -315,3 +317,21 @@
register_after_fork(self, lambda obj : obj.__dict__.clear())
def __reduce__(self):
return type(self), ()
+
+
+#
+# Automatic retry after EINTR
+#
+
+def _eintr_retry(func, _errors=(EnvironmentError, select.error)):
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ while True:
+ try:
+ return func(*args, **kwargs)
+ except _errors as e:
+ # select.error has no `errno` attribute
+ if e.args[0] == errno.EINTR:
+ continue
+ raise
+ return wrapped
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index f724b9f..3295d14 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -172,27 +172,6 @@
except ImportError:
_have_ssl = False
else:
- class SSLFakeFile:
- """A fake file like object that really wraps a SSLObject.
-
- It only supports what is needed in smtplib.
- """
- def __init__(self, sslobj):
- self.sslobj = sslobj
-
- def readline(self):
- str = b""
- chr = None
- while chr != b"\n":
- chr = self.sslobj.read(1)
- if not chr:
- break
- str += chr
- return str
-
- def close(self):
- pass
-
_have_ssl = True
@@ -322,6 +301,7 @@
if self.debuglevel > 0:
print('connect:', (host, port), file=stderr)
self.sock = self._get_socket(host, port, self.timeout)
+ self.file = None
(code, msg) = self.getreply()
if self.debuglevel > 0:
print("connect:", msg, file=stderr)
@@ -669,7 +649,7 @@
self.sock = context.wrap_socket(self.sock)
else:
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
- self.file = SSLFakeFile(self.sock)
+ self.file = None
# RFC 3207:
# The client MUST discard any knowledge obtained from
# the server, such as the list of SMTP service extensions,
@@ -853,7 +833,6 @@
new_socket = self.context.wrap_socket(new_socket)
else:
new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
- self.file = SSLFakeFile(new_socket)
return new_socket
__all__.append("SMTP_SSL")
@@ -890,6 +869,7 @@
# Handle Unix-domain sockets.
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.file = None
self.sock.connect(host)
except socket.error as msg:
if self.debuglevel > 0:
diff --git a/Lib/test/support.py b/Lib/test/support.py
index ec2378b..f5dbb85 100644
--- a/Lib/test/support.py
+++ b/Lib/test/support.py
@@ -1561,9 +1561,10 @@
try:
os.symlink(TESTFN, symlink_path)
can = True
- os.remove(symlink_path)
except (OSError, NotImplementedError):
can = False
+ else:
+ os.remove(symlink_path)
_can_symlink = can
return can
diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/test_multiprocessing.py
index 0c05ff6..85094cc 100644
--- a/Lib/test/test_multiprocessing.py
+++ b/Lib/test/test_multiprocessing.py
@@ -71,6 +71,23 @@
'HAVE_BROKEN_SEM_GETVALUE', False)
WIN32 = (sys.platform == "win32")
+if WIN32:
+ from _subprocess 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)
+
+ def wait_for_handle(handle, timeout):
+ if timeout is not None and timeout < 0.0:
+ timeout = None
+ return handle in _select([handle], [], [], timeout)[0]
#
# Some tests require ctypes
@@ -307,6 +324,26 @@
]
self.assertEqual(result, expected)
+ @classmethod
+ def _test_sentinel(cls, event):
+ event.wait(10.0)
+
+ def test_sentinel(self):
+ if self.TYPE == "threads":
+ return
+ event = self.Event()
+ p = self.Process(target=self._test_sentinel, args=(event,))
+ with self.assertRaises(ValueError):
+ p.sentinel
+ p.start()
+ self.addCleanup(p.join)
+ sentinel = p.sentinel
+ self.assertIsInstance(sentinel, int)
+ self.assertFalse(wait_for_handle(sentinel, timeout=0.0))
+ event.set()
+ p.join()
+ self.assertTrue(wait_for_handle(sentinel, timeout=DELTA))
+
#
#
#
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 421ea68..70a47b0 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -481,8 +481,8 @@
self.assertRaises(TypeError, os.pipe2, 'DEADBEEF')
self.assertRaises(TypeError, os.pipe2, 0, 0)
- # try calling without flag, like os.pipe()
- r, w = os.pipe2()
+ # try calling with flags = 0, like os.pipe()
+ r, w = os.pipe2(0)
os.close(r)
os.close(w)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 7d4ca2c..b52d8e8 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -127,9 +127,10 @@
with self.assertRaises(subprocess.TimeoutExpired) as c:
output = subprocess.check_output(
[sys.executable, "-c",
- "import sys; sys.stdout.write('BDFL')\n"
+ "import sys, time\n"
+ "sys.stdout.write('BDFL')\n"
"sys.stdout.flush()\n"
- "while True: pass"],
+ "time.sleep(3600)"],
# Some heavily loaded buildbots (sparc Debian 3.x) require
# this much time to start and print.
timeout=3)
diff --git a/Misc/NEWS b/Misc/NEWS
index 650ae60..874498b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -187,6 +187,13 @@
Library
-------
+- Issue #12040: Expose a new attribute ``sentinel`` on instances of
+ :class:`multiprocessing.Process`. Also, fix Process.join() to not use
+ polling anymore, when given a timeout.
+
+- Issue #11893: Remove obsolete internal wrapper class ``SSLFakeFile`` in the
+ smtplib module. Patch by Catalin Iacob.
+
- Issue #12080: Fix a Decimal.power() case that took an unreasonably long time
to compute.
@@ -764,6 +771,9 @@
Build
-----
+- Do not accidentally include the directory containing sqlite.h twice when
+ building sqlite3.
+
- Issue #11217: For 64-bit/32-bit Mac OS X universal framework builds,
ensure "make install" creates symlinks in --prefix bin for the "-32"
files in the framework bin directory like the installer does.
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 533f404..38ae5c7 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -2091,7 +2091,7 @@
if (len == 0) {
return PyUnicode_FromFormat("array('%c')", (int)typecode);
}
- if ((typecode == 'u'))
+ if ('u' == typecode)
v = array_tounicode(a, NULL);
else
v = array_tolist(a, NULL);
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 36ca67d..38f6157 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -645,9 +645,9 @@
return NULL;
} else {
/* bounds check the values */
- if (cnt < 0 || (cnt + dest) < cnt || (cnt + src) < cnt ||
- src < 0 || src > self->size || (src + cnt) > self->size ||
- dest < 0 || dest > self->size || (dest + cnt) > self->size) {
+ if ((cnt + dest) < cnt || (cnt + src) < cnt ||
+ src > self->size || (src + cnt) > self->size ||
+ dest > self->size || (dest + cnt) > self->size) {
PyErr_SetString(PyExc_ValueError,
"source, destination, or count out of range");
return NULL;
diff --git a/Modules/nismodule.c b/Modules/nismodule.c
index a81ca8c..0af495f 100644
--- a/Modules/nismodule.c
+++ b/Modules/nismodule.c
@@ -411,7 +411,7 @@
return NULL;
if ((list = PyList_New(0)) == NULL)
return NULL;
- for (maps = maps; maps; maps = maps->next) {
+ for (; maps; maps = maps->next) {
PyObject *str = PyUnicode_FromString(maps->map);
if (!str || PyList_Append(list, str) < 0)
{
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index e529afd..acc420f 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -6549,20 +6549,21 @@
#ifdef HAVE_PIPE2
PyDoc_STRVAR(posix_pipe2__doc__,
-"pipe2(flags=0) -> (read_end, write_end)\n\n\
-Create a pipe with flags set atomically.\
-flags is optional and can be constructed by ORing together zero or more\n\
-of these values: O_NONBLOCK, O_CLOEXEC.\n\
+"pipe2(flags) -> (read_end, write_end)\n\n\
+Create a pipe with flags set atomically.\n\
+flags can be constructed by ORing together one or more of these values:\n\
+O_NONBLOCK, O_CLOEXEC.\n\
");
static PyObject *
-posix_pipe2(PyObject *self, PyObject *args)
+posix_pipe2(PyObject *self, PyObject *arg)
{
- int flags = 0;
+ int flags;
int fds[2];
int res;
- if (!PyArg_ParseTuple(args, "|i:pipe2", &flags))
+ flags = PyLong_AsLong(arg);
+ if (flags == -1 && PyErr_Occurred())
return NULL;
res = pipe2(fds, flags);
@@ -9467,7 +9468,7 @@
{"pipe", posix_pipe, METH_NOARGS, posix_pipe__doc__},
#endif
#ifdef HAVE_PIPE2
- {"pipe2", posix_pipe2, METH_VARARGS, posix_pipe2__doc__},
+ {"pipe2", posix_pipe2, METH_O, posix_pipe2__doc__},
#endif
#ifdef HAVE_MKFIFO
{"mkfifo", posix_mkfifo, METH_VARARGS, posix_mkfifo__doc__},
diff --git a/setup.py b/setup.py
index 896d604..39751c3 100644
--- a/setup.py
+++ b/setup.py
@@ -1045,10 +1045,15 @@
else:
sqlite_extra_link_args = ()
+ include_dirs = ["Modules/_sqlite"]
+ # Only include the directory where sqlite was found if it does
+ # not already exist in set include directories, otherwise you
+ # can end up with a bad search path order.
+ if sqlite_incdir not in self.compiler.include_dirs:
+ include_dirs.append(sqlite_incdir)
exts.append(Extension('_sqlite3', sqlite_srcs,
define_macros=sqlite_defines,
- include_dirs=["Modules/_sqlite",
- sqlite_incdir],
+ include_dirs=include_dirs,
library_dirs=sqlite_libdir,
runtime_library_dirs=sqlite_libdir,
extra_link_args=sqlite_extra_link_args,