mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
gh-101881: Support (non-)blocking read/write functions on Windows pipes (GH-101882)
* fileutils: handle non-blocking pipe IO on Windows Handle erroring operations on non-blocking pipes by reading the _doserrno code. Limit writes on non-blocking pipes that are too large. * Support blocking functions on Windows Use the GetNamedPipeHandleState and SetNamedPipeHandleState Win32 API functions to add support for os.get_blocking and os.set_blocking.
This commit is contained in:
parent
36b139af63
commit
739c026f44
8 changed files with 107 additions and 25 deletions
|
|
@ -1091,13 +1091,17 @@ as internal buffering of data.
|
||||||
|
|
||||||
See also :func:`set_blocking` and :meth:`socket.socket.setblocking`.
|
See also :func:`set_blocking` and :meth:`socket.socket.setblocking`.
|
||||||
|
|
||||||
.. availability:: Unix.
|
.. availability:: Unix, Windows.
|
||||||
|
|
||||||
The function is limited on Emscripten and WASI, see
|
The function is limited on Emscripten and WASI, see
|
||||||
:ref:`wasm-availability` for more information.
|
:ref:`wasm-availability` for more information.
|
||||||
|
|
||||||
|
On Windows, this function is limited to pipes.
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12
|
||||||
|
Added support for pipes on Windows.
|
||||||
|
|
||||||
.. function:: isatty(fd, /)
|
.. function:: isatty(fd, /)
|
||||||
|
|
||||||
|
|
@ -1565,13 +1569,17 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
|
||||||
|
|
||||||
See also :func:`get_blocking` and :meth:`socket.socket.setblocking`.
|
See also :func:`get_blocking` and :meth:`socket.socket.setblocking`.
|
||||||
|
|
||||||
.. availability:: Unix.
|
.. availability:: Unix, Windows.
|
||||||
|
|
||||||
The function is limited on Emscripten and WASI, see
|
The function is limited on Emscripten and WASI, see
|
||||||
:ref:`wasm-availability` for more information.
|
:ref:`wasm-availability` for more information.
|
||||||
|
|
||||||
|
On Windows, this function is limited to pipes.
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12
|
||||||
|
Added support for pipes on Windows.
|
||||||
|
|
||||||
.. data:: SF_NODISKIO
|
.. data:: SF_NODISKIO
|
||||||
SF_MNOWAIT
|
SF_MNOWAIT
|
||||||
|
|
|
||||||
|
|
@ -160,11 +160,11 @@ PyAPI_FUNC(int) _Py_set_inheritable_async_safe(int fd, int inheritable,
|
||||||
|
|
||||||
PyAPI_FUNC(int) _Py_dup(int fd);
|
PyAPI_FUNC(int) _Py_dup(int fd);
|
||||||
|
|
||||||
#ifndef MS_WINDOWS
|
|
||||||
PyAPI_FUNC(int) _Py_get_blocking(int fd);
|
PyAPI_FUNC(int) _Py_get_blocking(int fd);
|
||||||
|
|
||||||
PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
|
PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
|
||||||
#else /* MS_WINDOWS */
|
|
||||||
|
#ifdef MS_WINDOWS
|
||||||
PyAPI_FUNC(void*) _Py_get_osfhandle_noraise(int fd);
|
PyAPI_FUNC(void*) _Py_get_osfhandle_noraise(int fd);
|
||||||
|
|
||||||
PyAPI_FUNC(void*) _Py_get_osfhandle(int fd);
|
PyAPI_FUNC(void*) _Py_get_osfhandle(int fd);
|
||||||
|
|
|
||||||
|
|
@ -4099,6 +4099,7 @@ class PathTConverterTests(unittest.TestCase):
|
||||||
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
||||||
'needs os.get_blocking() and os.set_blocking()')
|
'needs os.get_blocking() and os.set_blocking()')
|
||||||
@unittest.skipIf(support.is_emscripten, "Cannot unset blocking flag")
|
@unittest.skipIf(support.is_emscripten, "Cannot unset blocking flag")
|
||||||
|
@unittest.skipIf(sys.platform == 'win32', 'Windows only supports blocking on pipes')
|
||||||
class BlockingTests(unittest.TestCase):
|
class BlockingTests(unittest.TestCase):
|
||||||
def test_blocking(self):
|
def test_blocking(self):
|
||||||
fd = os.open(__file__, os.O_RDONLY)
|
fd = os.open(__file__, os.O_RDONLY)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for the os.get_blocking() and os.set_blocking() functions on Windows.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Handle read and write operations on non-blocking pipes properly on Windows.
|
||||||
18
Modules/clinic/posixmodule.c.h
generated
18
Modules/clinic/posixmodule.c.h
generated
|
|
@ -10402,8 +10402,6 @@ exit:
|
||||||
|
|
||||||
#endif /* defined(MS_WINDOWS) */
|
#endif /* defined(MS_WINDOWS) */
|
||||||
|
|
||||||
#if !defined(MS_WINDOWS)
|
|
||||||
|
|
||||||
PyDoc_STRVAR(os_get_blocking__doc__,
|
PyDoc_STRVAR(os_get_blocking__doc__,
|
||||||
"get_blocking($module, fd, /)\n"
|
"get_blocking($module, fd, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
|
@ -10439,10 +10437,6 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* !defined(MS_WINDOWS) */
|
|
||||||
|
|
||||||
#if !defined(MS_WINDOWS)
|
|
||||||
|
|
||||||
PyDoc_STRVAR(os_set_blocking__doc__,
|
PyDoc_STRVAR(os_set_blocking__doc__,
|
||||||
"set_blocking($module, fd, blocking, /)\n"
|
"set_blocking($module, fd, blocking, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
|
@ -10482,8 +10476,6 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* !defined(MS_WINDOWS) */
|
|
||||||
|
|
||||||
PyDoc_STRVAR(os_DirEntry_is_symlink__doc__,
|
PyDoc_STRVAR(os_DirEntry_is_symlink__doc__,
|
||||||
"is_symlink($self, /)\n"
|
"is_symlink($self, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
|
@ -11789,14 +11781,6 @@ exit:
|
||||||
#define OS_SET_HANDLE_INHERITABLE_METHODDEF
|
#define OS_SET_HANDLE_INHERITABLE_METHODDEF
|
||||||
#endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */
|
#endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */
|
||||||
|
|
||||||
#ifndef OS_GET_BLOCKING_METHODDEF
|
|
||||||
#define OS_GET_BLOCKING_METHODDEF
|
|
||||||
#endif /* !defined(OS_GET_BLOCKING_METHODDEF) */
|
|
||||||
|
|
||||||
#ifndef OS_SET_BLOCKING_METHODDEF
|
|
||||||
#define OS_SET_BLOCKING_METHODDEF
|
|
||||||
#endif /* !defined(OS_SET_BLOCKING_METHODDEF) */
|
|
||||||
|
|
||||||
#ifndef OS_GETRANDOM_METHODDEF
|
#ifndef OS_GETRANDOM_METHODDEF
|
||||||
#define OS_GETRANDOM_METHODDEF
|
#define OS_GETRANDOM_METHODDEF
|
||||||
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
||||||
|
|
@ -11812,4 +11796,4 @@ exit:
|
||||||
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
|
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
|
||||||
/*[clinic end generated code: output=a3f76228b549e8ec input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=1b0eb6a76b1a0e28 input=a9049054013a1b77]*/
|
||||||
|
|
|
||||||
|
|
@ -13930,7 +13930,6 @@ os_set_handle_inheritable_impl(PyObject *module, intptr_t handle,
|
||||||
}
|
}
|
||||||
#endif /* MS_WINDOWS */
|
#endif /* MS_WINDOWS */
|
||||||
|
|
||||||
#ifndef MS_WINDOWS
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
os.get_blocking -> bool
|
os.get_blocking -> bool
|
||||||
fd: int
|
fd: int
|
||||||
|
|
@ -13978,7 +13977,6 @@ os_set_blocking_impl(PyObject *module, int fd, int blocking)
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
#endif /* !MS_WINDOWS */
|
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
|
|
||||||
|
|
@ -1750,7 +1750,15 @@ _Py_read(int fd, void *buf, size_t count)
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
errno = 0;
|
errno = 0;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
|
_doserrno = 0;
|
||||||
n = read(fd, buf, (int)count);
|
n = read(fd, buf, (int)count);
|
||||||
|
// read() on a non-blocking empty pipe fails with EINVAL, which is
|
||||||
|
// mapped from the Windows error code ERROR_NO_DATA.
|
||||||
|
if (n < 0 && errno == EINVAL) {
|
||||||
|
if (_doserrno == ERROR_NO_DATA) {
|
||||||
|
errno = EAGAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
n = read(fd, buf, count);
|
n = read(fd, buf, count);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1804,6 +1812,7 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
if (count > _PY_WRITE_MAX) {
|
if (count > _PY_WRITE_MAX) {
|
||||||
count = _PY_WRITE_MAX;
|
count = _PY_WRITE_MAX;
|
||||||
|
|
@ -1814,7 +1823,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
errno = 0;
|
errno = 0;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
n = write(fd, buf, (int)count);
|
// write() on a non-blocking pipe fails with ENOSPC on Windows if
|
||||||
|
// the pipe lacks available space for the entire buffer.
|
||||||
|
int c = (int)count;
|
||||||
|
do {
|
||||||
|
_doserrno = 0;
|
||||||
|
n = write(fd, buf, c);
|
||||||
|
if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
errno = EAGAIN;
|
||||||
|
c /= 2;
|
||||||
|
} while (c > 0);
|
||||||
#else
|
#else
|
||||||
n = write(fd, buf, count);
|
n = write(fd, buf, count);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1829,7 +1849,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
|
||||||
do {
|
do {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
n = write(fd, buf, (int)count);
|
// write() on a non-blocking pipe fails with ENOSPC on Windows if
|
||||||
|
// the pipe lacks available space for the entire buffer.
|
||||||
|
int c = (int)count;
|
||||||
|
do {
|
||||||
|
_doserrno = 0;
|
||||||
|
n = write(fd, buf, c);
|
||||||
|
if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
errno = EAGAIN;
|
||||||
|
c /= 2;
|
||||||
|
} while (c > 0);
|
||||||
#else
|
#else
|
||||||
n = write(fd, buf, count);
|
n = write(fd, buf, count);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -2450,6 +2481,64 @@ error:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#else /* MS_WINDOWS */
|
#else /* MS_WINDOWS */
|
||||||
|
int
|
||||||
|
_Py_get_blocking(int fd)
|
||||||
|
{
|
||||||
|
HANDLE handle;
|
||||||
|
DWORD mode;
|
||||||
|
BOOL success;
|
||||||
|
|
||||||
|
handle = _Py_get_osfhandle(fd);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
success = GetNamedPipeHandleStateW(handle, &mode,
|
||||||
|
NULL, NULL, NULL, NULL, 0);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(mode & PIPE_NOWAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_Py_set_blocking(int fd, int blocking)
|
||||||
|
{
|
||||||
|
HANDLE handle;
|
||||||
|
DWORD mode;
|
||||||
|
BOOL success;
|
||||||
|
|
||||||
|
handle = _Py_get_osfhandle(fd);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
success = GetNamedPipeHandleStateW(handle, &mode,
|
||||||
|
NULL, NULL, NULL, NULL, 0);
|
||||||
|
if (success) {
|
||||||
|
if (blocking) {
|
||||||
|
mode &= ~PIPE_NOWAIT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mode |= PIPE_NOWAIT;
|
||||||
|
}
|
||||||
|
success = SetNamedPipeHandleState(handle, &mode, NULL, NULL);
|
||||||
|
}
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void*
|
void*
|
||||||
_Py_get_osfhandle_noraise(int fd)
|
_Py_get_osfhandle_noraise(int fd)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue