Skip to content

Commit ba175a1

Browse files
committed
fileutils: handle non-blocking pipes on Windows
Handle erroring operations on non-blocking pipes by reading the _doserrno code. Limit writes on non-blocking pipes that are too large.
1 parent f5a472d commit ba175a1

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle read and write operations on non-blocking pipes properly on Windows.

Python/fileutils.c

+64
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,50 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18041812
}
18051813
}
18061814
}
1815+
1816+
// In non-blocking mode, a write that exceeds the available size of a pipe
1817+
// fails without writing anything. Limiting writes to the pipe size allows
1818+
// a buffered write to succeed eventually, as the pipe is read.
1819+
HANDLE hfile = _Py_get_osfhandle(fd);
1820+
if (hfile == INVALID_HANDLE_VALUE) {
1821+
return -1;
1822+
}
1823+
DWORD mode, pipe_size;
1824+
if (gil_held) {
1825+
Py_BEGIN_ALLOW_THREADS
1826+
if (GetFileType(hfile) == FILE_TYPE_PIPE &&
1827+
GetNamedPipeHandleStateW(hfile, &mode, NULL, NULL, NULL,
1828+
NULL, 0) &&
1829+
mode & PIPE_NOWAIT)
1830+
{
1831+
// GetNamedPipeInfo() requires FILE_READ_ATTRIBUTES access.
1832+
// CreatePipe() includes this access for the write handle.
1833+
if (!GetNamedPipeInfo(hfile, NULL, NULL, &pipe_size, NULL)) {
1834+
pipe_size = 4096;
1835+
}
1836+
if (count > pipe_size) {
1837+
count = pipe_size;
1838+
}
1839+
}
1840+
Py_END_ALLOW_THREADS
1841+
}
1842+
else {
1843+
if (GetFileType(hfile) == FILE_TYPE_PIPE &&
1844+
GetNamedPipeHandleStateW(hfile, &mode, NULL, NULL, NULL,
1845+
NULL, 0) &&
1846+
mode & PIPE_NOWAIT)
1847+
{
1848+
// GetNamedPipeInfo() requires FILE_READ_ATTRIBUTES access.
1849+
// CreatePipe() includes this access for the write handle.
1850+
if (!GetNamedPipeInfo(hfile, NULL, NULL, &pipe_size, NULL)) {
1851+
pipe_size = 4096;
1852+
}
1853+
if (count > pipe_size) {
1854+
count = pipe_size;
1855+
}
1856+
}
1857+
}
1858+
18071859
#endif
18081860
if (count > _PY_WRITE_MAX) {
18091861
count = _PY_WRITE_MAX;
@@ -1814,7 +1866,13 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18141866
Py_BEGIN_ALLOW_THREADS
18151867
errno = 0;
18161868
#ifdef MS_WINDOWS
1869+
_doserrno = 0;
18171870
n = write(fd, buf, (int)count);
1871+
// write() on a non-blocking pipe fails with ENOSPC on Windows if
1872+
// the pipe lacks available space for the entire buffer.
1873+
if (n < 0 && errno == ENOSPC && _doserrno == 0) {
1874+
errno = EAGAIN;
1875+
}
18181876
#else
18191877
n = write(fd, buf, count);
18201878
#endif
@@ -1829,7 +1887,13 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
18291887
do {
18301888
errno = 0;
18311889
#ifdef MS_WINDOWS
1890+
_doserrno = 0;
18321891
n = write(fd, buf, (int)count);
1892+
// write() on a non-blocking pipe fails with ENOSPC on Windows if
1893+
// the pipe lacks available space for the entire buffer.
1894+
if (n < 0 && errno == ENOSPC && _doserrno == 0) {
1895+
errno = EAGAIN;
1896+
}
18331897
#else
18341898
n = write(fd, buf, count);
18351899
#endif

0 commit comments

Comments
 (0)