From 1e51d96c0f5226be1a5b06975890e572fb4feecd Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 18 Aug 2020 15:14:47 +0200 Subject: [PATCH 01/17] Add F_SETPIPE_SZ and F_GETPIPE_SZ to fcntl module --- Modules/fcntlmodule.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 39baea01ec84ea..50748c7c13f8e9 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -577,6 +577,14 @@ all_ins(PyObject* m) if (PyModule_AddIntMacro(m, F_SHLCK)) return -1; #endif +/* Linux specifics */ +#ifdef F_SETPIPE_SZ + if (PyModule_AddIntMacro(m, F_SETPIPE_SZ)) return -1; +#endif +#ifdef F_GETPIPE_SZ + if (PyModule_AddIntMacro(m, F_GETPIPE_SZ)) return -1; +#endif + /* OS X specifics */ #ifdef F_FULLFSYNC if (PyModule_AddIntMacro(m, F_FULLFSYNC)) return -1; From a218c3902f4f79036d2010e16db72b4608e3a356 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 18 Aug 2020 15:38:07 +0200 Subject: [PATCH 02/17] Add pipesize parameter for Popen class This will allow the user to control the size of the pipes. On linux the default is 64K. When a pipe is full it blocks for writing. When a pipe is empty it blocks for reading. On processes that are very fast this can lead to a lot of wasted CPU cycles. On a typical Linux system the max pipe size is 1024K which is much better. For high performance-oriented libraries such as xopen it is nice to be able to set the pipe size. The current workaround is to use my_popen_process.stdout.fileno() in conjuction with fcntl and 1031 (value of F_SETPIPE_SZ) to acquire this behavior. --- Lib/subprocess.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 86fdf27f9b03bd..1e0b55d0471226 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -62,6 +62,13 @@ import grp except ImportError: grp = None +try: + import fcntl + from fcntl import F_SETPIPE_SZ +except ImportError: + fcntl = None + F_SETPIPE_SZ = None + __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", "getoutput", "check_output", "run", "CalledProcessError", "DEVNULL", @@ -756,7 +763,7 @@ def __init__(self, args, bufsize=-1, executable=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, user=None, group=None, extra_groups=None, - encoding=None, errors=None, text=None, umask=-1): + encoding=None, errors=None, text=None, umask=-1, pipesize=-1): """Create new Popen instance.""" _cleanup() # Held while anything is calling waitpid before returncode has been @@ -770,9 +777,12 @@ def __init__(self, args, bufsize=-1, executable=None, self._communication_started = False if bufsize is None: bufsize = -1 # Restore default + if pipesize is None: + pipesize = -1 # Restore default if not isinstance(bufsize, int): raise TypeError("bufsize must be an integer") - + if not isinstance(pipesize, int): + raise TypeError("pipesize must be an integer") if _mswindows: if preexec_fn is not None: raise ValueError("preexec_fn is not supported on Windows " @@ -797,6 +807,7 @@ def __init__(self, args, bufsize=-1, executable=None, self.returncode = None self.encoding = encoding self.errors = errors + self.pipesize = pipesize # Validate the combinations of text and universal_newlines if (text is not None and universal_newlines is not None @@ -1575,6 +1586,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() + if fcntl and self.pipesize > 0: + fcntl.fcntl(p2cwrite, F_SETPIPE_SZ, self.pipesize) elif stdin == DEVNULL: p2cread = self._get_devnull() elif isinstance(stdin, int): @@ -1587,6 +1600,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() + if fcntl and self.pipesize > 0: + fcntl.fcntl(c2pwrite, F_SETPIPE_SZ, self.pipesize) elif stdout == DEVNULL: c2pwrite = self._get_devnull() elif isinstance(stdout, int): @@ -1599,6 +1614,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stderr == PIPE: errread, errwrite = os.pipe() + if fcntl and self.pipesize > 0: + fcntl.fcntl(errwrite, F_SETPIPE_SZ, self.pipesize) elif stderr == STDOUT: if c2pwrite != -1: errwrite = c2pwrite From 02b1d43762b65e0763c3b8cbde632848e60f26df Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 18 Aug 2020 16:03:29 +0200 Subject: [PATCH 03/17] Add test for F_SETPIPE_SZ and F_GETPIPE_SZ --- Lib/test/test_fcntl.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 7e1092083269e4..ef223d869bf475 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -72,11 +72,14 @@ class TestFcntl(unittest.TestCase): def setUp(self): self.f = None + self.testpipe_path = "testpipe" def tearDown(self): if self.f and not self.f.closed: self.f.close() unlink(TESTFN) + if os.path.exists(self.testpipe_path): + unlink(self.testpipe_path) def test_fcntl_fileno(self): # the example from the library docs @@ -190,6 +193,16 @@ def test_fcntl_f_getpath(self): res = fcntl.fcntl(self.f.fileno(), fcntl.F_GETPATH, bytes(len(expected))) self.assertEqual(expected, res) + @unittest.skipIf(sys.platform != 'linux', "F_SETPIPE_SZ and F_GETPIPE_SZ are only available on linux") + def test_fcntl_f_pipesize(self): + os.mkfifo(self.testpipe_path) + test_pipe_fd = os.open(self.testpipe_path, os.O_RDWR) + pipesize = 16 * 1024 # 64K is linux default. + self.assertNotEqual(fcntl.fcntl(test_pipe_fd, fcntl.F_GETPIPE_SZ), pipesize) + fcntl.fcntl(test_pipe_fd, fcntl.F_SETPIPE_SZ, pipesize) + self.assertEqual(fcntl.fcntl(test_pipe_fd, fcntl.F_GETPIPE_SZ), pipesize) + + def test_main(): run_unittest(TestFcntl) From af05503ef129f9ef0433ab793bb0f60e4c6b805c Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 18 Aug 2020 16:49:30 +0200 Subject: [PATCH 04/17] Ensure cross-platform compatibility for pipesize in subprocess.Popen --- Lib/subprocess.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1e0b55d0471226..0e0c3057868372 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -64,10 +64,8 @@ grp = None try: import fcntl - from fcntl import F_SETPIPE_SZ except ImportError: fcntl = None - F_SETPIPE_SZ = None __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", @@ -1258,11 +1256,11 @@ def _get_handles(self, stdin, stdout, stderr): if stdin is None: p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: - p2cread, _ = _winapi.CreatePipe(None, 0) + p2cread, _ = _winapi.CreatePipe(None, self.pipesize) p2cread = Handle(p2cread) _winapi.CloseHandle(_) elif stdin == PIPE: - p2cread, p2cwrite = _winapi.CreatePipe(None, 0) + p2cread, p2cwrite = _winapi.CreatePipe(None, self.pipesize) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) elif stdin == DEVNULL: p2cread = msvcrt.get_osfhandle(self._get_devnull()) @@ -1276,11 +1274,11 @@ def _get_handles(self, stdin, stdout, stderr): if stdout is None: c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) if c2pwrite is None: - _, c2pwrite = _winapi.CreatePipe(None, 0) + _, c2pwrite = _winapi.CreatePipe(None, self.pipesize) c2pwrite = Handle(c2pwrite) _winapi.CloseHandle(_) elif stdout == PIPE: - c2pread, c2pwrite = _winapi.CreatePipe(None, 0) + c2pread, c2pwrite = _winapi.CreatePipe(None, self.pipesize) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) elif stdout == DEVNULL: c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) @@ -1294,11 +1292,11 @@ def _get_handles(self, stdin, stdout, stderr): if stderr is None: errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) if errwrite is None: - _, errwrite = _winapi.CreatePipe(None, 0) + _, errwrite = _winapi.CreatePipe(None, self.pipesize) errwrite = Handle(errwrite) _winapi.CloseHandle(_) elif stderr == PIPE: - errread, errwrite = _winapi.CreatePipe(None, 0) + errread, errwrite = _winapi.CreatePipe(None, self.pipesize) errread, errwrite = Handle(errread), Handle(errwrite) elif stderr == STDOUT: errwrite = c2pwrite @@ -1586,8 +1584,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() - if fcntl and self.pipesize > 0: - fcntl.fcntl(p2cwrite, F_SETPIPE_SZ, self.pipesize) + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize) elif stdin == DEVNULL: p2cread = self._get_devnull() elif isinstance(stdin, int): @@ -1600,8 +1598,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() - if fcntl and self.pipesize > 0: - fcntl.fcntl(c2pwrite, F_SETPIPE_SZ, self.pipesize) + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(c2pwrite, fcntl.F_SETPIPE_SZ, self.pipesize) elif stdout == DEVNULL: c2pwrite = self._get_devnull() elif isinstance(stdout, int): @@ -1614,8 +1612,8 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stderr == PIPE: errread, errwrite = os.pipe() - if fcntl and self.pipesize > 0: - fcntl.fcntl(errwrite, F_SETPIPE_SZ, self.pipesize) + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(errwrite, fcntl.F_SETPIPE_SZ, self.pipesize) elif stderr == STDOUT: if c2pwrite != -1: errwrite = c2pwrite From 493d6c3b9a417609b3e8765bcbd0bbbc29e88248 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 18 Aug 2020 16:49:55 +0200 Subject: [PATCH 05/17] Add linux test for pipesize in subprocess --- Lib/test/test_subprocess.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 434ba567db0a56..e560e662688819 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -10,6 +10,7 @@ import io import itertools import os +import fcntl import errno import tempfile import time @@ -661,6 +662,22 @@ def test_stdin_devnull(self): p.wait() self.assertEqual(p.stdin, None) + @unittest.skipIf(sys.platform != "linux", "Pipe size can only be set on linux.") + def test_pipesizes(self): + # stdin redirection + pipesize = 16 * 1024 + p = subprocess.Popen([sys.executable, "-c", + 'import sys; sys.stdin.read(); sys.stdout.write("out"); sys.stderr.write("error!")'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + pipesize=pipesize) + for fifo in [p.stdin, p.stdout, p.stderr]: + self.assertEqual(fcntl.fcntl(fifo.fileno(), fcntl.F_GETPIPE_SZ), pipesize) + p.stdin.write(b"pear") + p.stdin.close() + p.wait() + def test_env(self): newenv = os.environ.copy() newenv["FRUIT"] = "orange" From aad24c600c884a12325b383681ad6c149d60edda Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 08:25:13 +0200 Subject: [PATCH 06/17] Enable subprocess pipesize test for all platforms Windows does support changing pipesize, but currently _winapi does not support measuring it. Hence the pipesize cannot be tested on this platform (yet). --- Lib/test/test_subprocess.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index e560e662688819..1bcf0a6abeadda 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -10,7 +10,6 @@ import io import itertools import os -import fcntl import errno import tempfile import time @@ -40,6 +39,11 @@ except ImportError: grp = None +try: + import fcntl +except: + fcntl = None + if support.PGO: raise unittest.SkipTest("test is not helpful for PGO") @@ -662,7 +666,6 @@ def test_stdin_devnull(self): p.wait() self.assertEqual(p.stdin, None) - @unittest.skipIf(sys.platform != "linux", "Pipe size can only be set on linux.") def test_pipesizes(self): # stdin redirection pipesize = 16 * 1024 @@ -672,8 +675,13 @@ def test_pipesizes(self): stdout=subprocess.PIPE, stderr=subprocess.PIPE, pipesize=pipesize) - for fifo in [p.stdin, p.stdout, p.stderr]: - self.assertEqual(fcntl.fcntl(fifo.fileno(), fcntl.F_GETPIPE_SZ), pipesize) + # We only assert pipe size has changed on platforms that support it. + if sys.platform != "win32" and hasattr(fcntl, "F_GETPIPE_SZ"): + for fifo in [p.stdin, p.stdout, p.stderr]: + self.assertEqual(fcntl.fcntl(fifo.fileno(), fcntl.F_GETPIPE_SZ), pipesize) + # Windows pipe size can be acquired with the GetNamedPipeInfoFunction + # https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo + # However, this function is not yet in _winapi. p.stdin.write(b"pear") p.stdin.close() p.wait() From acf9ae4f53ae84ae3c1a410778c7d144a8716c34 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 08:28:01 +0200 Subject: [PATCH 07/17] Do not set pipesizes for Popen on Windows, as this can not be tested as of yet --- Lib/subprocess.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 0e0c3057868372..6a6c2fc98e83f3 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -775,12 +775,14 @@ def __init__(self, args, bufsize=-1, executable=None, self._communication_started = False if bufsize is None: bufsize = -1 # Restore default - if pipesize is None: - pipesize = -1 # Restore default if not isinstance(bufsize, int): raise TypeError("bufsize must be an integer") + + if pipesize is None: + pipesize = -1 # Restore default if not isinstance(pipesize, int): raise TypeError("pipesize must be an integer") + if _mswindows: if preexec_fn is not None: raise ValueError("preexec_fn is not supported on Windows " @@ -1256,11 +1258,11 @@ def _get_handles(self, stdin, stdout, stderr): if stdin is None: p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: - p2cread, _ = _winapi.CreatePipe(None, self.pipesize) + p2cread, _ = _winapi.CreatePipe(None, 0) p2cread = Handle(p2cread) _winapi.CloseHandle(_) elif stdin == PIPE: - p2cread, p2cwrite = _winapi.CreatePipe(None, self.pipesize) + p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) elif stdin == DEVNULL: p2cread = msvcrt.get_osfhandle(self._get_devnull()) @@ -1274,11 +1276,11 @@ def _get_handles(self, stdin, stdout, stderr): if stdout is None: c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) if c2pwrite is None: - _, c2pwrite = _winapi.CreatePipe(None, self.pipesize) + _, c2pwrite = _winapi.CreatePipe(None, 0) c2pwrite = Handle(c2pwrite) _winapi.CloseHandle(_) elif stdout == PIPE: - c2pread, c2pwrite = _winapi.CreatePipe(None, self.pipesize) + c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) elif stdout == DEVNULL: c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) @@ -1292,11 +1294,11 @@ def _get_handles(self, stdin, stdout, stderr): if stderr is None: errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) if errwrite is None: - _, errwrite = _winapi.CreatePipe(None, self.pipesize) + _, errwrite = _winapi.CreatePipe(None, 0) errwrite = Handle(errwrite) _winapi.CloseHandle(_) elif stderr == PIPE: - errread, errwrite = _winapi.CreatePipe(None, self.pipesize) + errread, errwrite = _winapi.CreatePipe(None, 0) errread, errwrite = Handle(errread), Handle(errwrite) elif stderr == STDOUT: errwrite = c2pwrite From 6815aaa293ef6bac30e80d7f57f192af5fa24a64 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 08:29:04 +0200 Subject: [PATCH 08/17] Add F_GETPIPE_SZ, F_SETPIPE_SZ and subprocess.Popen pipesize argument to the docs --- Doc/library/fcntl.rst | 5 +++++ Doc/library/subprocess.rst | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 07a15d27216e92..9d8021150c42f5 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -39,6 +39,11 @@ descriptor. On Linux(>=3.15), the fcntl module exposes the ``F_OFD_GETLK``, ``F_OFD_SETLK`` and ``F_OFD_SETLKW`` constants, which working with open file description locks. +.. versionchanged:: 3.10 + On Linux >= 2.6.11, the fcntl module exposes the ``F_GETPIPE_SZ`` and + ``F_SETPIPE_SZ`` constants, which allow to check and modify a pipe's size + respectively. + The module defines the following functions: diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index e37cc980e97575..a7f20881b45618 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -341,7 +341,7 @@ functions. startupinfo=None, creationflags=0, restore_signals=True, \ start_new_session=False, pass_fds=(), \*, group=None, \ extra_groups=None, user=None, umask=-1, \ - encoding=None, errors=None, text=None) + encoding=None, errors=None, text=None, pipesize=-1) Execute a child program in a new process. On POSIX, the class uses :meth:`os.execvp`-like behavior to execute the child program. On Windows, @@ -625,6 +625,12 @@ functions. * :data:`CREATE_DEFAULT_ERROR_MODE` * :data:`CREATE_BREAKAWAY_FROM_JOB` + ``pipesize`` can be used to change the size of the pipe when + ``subprocess.PIPE`` is used for ``stdin``,``stdout`` or ``stderr`` on Linux. + + .. versionadded:: 3.10 + The ``pipesize`` parameter was added. + Popen objects are supported as context managers via the :keyword:`with` statement: on exit, standard file descriptors are closed, and the process is waited for. :: From 6bfd6a5b1958847addae142e229d63c2a81898f8 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 08:30:11 +0200 Subject: [PATCH 09/17] Add Ruben Vorderman to ACKS for work on the pipesize parameter in subprocess.Popen --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 1599b09c692b75..f018aa436e7ccf 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1805,6 +1805,7 @@ Johannes Vogel Michael Vogt Radu Voicilas Alex Volkov +Ruben Vorderman Guido Vranken Martijn Vries Sjoerd de Vries From cf97235ec1fe6c1d41fb51865827ff83caa82b84 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 08:33:02 +0200 Subject: [PATCH 10/17] Add blurb for bpo 41586 --- .../next/Library/2020-08-19-08-32-13.bpo-41586.IYjmjK.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-08-19-08-32-13.bpo-41586.IYjmjK.rst diff --git a/Misc/NEWS.d/next/Library/2020-08-19-08-32-13.bpo-41586.IYjmjK.rst b/Misc/NEWS.d/next/Library/2020-08-19-08-32-13.bpo-41586.IYjmjK.rst new file mode 100644 index 00000000000000..40461679ebdfeb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-08-19-08-32-13.bpo-41586.IYjmjK.rst @@ -0,0 +1,2 @@ +Add F_SETPIPE_SZ and F_GETPIPE_SZ to fcntl module. Allow setting pipesize on +subprocess.Popen. From 389e4e9643f4974df246a7aaf71371b7e9b6e2a2 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 09:11:42 +0200 Subject: [PATCH 11/17] Simplify test fore F_GETPIPE_SZ and F_SETPIPE_SZ so no file cleanup is needed. --- Lib/test/test_fcntl.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index ef223d869bf475..5965ece4691bbd 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -72,14 +72,11 @@ class TestFcntl(unittest.TestCase): def setUp(self): self.f = None - self.testpipe_path = "testpipe" def tearDown(self): if self.f and not self.f.closed: self.f.close() unlink(TESTFN) - if os.path.exists(self.testpipe_path): - unlink(self.testpipe_path) def test_fcntl_fileno(self): # the example from the library docs @@ -195,12 +192,13 @@ def test_fcntl_f_getpath(self): @unittest.skipIf(sys.platform != 'linux', "F_SETPIPE_SZ and F_GETPIPE_SZ are only available on linux") def test_fcntl_f_pipesize(self): - os.mkfifo(self.testpipe_path) - test_pipe_fd = os.open(self.testpipe_path, os.O_RDWR) + test_pipe_r, test_pipe_w = os.pipe() pipesize = 16 * 1024 # 64K is linux default. - self.assertNotEqual(fcntl.fcntl(test_pipe_fd, fcntl.F_GETPIPE_SZ), pipesize) - fcntl.fcntl(test_pipe_fd, fcntl.F_SETPIPE_SZ, pipesize) - self.assertEqual(fcntl.fcntl(test_pipe_fd, fcntl.F_GETPIPE_SZ), pipesize) + self.assertNotEqual(fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ), pipesize) + fcntl.fcntl(test_pipe_w, fcntl.F_SETPIPE_SZ, pipesize) + self.assertEqual(fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ), pipesize) + os.close(test_pipe_r) + os.close(test_pipe_w) def test_main(): From e601174250c83a76e60550d45b93e1720796bd88 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 09:28:11 +0200 Subject: [PATCH 12/17] Test F_SETPIPE_SZ and F_GETPIPE_SZ on all supported platforms --- Lib/test/test_fcntl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 5965ece4691bbd..b3c0b067efa3be 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -190,7 +190,8 @@ def test_fcntl_f_getpath(self): res = fcntl.fcntl(self.f.fileno(), fcntl.F_GETPATH, bytes(len(expected))) self.assertEqual(expected, res) - @unittest.skipIf(sys.platform != 'linux', "F_SETPIPE_SZ and F_GETPIPE_SZ are only available on linux") + @unittest.skipIf(not (hasattr(fcntl, "F_SETPIPE_SZ") and hasattr(fcntl, "F_GETPIPE_SZ")), + "F_SETPIPE_SZ and F_GETPIPE_SZ are not available on all unix platforms.") def test_fcntl_f_pipesize(self): test_pipe_r, test_pipe_w = os.pipe() pipesize = 16 * 1024 # 64K is linux default. From 24f061b4e4bdede02c9a3eacb854b46a13f6b094 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 10:48:53 +0200 Subject: [PATCH 13/17] Intentionally exclude fcntl from subprocess.__all__ test. fcntl is not available on Windows. --- Lib/test/test_subprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 1bcf0a6abeadda..1df6e727f99f99 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3528,7 +3528,7 @@ def test_getoutput(self): def test__all__(self): """Ensure that __all__ is populated properly.""" - intentionally_excluded = {"list2cmdline", "Handle", "pwd", "grp"} + intentionally_excluded = {"list2cmdline", "Handle", "pwd", "grp", "fcntl"} exported = set(subprocess.__all__) possible_exports = set() import types From 58925f3bdeaa904b64e8e5257c594c1268c9de47 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 19 Aug 2020 11:06:32 +0200 Subject: [PATCH 14/17] Properly format the pipesize argument in the subprocess docs. --- Doc/library/subprocess.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index a7f20881b45618..17a956f466b434 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -625,8 +625,9 @@ functions. * :data:`CREATE_DEFAULT_ERROR_MODE` * :data:`CREATE_BREAKAWAY_FROM_JOB` - ``pipesize`` can be used to change the size of the pipe when - ``subprocess.PIPE`` is used for ``stdin``,``stdout`` or ``stderr`` on Linux. + *pipesize* can be used to change the size of the pipe when + :data:`PIPE` is used for *stdin*, *stdout* or *stderr*. The size of the pipe + is only changed on platforms that support this. .. versionadded:: 3.10 The ``pipesize`` parameter was added. From 15b759e928e4322c8aab5ebcb46dd3a9116d8f88 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Mon, 24 Aug 2020 09:42:29 +0200 Subject: [PATCH 15/17] Determine default dynamically --- Lib/test/test_fcntl.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index b3c0b067efa3be..8d6e9ff788454f 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -194,10 +194,11 @@ def test_fcntl_f_getpath(self): "F_SETPIPE_SZ and F_GETPIPE_SZ are not available on all unix platforms.") def test_fcntl_f_pipesize(self): test_pipe_r, test_pipe_w = os.pipe() - pipesize = 16 * 1024 # 64K is linux default. - self.assertNotEqual(fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ), pipesize) - fcntl.fcntl(test_pipe_w, fcntl.F_SETPIPE_SZ, pipesize) - self.assertEqual(fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ), pipesize) + # Get the default pipesize with F_GETPIPE_SZ + pipesize_default = fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ) + # Multiply the default with 2 to get a new value. + fcntl.fcntl(test_pipe_w, fcntl.F_SETPIPE_SZ, pipesize_default * 2) + self.assertEqual(fcntl.fcntl(test_pipe_w, fcntl.F_GETPIPE_SZ), pipesize_default * 2) os.close(test_pipe_r) os.close(test_pipe_w) From d4d08869f71441010dfbf8d9b7d6660249ec4bdf Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Mon, 24 Aug 2020 09:56:23 +0200 Subject: [PATCH 16/17] Add test for pipesize default --- Lib/test/test_subprocess.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 1df6e727f99f99..8b576c036ef0d2 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -686,6 +686,26 @@ def test_pipesizes(self): p.stdin.close() p.wait() + def test_pipesize_default(self): + p = subprocess.Popen([sys.executable, "-c", + 'import sys; sys.stdin.read(); sys.stdout.write("out");' + ' sys.stderr.write("error!")'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + pipesize=-1) + # UNIX tests using fcntl + if sys.platform != "win32" and hasattr(fcntl, "F_GETPIPE_SZ"): + fp_r, fp_w = os.pipe() + default_pipesize = fcntl.fcntl(fp_w, fcntl.F_GETPIPE_SZ) + for fifo in [p.stdin, p.stdout, p.stderr]: + self.assertEqual( + fcntl.fcntl(fifo.fileno(), fcntl.F_GETPIPE_SZ), default_pipesize) + # On other platforms we cannot test the pipe size (yet). But above code + # using pipesize=-1 should not crash. + p.stdin.close() + p.wait() + def test_env(self): newenv = os.environ.copy() newenv["FRUIT"] = "orange" From c129ca8225e81c349b8aa3c3e6bde383b1fcc94d Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Mon, 24 Aug 2020 10:00:29 +0200 Subject: [PATCH 17/17] Add a note to the documentation about only Linux supporting the pipesize parameter --- Doc/library/subprocess.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 17a956f466b434..7993b103f473e2 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -627,7 +627,8 @@ functions. *pipesize* can be used to change the size of the pipe when :data:`PIPE` is used for *stdin*, *stdout* or *stderr*. The size of the pipe - is only changed on platforms that support this. + is only changed on platforms that support this (only Linux at this time of + writing). Other platforms will ignore this parameter. .. versionadded:: 3.10 The ``pipesize`` parameter was added.