gh-90102: Remove isatty call during regular open (#124922)

Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Cody Maloney 2024-10-07 23:50:42 -07:00 committed by GitHub
parent 6e3c70c61b
commit cc9b9bebb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 47 additions and 5 deletions

View File

@ -757,6 +757,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_initializing)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_initializing));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_io)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_io));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_is_text_encoding)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_is_text_encoding));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_isatty_open_only));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_length_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_length_));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_limbo)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_limbo));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_lock_unlock_module)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_lock_unlock_module));

View File

@ -246,6 +246,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_initializing) STRUCT_FOR_ID(_initializing)
STRUCT_FOR_ID(_io) STRUCT_FOR_ID(_io)
STRUCT_FOR_ID(_is_text_encoding) STRUCT_FOR_ID(_is_text_encoding)
STRUCT_FOR_ID(_isatty_open_only)
STRUCT_FOR_ID(_length_) STRUCT_FOR_ID(_length_)
STRUCT_FOR_ID(_limbo) STRUCT_FOR_ID(_limbo)
STRUCT_FOR_ID(_lock_unlock_module) STRUCT_FOR_ID(_lock_unlock_module)

View File

@ -755,6 +755,7 @@ extern "C" {
INIT_ID(_initializing), \ INIT_ID(_initializing), \
INIT_ID(_io), \ INIT_ID(_io), \
INIT_ID(_is_text_encoding), \ INIT_ID(_is_text_encoding), \
INIT_ID(_isatty_open_only), \
INIT_ID(_length_), \ INIT_ID(_length_), \
INIT_ID(_limbo), \ INIT_ID(_limbo), \
INIT_ID(_lock_unlock_module), \ INIT_ID(_lock_unlock_module), \

View File

@ -784,6 +784,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1); assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(_isatty_open_only);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(_length_); string = &_Py_ID(_length_);
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));

View File

@ -238,7 +238,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
result = raw result = raw
try: try:
line_buffering = False line_buffering = False
if buffering == 1 or buffering < 0 and raw.isatty(): if buffering == 1 or buffering < 0 and raw._isatty_open_only():
buffering = -1 buffering = -1
line_buffering = True line_buffering = True
if buffering < 0: if buffering < 0:
@ -1794,6 +1794,21 @@ class FileIO(RawIOBase):
self._checkClosed() self._checkClosed()
return os.isatty(self._fd) return os.isatty(self._fd)
def _isatty_open_only(self):
"""Checks whether the file is a TTY using an open-only optimization.
TTYs are always character devices. If the interpreter knows a file is
not a character device when it would call ``isatty``, can skip that
call. Inside ``open()`` there is a fresh stat result that contains that
information. Use the stat result to skip a system call. Outside of that
context TOCTOU issues (the fd could be arbitrarily modified by
surrounding code).
"""
if (self._stat_atopen is not None
and not stat.S_ISCHR(self._stat_atopen.st_mode)):
return True
return os.isatty(self._fd)
@property @property
def closefd(self): def closefd(self):
"""True if the file descriptor will be closed by close().""" """True if the file descriptor will be closed by close()."""

View File

@ -0,0 +1,3 @@
Skip the ``isatty`` system call during open() when the file is known to not
be a character device. This provides a slight performance improvement when
reading whole files.

View File

@ -346,7 +346,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
/* buffering */ /* buffering */
if (buffering < 0) { if (buffering < 0) {
PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(isatty)); PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(_isatty_open_only));
if (res == NULL) if (res == NULL)
goto error; goto error;
isatty = PyObject_IsTrue(res); isatty = PyObject_IsTrue(res);

View File

@ -12,9 +12,6 @@
#ifdef HAVE_SYS_TYPES_H #ifdef HAVE_SYS_TYPES_H
# include <sys/types.h> # include <sys/types.h>
#endif #endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_IO_H #ifdef HAVE_IO_H
# include <io.h> # include <io.h>
#endif #endif
@ -1218,6 +1215,24 @@ _io_FileIO_isatty_impl(fileio *self)
return PyBool_FromLong(res); return PyBool_FromLong(res);
} }
/* Checks whether the file is a TTY using an open-only optimization.
TTYs are always character devices. If the interpreter knows a file is
not a character device when it would call ``isatty``, can skip that
call. Inside ``open()`` there is a fresh stat result that contains that
information. Use the stat result to skip a system call. Outside of that
context TOCTOU issues (the fd could be arbitrarily modified by
surrounding code). */
static PyObject *
_io_FileIO_isatty_open_only(PyObject *op, PyObject *Py_UNUSED(ignored))
{
fileio *self = _PyFileIO_CAST(op);
if (self->stat_atopen != NULL && !S_ISCHR(self->stat_atopen->st_mode)) {
Py_RETURN_FALSE;
}
return _io_FileIO_isatty_impl(self);
}
#include "clinic/fileio.c.h" #include "clinic/fileio.c.h"
static PyMethodDef fileio_methods[] = { static PyMethodDef fileio_methods[] = {
@ -1234,6 +1249,7 @@ static PyMethodDef fileio_methods[] = {
_IO_FILEIO_WRITABLE_METHODDEF _IO_FILEIO_WRITABLE_METHODDEF
_IO_FILEIO_FILENO_METHODDEF _IO_FILEIO_FILENO_METHODDEF
_IO_FILEIO_ISATTY_METHODDEF _IO_FILEIO_ISATTY_METHODDEF
{"_isatty_open_only", _io_FileIO_isatty_open_only, METH_NOARGS},
{"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL}, {"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL},
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},

View File

@ -1128,6 +1128,7 @@ static PyMethodDef winconsoleio_methods[] = {
_IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF _IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
_IO__WINDOWSCONSOLEIO_FILENO_METHODDEF _IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
{"_isatty_open_only", (PyCFunction)_io__WindowsConsoleIO_isatty, METH_NOARGS},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };