Skip to content

Commit 739c026

Browse files
authored
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.
1 parent 36b139a commit 739c026

File tree

8 files changed

+107
-25
lines changed

8 files changed

+107
-25
lines changed

Doc/library/os.rst

+10-2
Original file line numberDiff line numberDiff line change
@@ -1091,13 +1091,17 @@ as internal buffering of data.
10911091

10921092
See also :func:`set_blocking` and :meth:`socket.socket.setblocking`.
10931093

1094-
.. availability:: Unix.
1094+
.. availability:: Unix, Windows.
10951095

10961096
The function is limited on Emscripten and WASI, see
10971097
:ref:`wasm-availability` for more information.
10981098

1099+
On Windows, this function is limited to pipes.
1100+
10991101
.. versionadded:: 3.5
11001102

1103+
.. versionchanged:: 3.12
1104+
Added support for pipes on Windows.
11011105

11021106
.. function:: isatty(fd, /)
11031107

@@ -1565,13 +1569,17 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
15651569

15661570
See also :func:`get_blocking` and :meth:`socket.socket.setblocking`.
15671571

1568-
.. availability:: Unix.
1572+
.. availability:: Unix, Windows.
15691573

15701574
The function is limited on Emscripten and WASI, see
15711575
:ref:`wasm-availability` for more information.
15721576

1577+
On Windows, this function is limited to pipes.
1578+
15731579
.. versionadded:: 3.5
15741580

1581+
.. versionchanged:: 3.12
1582+
Added support for pipes on Windows.
15751583

15761584
.. data:: SF_NODISKIO
15771585
SF_MNOWAIT

Include/internal/pycore_fileutils.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ PyAPI_FUNC(int) _Py_set_inheritable_async_safe(int fd, int inheritable,
160160

161161
PyAPI_FUNC(int) _Py_dup(int fd);
162162

163-
#ifndef MS_WINDOWS
164163
PyAPI_FUNC(int) _Py_get_blocking(int fd);
165164

166165
PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
167-
#else /* MS_WINDOWS */
166+
167+
#ifdef MS_WINDOWS
168168
PyAPI_FUNC(void*) _Py_get_osfhandle_noraise(int fd);
169169

170170
PyAPI_FUNC(void*) _Py_get_osfhandle(int fd);

Lib/test/test_os.py

+1
Original file line numberDiff line numberDiff line change
@@ -4099,6 +4099,7 @@ def test_path_t_converter_and_custom_class(self):
40994099
@unittest.skipUnless(hasattr(os, 'get_blocking'),
41004100
'needs os.get_blocking() and os.set_blocking()')
41014101
@unittest.skipIf(support.is_emscripten, "Cannot unset blocking flag")
4102+
@unittest.skipIf(sys.platform == 'win32', 'Windows only supports blocking on pipes')
41024103
class BlockingTests(unittest.TestCase):
41034104
def test_blocking(self):
41044105
fd = os.open(__file__, os.O_RDONLY)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for the os.get_blocking() and os.set_blocking() functions on Windows.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle read and write operations on non-blocking pipes properly on Windows.

Modules/clinic/posixmodule.c.h

+1-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

-2
Original file line numberDiff line numberDiff line change
@@ -13930,7 +13930,6 @@ os_set_handle_inheritable_impl(PyObject *module, intptr_t handle,
1393013930
}
1393113931
#endif /* MS_WINDOWS */
1393213932

13933-
#ifndef MS_WINDOWS
1393413933
/*[clinic input]
1393513934
os.get_blocking -> bool
1393613935
fd: int
@@ -13978,7 +13977,6 @@ os_set_blocking_impl(PyObject *module, int fd, int blocking)
1397813977
return NULL;
1397913978
Py_RETURN_NONE;
1398013979
}
13981-
#endif /* !MS_WINDOWS */
1398213980

1398313981

1398413982
/*[clinic input]

Python/fileutils.c

+91-2
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,15 @@ _Py_read(int fd, void *buf, size_t count)
17501750
Py_BEGIN_ALLOW_THREADS
17511751
errno = 0;
17521752
#ifdef MS_WINDOWS
1753+
_doserrno = 0;
17531754
n = read(fd, buf, (int)count);
1755+
// read() on a non-blocking empty pipe fails with EINVAL, which is
1756+
// mapped from the Windows error code ERROR_NO_DATA.
1757+
if (n < 0 && errno == EINVAL) {
1758+
if (_doserrno == ERROR_NO_DATA) {
1759+
errno = EAGAIN;
1760+
}
1761+
}
17541762
#else
17551763
n = read(fd, buf, count);
17561764
#endif
@@ -1804,6 +1812,7 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18041812
}
18051813
}
18061814
}
1815+
18071816
#endif
18081817
if (count > _PY_WRITE_MAX) {
18091818
count = _PY_WRITE_MAX;
@@ -1814,7 +1823,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18141823
Py_BEGIN_ALLOW_THREADS
18151824
errno = 0;
18161825
#ifdef MS_WINDOWS
1817-
n = write(fd, buf, (int)count);
1826+
// write() on a non-blocking pipe fails with ENOSPC on Windows if
1827+
// the pipe lacks available space for the entire buffer.
1828+
int c = (int)count;
1829+
do {
1830+
_doserrno = 0;
1831+
n = write(fd, buf, c);
1832+
if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
1833+
break;
1834+
}
1835+
errno = EAGAIN;
1836+
c /= 2;
1837+
} while (c > 0);
18181838
#else
18191839
n = write(fd, buf, count);
18201840
#endif
@@ -1829,7 +1849,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18291849
do {
18301850
errno = 0;
18311851
#ifdef MS_WINDOWS
1832-
n = write(fd, buf, (int)count);
1852+
// write() on a non-blocking pipe fails with ENOSPC on Windows if
1853+
// the pipe lacks available space for the entire buffer.
1854+
int c = (int)count;
1855+
do {
1856+
_doserrno = 0;
1857+
n = write(fd, buf, c);
1858+
if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
1859+
break;
1860+
}
1861+
errno = EAGAIN;
1862+
c /= 2;
1863+
} while (c > 0);
18331864
#else
18341865
n = write(fd, buf, count);
18351866
#endif
@@ -2450,6 +2481,64 @@ _Py_set_blocking(int fd, int blocking)
24502481
return -1;
24512482
}
24522483
#else /* MS_WINDOWS */
2484+
int
2485+
_Py_get_blocking(int fd)
2486+
{
2487+
HANDLE handle;
2488+
DWORD mode;
2489+
BOOL success;
2490+
2491+
handle = _Py_get_osfhandle(fd);
2492+
if (handle == INVALID_HANDLE_VALUE) {
2493+
return -1;
2494+
}
2495+
2496+
Py_BEGIN_ALLOW_THREADS
2497+
success = GetNamedPipeHandleStateW(handle, &mode,
2498+
NULL, NULL, NULL, NULL, 0);
2499+
Py_END_ALLOW_THREADS
2500+
2501+
if (!success) {
2502+
PyErr_SetFromWindowsErr(0);
2503+
return -1;
2504+
}
2505+
2506+
return !(mode & PIPE_NOWAIT);
2507+
}
2508+
2509+
int
2510+
_Py_set_blocking(int fd, int blocking)
2511+
{
2512+
HANDLE handle;
2513+
DWORD mode;
2514+
BOOL success;
2515+
2516+
handle = _Py_get_osfhandle(fd);
2517+
if (handle == INVALID_HANDLE_VALUE) {
2518+
return -1;
2519+
}
2520+
2521+
Py_BEGIN_ALLOW_THREADS
2522+
success = GetNamedPipeHandleStateW(handle, &mode,
2523+
NULL, NULL, NULL, NULL, 0);
2524+
if (success) {
2525+
if (blocking) {
2526+
mode &= ~PIPE_NOWAIT;
2527+
}
2528+
else {
2529+
mode |= PIPE_NOWAIT;
2530+
}
2531+
success = SetNamedPipeHandleState(handle, &mode, NULL, NULL);
2532+
}
2533+
Py_END_ALLOW_THREADS
2534+
2535+
if (!success) {
2536+
PyErr_SetFromWindowsErr(0);
2537+
return -1;
2538+
}
2539+
return 0;
2540+
}
2541+
24532542
void*
24542543
_Py_get_osfhandle_noraise(int fd)
24552544
{

0 commit comments

Comments
 (0)