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. 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) .. function:: isatty(fd)
Return ``True`` if the file descriptor *fd* is open and connected to a 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 .. 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 .. data:: SF_NODISKIO
SF_MNOWAIT SF_MNOWAIT
SF_SYNC SF_SYNC

View File

@ -70,7 +70,14 @@ PyAPI_FUNC(int) _Py_set_inheritable(int fd, int inheritable,
int *atomic_flag_works); int *atomic_flag_works);
PyAPI_FUNC(int) _Py_dup(int fd); 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 #ifdef __cplusplus
} }

View File

@ -1,7 +1,6 @@
"""Selector event loop for Unix with signal handling.""" """Selector event loop for Unix with signal handling."""
import errno import errno
import fcntl
import os import os
import signal import signal
import socket import socket
@ -259,12 +258,6 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
return server 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): class _UnixReadPipeTransport(transports.ReadTransport):
max_size = 256 * 1024 # max bytes we read in one event loop iteration 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_ISSOCK(mode) or
stat.S_ISCHR(mode)): stat.S_ISCHR(mode)):
raise ValueError("Pipe transport is for pipes/sockets only.") raise ValueError("Pipe transport is for pipes/sockets only.")
_set_nonblocking(self._fileno) os.set_blocking(self._fileno, False)
self._protocol = protocol self._protocol = protocol
self._closing = False self._closing = False
self._loop.add_reader(self._fileno, self._read_ready) self._loop.add_reader(self._fileno, self._read_ready)
@ -373,7 +366,7 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
stat.S_ISCHR(mode)): stat.S_ISCHR(mode)):
raise ValueError("Pipe transport is only for " raise ValueError("Pipe transport is only for "
"pipes, sockets and character devices") "pipes, sockets and character devices")
_set_nonblocking(self._fileno) os.set_blocking(self._fileno, False)
self._protocol = protocol self._protocol = protocol
self._buffer = [] self._buffer = []
self._conn_lost = 0 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... # Regardless, this is useful for pipes, and stdin/stdout...
if os.name == 'posix': if os.name == 'posix':
import fcntl
class file_wrapper: class file_wrapper:
# Here we override just enough to make a file # Here we override just enough to make a file
# look like a socket for the purposes of asyncore. # look like a socket for the purposes of asyncore.
@ -642,9 +640,7 @@ if os.name == 'posix':
pass pass
self.set_file(fd) self.set_file(fd)
# set it to non-blocking mode # set it to non-blocking mode
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) os.set_blocking(fd, False)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def set_file(self, fd): def set_file(self, fd):
self.socket = file_wrapper(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 = mock.Mock(spec_set=io.RawIOBase)
self.pipe.fileno.return_value = 5 self.pipe.fileno.return_value = 5
fcntl_patcher = mock.patch('fcntl.fcntl') blocking_patcher = mock.patch('os.set_blocking')
fcntl_patcher.start() blocking_patcher.start()
self.addCleanup(fcntl_patcher.stop) self.addCleanup(blocking_patcher.stop)
fstat_patcher = mock.patch('os.fstat') fstat_patcher = mock.patch('os.fstat')
m_fstat = fstat_patcher.start() m_fstat = fstat_patcher.start()
@ -469,9 +469,9 @@ class UnixWritePipeTransportTests(test_utils.TestCase):
self.pipe = mock.Mock(spec_set=io.RawIOBase) self.pipe = mock.Mock(spec_set=io.RawIOBase)
self.pipe.fileno.return_value = 5 self.pipe.fileno.return_value = 5
fcntl_patcher = mock.patch('fcntl.fcntl') blocking_patcher = mock.patch('os.set_blocking')
fcntl_patcher.start() blocking_patcher.start()
self.addCleanup(fcntl_patcher.stop) self.addCleanup(blocking_patcher.stop)
fstat_patcher = mock.patch('os.fstat') fstat_patcher = mock.patch('os.fstat')
m_fstat = fstat_patcher.start() m_fstat = fstat_patcher.start()

View File

@ -44,10 +44,6 @@ try:
import threading import threading
except ImportError: except ImportError:
threading = None threading = None
try:
import fcntl
except ImportError:
fcntl = None
def _default_chunk_size(): def _default_chunk_size():
"""Get the default TextIOWrapper chunk size""" """Get the default TextIOWrapper chunk size"""
@ -3230,26 +3226,20 @@ class MiscIOTest(unittest.TestCase):
with self.open(support.TESTFN, **kwargs) as f: with self.open(support.TESTFN, **kwargs) as f:
self.assertRaises(TypeError, pickle.dumps, f, protocol) self.assertRaises(TypeError, pickle.dumps, f, protocol)
@unittest.skipUnless(fcntl, 'fcntl required for this test')
def test_nonblock_pipe_write_bigbuf(self): def test_nonblock_pipe_write_bigbuf(self):
self._test_nonblock_pipe_write(16*1024) self._test_nonblock_pipe_write(16*1024)
@unittest.skipUnless(fcntl, 'fcntl required for this test')
def test_nonblock_pipe_write_smallbuf(self): def test_nonblock_pipe_write_smallbuf(self):
self._test_nonblock_pipe_write(1024) self._test_nonblock_pipe_write(1024)
def _set_non_blocking(self, fd): @unittest.skipUnless(hasattr(os, 'set_blocking'),
flags = fcntl.fcntl(fd, fcntl.F_GETFL) 'os.set_blocking() required for this test')
self.assertNotEqual(flags, -1)
res = fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.assertEqual(res, 0)
def _test_nonblock_pipe_write(self, bufsize): def _test_nonblock_pipe_write(self, bufsize):
sent = [] sent = []
received = [] received = []
r, w = os.pipe() r, w = os.pipe()
self._set_non_blocking(r) os.set_blocking(r, False)
self._set_non_blocking(w) os.set_blocking(w, False)
# To exercise all code paths in the C implementation we need # To exercise all code paths in the C implementation we need
# to play with buffer sizes. For instance, if we choose a # 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): def test_writev(self):
self.check(os.writev, [b'abc']) 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): class LinkTests(unittest.TestCase):
def setUp(self): def setUp(self):
@ -2591,6 +2601,21 @@ class FDInheritanceTests(unittest.TestCase):
self.assertEqual(os.get_inheritable(slave_fd), False) 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 @support.reap_threads
def test_main(): def test_main():
support.run_unittest( support.run_unittest(
@ -2626,6 +2651,7 @@ def test_main():
CPUCountTests, CPUCountTests,
FDInheritanceTests, FDInheritanceTests,
Win32JunctionTests, Win32JunctionTests,
BlockingTests,
) )
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -9,7 +9,6 @@ import errno
import sys import sys
import time import time
import os import os
import fcntl
import platform import platform
import pwd import pwd
import shutil import shutil
@ -355,7 +354,7 @@ class PosixTester(unittest.TestCase):
def test_oscloexec(self): def test_oscloexec(self):
fd = os.open(support.TESTFN, os.O_RDONLY|os.O_CLOEXEC) fd = os.open(support.TESTFN, os.O_RDONLY|os.O_CLOEXEC)
self.addCleanup(os.close, fd) 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'), @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'),
'test needs posix.O_EXLOCK') 'test needs posix.O_EXLOCK')
@ -605,8 +604,8 @@ class PosixTester(unittest.TestCase):
self.addCleanup(os.close, w) self.addCleanup(os.close, w)
self.assertFalse(os.get_inheritable(r)) self.assertFalse(os.get_inheritable(r))
self.assertFalse(os.get_inheritable(w)) self.assertFalse(os.get_inheritable(w))
self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK) self.assertFalse(os.get_blocking(r))
self.assertTrue(fcntl.fcntl(w, fcntl.F_GETFL) & os.O_NONBLOCK) self.assertFalse(os.get_blocking(w))
# try reading from an empty pipe: this should fail, not block # try reading from an empty pipe: this should fail, not block
self.assertRaises(OSError, os.read, r, 1) self.assertRaises(OSError, os.read, r, 1)
# try a write big enough to fill-up the pipe: this should either # 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 from test.support import verbose, run_unittest, import_module, reap_children
#Skip these tests if either fcntl or termios is not available # Skip these tests if termios is not available
fcntl = import_module('fcntl')
import_module('termios') import_module('termios')
import errno import errno
@ -84,16 +83,18 @@ class PtyTest(unittest.TestCase):
# in master_open(), we need to read the EOF. # in master_open(), we need to read the EOF.
# Ensure the fd is non-blocking in case there's nothing to read. # Ensure the fd is non-blocking in case there's nothing to read.
orig_flags = fcntl.fcntl(master_fd, fcntl.F_GETFL) blocking = os.get_blocking(master_fd)
fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags | os.O_NONBLOCK)
try: try:
s1 = os.read(master_fd, 1024) os.set_blocking(master_fd, False)
self.assertEqual(b'', s1) try:
except OSError as e: s1 = os.read(master_fd, 1024)
if e.errno != errno.EAGAIN: self.assertEqual(b'', s1)
raise except OSError as e:
# Restore the original flags. if e.errno != errno.EAGAIN:
fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags) raise
finally:
# Restore the original flags.
os.set_blocking(master_fd, blocking)
debug("Writing to slave_fd") debug("Writing to slave_fd")
os.write(slave_fd, TEST_STRING_1) 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 # use a subprocess to have only one thread
code = """if 1: code = """if 1:
import _testcapi import _testcapi
import fcntl
import os import os
import signal import signal
import struct import struct
@ -299,10 +298,7 @@ class WakeupSignalTests(unittest.TestCase):
signal.signal(signal.SIGALRM, handler) signal.signal(signal.SIGALRM, handler)
read, write = os.pipe() read, write = os.pipe()
for fd in (read, write): os.set_blocking(write, False)
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
signal.set_wakeup_fd(write) signal.set_wakeup_fd(write)
test() test()
@ -322,7 +318,6 @@ class WakeupSignalTests(unittest.TestCase):
code = """if 1: code = """if 1:
import _testcapi import _testcapi
import errno import errno
import fcntl
import os import os
import signal import signal
import sys import sys
@ -333,8 +328,7 @@ class WakeupSignalTests(unittest.TestCase):
signal.signal(signal.SIGALRM, handler) signal.signal(signal.SIGALRM, handler)
r, w = os.pipe() r, w = os.pipe()
flags = fcntl.fcntl(r, fcntl.F_GETFL, 0) os.set_blocking(r, False)
fcntl.fcntl(r, fcntl.F_SETFL, flags | os.O_NONBLOCK)
# Set wakeup_fd a read-only file descriptor to trigger the error # Set wakeup_fd a read-only file descriptor to trigger the error
signal.set_wakeup_fd(r) signal.set_wakeup_fd(r)

View File

@ -113,6 +113,10 @@ Core and Builtins
Library 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 - Issue #17172: Make turtledemo start as active on Mac even when run with
subprocess. Patch by Ned Daily and Lita Cho. 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 */ #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] /*[clinic input]
dump buffer dump buffer
[clinic start generated code]*/ [clinic start generated code]*/
@ -11605,6 +11655,10 @@ static PyMethodDef posix_methods[] = {
METH_VARARGS, get_handle_inheritable__doc__}, METH_VARARGS, get_handle_inheritable__doc__},
{"set_handle_inheritable", posix_set_handle_inheritable, {"set_handle_inheritable", posix_set_handle_inheritable,
METH_VARARGS, set_handle_inheritable__doc__}, 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 #endif
{NULL, NULL} /* Sentinel */ {NULL, NULL} /* Sentinel */
}; };

View File

@ -1045,3 +1045,56 @@ _Py_dup(int fd)
return 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