Issue #22054: Add os.get_blocking() and os.set_blocking() functions to get and
set the blocking mode of a file descriptor (False if the O_NONBLOCK flag is set, True otherwise). These functions are not available on Windows.
This commit is contained in:
parent
6aa4269ed2
commit
1db9e7bb19
|
@ -807,6 +807,17 @@ as internal buffering of data.
|
|||
Availability: Unix.
|
||||
|
||||
|
||||
.. function:: get_blocking(fd)
|
||||
|
||||
Get the blocking mode of the file descriptor: ``False`` if the
|
||||
:data:`O_NONBLOCK` flag is set, ``True`` if the flag is cleared.
|
||||
|
||||
See also :func:`set_blocking` and :meth:`socket.socket.setblocking`.
|
||||
|
||||
Availability: Unix.
|
||||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
.. function:: isatty(fd)
|
||||
|
||||
Return ``True`` if the file descriptor *fd* is open and connected to a
|
||||
|
@ -1107,6 +1118,18 @@ or `the MSDN <http://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Window
|
|||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
.. function:: set_blocking(fd, blocking)
|
||||
|
||||
Set the blocking mode of the specified file descriptor. Set the
|
||||
:data:`O_NONBLOCK` flag if blocking is ``False``, clear the flag otherwise.
|
||||
|
||||
See also :func:`get_blocking` and :meth:`socket.socket.setblocking`.
|
||||
|
||||
Availability: Unix.
|
||||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
|
||||
.. data:: SF_NODISKIO
|
||||
SF_MNOWAIT
|
||||
SF_SYNC
|
||||
|
|
|
@ -70,7 +70,14 @@ PyAPI_FUNC(int) _Py_set_inheritable(int fd, int inheritable,
|
|||
int *atomic_flag_works);
|
||||
|
||||
PyAPI_FUNC(int) _Py_dup(int fd);
|
||||
#endif
|
||||
|
||||
#ifndef MS_WINDOWS
|
||||
PyAPI_FUNC(int) _Py_get_blocking(int fd);
|
||||
|
||||
PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
|
||||
#endif /* !MS_WINDOWS */
|
||||
|
||||
#endif /* Py_LIMITED_API */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""Selector event loop for Unix with signal handling."""
|
||||
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
|
@ -259,12 +258,6 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
return server
|
||||
|
||||
|
||||
def _set_nonblocking(fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
flags = flags | os.O_NONBLOCK
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
||||
|
||||
|
||||
class _UnixReadPipeTransport(transports.ReadTransport):
|
||||
|
||||
max_size = 256 * 1024 # max bytes we read in one event loop iteration
|
||||
|
@ -280,7 +273,7 @@ class _UnixReadPipeTransport(transports.ReadTransport):
|
|||
stat.S_ISSOCK(mode) or
|
||||
stat.S_ISCHR(mode)):
|
||||
raise ValueError("Pipe transport is for pipes/sockets only.")
|
||||
_set_nonblocking(self._fileno)
|
||||
os.set_blocking(self._fileno, False)
|
||||
self._protocol = protocol
|
||||
self._closing = False
|
||||
self._loop.add_reader(self._fileno, self._read_ready)
|
||||
|
@ -373,7 +366,7 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
|
|||
stat.S_ISCHR(mode)):
|
||||
raise ValueError("Pipe transport is only for "
|
||||
"pipes, sockets and character devices")
|
||||
_set_nonblocking(self._fileno)
|
||||
os.set_blocking(self._fileno, False)
|
||||
self._protocol = protocol
|
||||
self._buffer = []
|
||||
self._conn_lost = 0
|
||||
|
|
|
@ -590,8 +590,6 @@ def close_all(map=None, ignore_all=False):
|
|||
# Regardless, this is useful for pipes, and stdin/stdout...
|
||||
|
||||
if os.name == 'posix':
|
||||
import fcntl
|
||||
|
||||
class file_wrapper:
|
||||
# Here we override just enough to make a file
|
||||
# look like a socket for the purposes of asyncore.
|
||||
|
@ -642,9 +640,7 @@ if os.name == 'posix':
|
|||
pass
|
||||
self.set_file(fd)
|
||||
# set it to non-blocking mode
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
|
||||
flags = flags | os.O_NONBLOCK
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
||||
os.set_blocking(fd, False)
|
||||
|
||||
def set_file(self, fd):
|
||||
self.socket = file_wrapper(fd)
|
||||
|
|
|
@ -306,9 +306,9 @@ class UnixReadPipeTransportTests(test_utils.TestCase):
|
|||
self.pipe = mock.Mock(spec_set=io.RawIOBase)
|
||||
self.pipe.fileno.return_value = 5
|
||||
|
||||
fcntl_patcher = mock.patch('fcntl.fcntl')
|
||||
fcntl_patcher.start()
|
||||
self.addCleanup(fcntl_patcher.stop)
|
||||
blocking_patcher = mock.patch('os.set_blocking')
|
||||
blocking_patcher.start()
|
||||
self.addCleanup(blocking_patcher.stop)
|
||||
|
||||
fstat_patcher = mock.patch('os.fstat')
|
||||
m_fstat = fstat_patcher.start()
|
||||
|
@ -469,9 +469,9 @@ class UnixWritePipeTransportTests(test_utils.TestCase):
|
|||
self.pipe = mock.Mock(spec_set=io.RawIOBase)
|
||||
self.pipe.fileno.return_value = 5
|
||||
|
||||
fcntl_patcher = mock.patch('fcntl.fcntl')
|
||||
fcntl_patcher.start()
|
||||
self.addCleanup(fcntl_patcher.stop)
|
||||
blocking_patcher = mock.patch('os.set_blocking')
|
||||
blocking_patcher.start()
|
||||
self.addCleanup(blocking_patcher.stop)
|
||||
|
||||
fstat_patcher = mock.patch('os.fstat')
|
||||
m_fstat = fstat_patcher.start()
|
||||
|
|
|
@ -44,10 +44,6 @@ try:
|
|||
import threading
|
||||
except ImportError:
|
||||
threading = None
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
def _default_chunk_size():
|
||||
"""Get the default TextIOWrapper chunk size"""
|
||||
|
@ -3230,26 +3226,20 @@ class MiscIOTest(unittest.TestCase):
|
|||
with self.open(support.TESTFN, **kwargs) as f:
|
||||
self.assertRaises(TypeError, pickle.dumps, f, protocol)
|
||||
|
||||
@unittest.skipUnless(fcntl, 'fcntl required for this test')
|
||||
def test_nonblock_pipe_write_bigbuf(self):
|
||||
self._test_nonblock_pipe_write(16*1024)
|
||||
|
||||
@unittest.skipUnless(fcntl, 'fcntl required for this test')
|
||||
def test_nonblock_pipe_write_smallbuf(self):
|
||||
self._test_nonblock_pipe_write(1024)
|
||||
|
||||
def _set_non_blocking(self, fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
self.assertNotEqual(flags, -1)
|
||||
res = fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
self.assertEqual(res, 0)
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'set_blocking'),
|
||||
'os.set_blocking() required for this test')
|
||||
def _test_nonblock_pipe_write(self, bufsize):
|
||||
sent = []
|
||||
received = []
|
||||
r, w = os.pipe()
|
||||
self._set_non_blocking(r)
|
||||
self._set_non_blocking(w)
|
||||
os.set_blocking(r, False)
|
||||
os.set_blocking(w, False)
|
||||
|
||||
# To exercise all code paths in the C implementation we need
|
||||
# to play with buffer sizes. For instance, if we choose a
|
||||
|
|
|
@ -1376,6 +1376,16 @@ class TestInvalidFD(unittest.TestCase):
|
|||
def test_writev(self):
|
||||
self.check(os.writev, [b'abc'])
|
||||
|
||||
def test_inheritable(self):
|
||||
self.check(os.get_inheritable)
|
||||
self.check(os.set_inheritable, True)
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
||||
'needs os.get_blocking() and os.set_blocking()')
|
||||
def test_blocking(self):
|
||||
self.check(os.get_blocking)
|
||||
self.check(os.set_blocking, True)
|
||||
|
||||
|
||||
class LinkTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -2591,6 +2601,21 @@ class FDInheritanceTests(unittest.TestCase):
|
|||
self.assertEqual(os.get_inheritable(slave_fd), False)
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
||||
'needs os.get_blocking() and os.set_blocking()')
|
||||
class BlockingTests(unittest.TestCase):
|
||||
def test_blocking(self):
|
||||
fd = os.open(__file__, os.O_RDONLY)
|
||||
self.addCleanup(os.close, fd)
|
||||
self.assertEqual(os.get_blocking(fd), True)
|
||||
|
||||
os.set_blocking(fd, False)
|
||||
self.assertEqual(os.get_blocking(fd), False)
|
||||
|
||||
os.set_blocking(fd, True)
|
||||
self.assertEqual(os.get_blocking(fd), True)
|
||||
|
||||
|
||||
@support.reap_threads
|
||||
def test_main():
|
||||
support.run_unittest(
|
||||
|
@ -2626,6 +2651,7 @@ def test_main():
|
|||
CPUCountTests,
|
||||
FDInheritanceTests,
|
||||
Win32JunctionTests,
|
||||
BlockingTests,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -9,7 +9,6 @@ import errno
|
|||
import sys
|
||||
import time
|
||||
import os
|
||||
import fcntl
|
||||
import platform
|
||||
import pwd
|
||||
import shutil
|
||||
|
@ -355,7 +354,7 @@ class PosixTester(unittest.TestCase):
|
|||
def test_oscloexec(self):
|
||||
fd = os.open(support.TESTFN, os.O_RDONLY|os.O_CLOEXEC)
|
||||
self.addCleanup(os.close, fd)
|
||||
self.assertTrue(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
|
||||
self.assertFalse(os.get_inheritable(fd))
|
||||
|
||||
@unittest.skipUnless(hasattr(posix, 'O_EXLOCK'),
|
||||
'test needs posix.O_EXLOCK')
|
||||
|
@ -605,8 +604,8 @@ class PosixTester(unittest.TestCase):
|
|||
self.addCleanup(os.close, w)
|
||||
self.assertFalse(os.get_inheritable(r))
|
||||
self.assertFalse(os.get_inheritable(w))
|
||||
self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
|
||||
self.assertTrue(fcntl.fcntl(w, fcntl.F_GETFL) & os.O_NONBLOCK)
|
||||
self.assertFalse(os.get_blocking(r))
|
||||
self.assertFalse(os.get_blocking(w))
|
||||
# try reading from an empty pipe: this should fail, not block
|
||||
self.assertRaises(OSError, os.read, r, 1)
|
||||
# try a write big enough to fill-up the pipe: this should either
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from test.support import verbose, run_unittest, import_module, reap_children
|
||||
|
||||
#Skip these tests if either fcntl or termios is not available
|
||||
fcntl = import_module('fcntl')
|
||||
# Skip these tests if termios is not available
|
||||
import_module('termios')
|
||||
|
||||
import errno
|
||||
|
@ -84,16 +83,18 @@ class PtyTest(unittest.TestCase):
|
|||
# in master_open(), we need to read the EOF.
|
||||
|
||||
# Ensure the fd is non-blocking in case there's nothing to read.
|
||||
orig_flags = fcntl.fcntl(master_fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags | os.O_NONBLOCK)
|
||||
blocking = os.get_blocking(master_fd)
|
||||
try:
|
||||
s1 = os.read(master_fd, 1024)
|
||||
self.assertEqual(b'', s1)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EAGAIN:
|
||||
raise
|
||||
# Restore the original flags.
|
||||
fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags)
|
||||
os.set_blocking(master_fd, False)
|
||||
try:
|
||||
s1 = os.read(master_fd, 1024)
|
||||
self.assertEqual(b'', s1)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EAGAIN:
|
||||
raise
|
||||
finally:
|
||||
# Restore the original flags.
|
||||
os.set_blocking(master_fd, blocking)
|
||||
|
||||
debug("Writing to slave_fd")
|
||||
os.write(slave_fd, TEST_STRING_1)
|
||||
|
|
|
@ -276,7 +276,6 @@ class WakeupSignalTests(unittest.TestCase):
|
|||
# use a subprocess to have only one thread
|
||||
code = """if 1:
|
||||
import _testcapi
|
||||
import fcntl
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
|
@ -299,10 +298,7 @@ class WakeupSignalTests(unittest.TestCase):
|
|||
|
||||
signal.signal(signal.SIGALRM, handler)
|
||||
read, write = os.pipe()
|
||||
for fd in (read, write):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
|
||||
flags = flags | os.O_NONBLOCK
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
||||
os.set_blocking(write, False)
|
||||
signal.set_wakeup_fd(write)
|
||||
|
||||
test()
|
||||
|
@ -322,7 +318,6 @@ class WakeupSignalTests(unittest.TestCase):
|
|||
code = """if 1:
|
||||
import _testcapi
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
@ -333,8 +328,7 @@ class WakeupSignalTests(unittest.TestCase):
|
|||
|
||||
signal.signal(signal.SIGALRM, handler)
|
||||
r, w = os.pipe()
|
||||
flags = fcntl.fcntl(r, fcntl.F_GETFL, 0)
|
||||
fcntl.fcntl(r, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
os.set_blocking(r, False)
|
||||
|
||||
# Set wakeup_fd a read-only file descriptor to trigger the error
|
||||
signal.set_wakeup_fd(r)
|
||||
|
|
|
@ -113,6 +113,10 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #22054: Add os.get_blocking() and os.set_blocking() functions to get
|
||||
and set the blocking mode of a file descriptor (False if the O_NONBLOCK flag
|
||||
is set, True otherwise). These functions are not available on Windows.
|
||||
|
||||
- Issue #17172: Make turtledemo start as active on Mac even when run with
|
||||
subprocess. Patch by Ned Daily and Lita Cho.
|
||||
|
||||
|
|
|
@ -11146,6 +11146,56 @@ posix_set_handle_inheritable(PyObject *self, PyObject *args)
|
|||
#endif /* MS_WINDOWS */
|
||||
|
||||
|
||||
#ifndef MS_WINDOWS
|
||||
PyDoc_STRVAR(get_blocking__doc__,
|
||||
"get_blocking(fd) -> bool\n" \
|
||||
"\n" \
|
||||
"Get the blocking mode of the file descriptor:\n" \
|
||||
"False if the O_NONBLOCK flag is set, True if the flag is cleared.");
|
||||
|
||||
static PyObject*
|
||||
posix_get_blocking(PyObject *self, PyObject *args)
|
||||
{
|
||||
int fd;
|
||||
int blocking;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i:get_blocking", &fd))
|
||||
return NULL;
|
||||
|
||||
if (!_PyVerify_fd(fd))
|
||||
return posix_error();
|
||||
|
||||
blocking = _Py_get_blocking(fd);
|
||||
if (blocking < 0)
|
||||
return NULL;
|
||||
return PyBool_FromLong(blocking);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(set_blocking__doc__,
|
||||
"set_blocking(fd, blocking)\n" \
|
||||
"\n" \
|
||||
"Set the blocking mode of the specified file descriptor.\n" \
|
||||
"Set the O_NONBLOCK flag if blocking is False,\n" \
|
||||
"clear the O_NONBLOCK flag otherwise.");
|
||||
|
||||
static PyObject*
|
||||
posix_set_blocking(PyObject *self, PyObject *args)
|
||||
{
|
||||
int fd, blocking;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ii:set_blocking", &fd, &blocking))
|
||||
return NULL;
|
||||
|
||||
if (!_PyVerify_fd(fd))
|
||||
return posix_error();
|
||||
|
||||
if (_Py_set_blocking(fd, blocking) < 0)
|
||||
return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif /* !MS_WINDOWS */
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
dump buffer
|
||||
[clinic start generated code]*/
|
||||
|
@ -11605,6 +11655,10 @@ static PyMethodDef posix_methods[] = {
|
|||
METH_VARARGS, get_handle_inheritable__doc__},
|
||||
{"set_handle_inheritable", posix_set_handle_inheritable,
|
||||
METH_VARARGS, set_handle_inheritable__doc__},
|
||||
#endif
|
||||
#ifndef MS_WINDOWS
|
||||
{"get_blocking", posix_get_blocking, METH_VARARGS, get_blocking__doc__},
|
||||
{"set_blocking", posix_set_blocking, METH_VARARGS, set_blocking__doc__},
|
||||
#endif
|
||||
{NULL, NULL} /* Sentinel */
|
||||
};
|
||||
|
|
|
@ -1045,3 +1045,56 @@ _Py_dup(int fd)
|
|||
return fd;
|
||||
}
|
||||
|
||||
#ifndef MS_WINDOWS
|
||||
/* Get the blocking mode of the file descriptor.
|
||||
Return 0 if the O_NONBLOCK flag is set, 1 if the flag is cleared,
|
||||
raise an exception and return -1 on error. */
|
||||
int
|
||||
_Py_get_blocking(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return !(flags & O_NONBLOCK);
|
||||
}
|
||||
|
||||
/* Set the blocking mode of the specified file descriptor.
|
||||
|
||||
Set the O_NONBLOCK flag if blocking is False, clear the O_NONBLOCK flag
|
||||
otherwise.
|
||||
|
||||
Return 0 on success, raise an exception and return -1 on error. */
|
||||
int
|
||||
_Py_set_blocking(int fd, int blocking)
|
||||
{
|
||||
#if defined(HAVE_SYS_IOCTL_H) && defined(FIONBIO)
|
||||
int arg = !blocking;
|
||||
if (ioctl(fd, FIONBIO, &arg) < 0)
|
||||
goto error;
|
||||
#else
|
||||
int flags, res;
|
||||
|
||||
flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
goto error;
|
||||
|
||||
if (blocking)
|
||||
flags = flags & (~O_NONBLOCK);
|
||||
else
|
||||
flags = flags | O_NONBLOCK;
|
||||
|
||||
res = fcntl(fd, F_SETFL, flags);
|
||||
if (res < 0)
|
||||
goto error;
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in New Issue