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:
Victor Stinner 2014-07-29 22:32:47 +02:00
parent 6aa4269ed2
commit 1db9e7bb19
13 changed files with 198 additions and 58 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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__":

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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 */
};

View File

@ -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