aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h1
-rw-r--r--Include/internal/pycore_global_strings.h1
-rw-r--r--Include/internal/pycore_runtime_init_generated.h1
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h4
-rw-r--r--Lib/_pyio.py17
-rw-r--r--Misc/NEWS.d/next/Library/2024-10-02-22-53-48.gh-issue-90102.4qX52R.rst3
-rw-r--r--Modules/_io/_iomodule.c2
-rw-r--r--Modules/_io/fileio.c22
-rw-r--r--Modules/_io/winconsoleio.c1
9 files changed, 47 insertions, 5 deletions
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index 28a76c36801..3140a75a47c 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -757,6 +757,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_initializing));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_io));
_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(_limbo));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_lock_unlock_module));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index ac789b06fb8..1591cb0a3f1 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -246,6 +246,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_initializing)
STRUCT_FOR_ID(_io)
STRUCT_FOR_ID(_is_text_encoding)
+ STRUCT_FOR_ID(_isatty_open_only)
STRUCT_FOR_ID(_length_)
STRUCT_FOR_ID(_limbo)
STRUCT_FOR_ID(_lock_unlock_module)
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index 7847a5c63eb..c9d20d0b5aa 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -755,6 +755,7 @@ extern "C" {
INIT_ID(_initializing), \
INIT_ID(_io), \
INIT_ID(_is_text_encoding), \
+ INIT_ID(_isatty_open_only), \
INIT_ID(_length_), \
INIT_ID(_limbo), \
INIT_ID(_lock_unlock_module), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index a688f70a2ba..d335373e88e 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -784,6 +784,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(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_);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 18849b309b8..2a1d2a33d02 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -238,7 +238,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
result = raw
try:
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
line_buffering = True
if buffering < 0:
@@ -1794,6 +1794,21 @@ class FileIO(RawIOBase):
self._checkClosed()
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
def closefd(self):
"""True if the file descriptor will be closed by close()."""
diff --git a/Misc/NEWS.d/next/Library/2024-10-02-22-53-48.gh-issue-90102.4qX52R.rst b/Misc/NEWS.d/next/Library/2024-10-02-22-53-48.gh-issue-90102.4qX52R.rst
new file mode 100644
index 00000000000..0e708ed11d2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-02-22-53-48.gh-issue-90102.4qX52R.rst
@@ -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.
diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c
index 1238e607424..6622f2cabb9 100644
--- a/Modules/_io/_iomodule.c
+++ b/Modules/_io/_iomodule.c
@@ -346,7 +346,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
/* buffering */
if (buffering < 0) {
- PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(isatty));
+ PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(_isatty_open_only));
if (res == NULL)
goto error;
isatty = PyObject_IsTrue(res);
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index d9597f860b9..f374592eb95 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -12,9 +12,6 @@
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
-#ifdef HAVE_SYS_STAT_H
-# include <sys/stat.h>
-#endif
#ifdef HAVE_IO_H
# include <io.h>
#endif
@@ -1218,6 +1215,24 @@ _io_FileIO_isatty_impl(fileio *self)
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"
static PyMethodDef fileio_methods[] = {
@@ -1234,6 +1249,7 @@ static PyMethodDef fileio_methods[] = {
_IO_FILEIO_WRITABLE_METHODDEF
_IO_FILEIO_FILENO_METHODDEF
_IO_FILEIO_ISATTY_METHODDEF
+ {"_isatty_open_only", _io_FileIO_isatty_open_only, METH_NOARGS},
{"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL},
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c
index ec5c298066a..d7cb5abfdc0 100644
--- a/Modules/_io/winconsoleio.c
+++ b/Modules/_io/winconsoleio.c
@@ -1128,6 +1128,7 @@ static PyMethodDef winconsoleio_methods[] = {
_IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
_IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
+ {"_isatty_open_only", (PyCFunction)_io__WindowsConsoleIO_isatty, METH_NOARGS},
{NULL, NULL} /* sentinel */
};