Skip to content

Commit cc9b9be

Browse files
cmaloneyvstinner
andauthored
gh-90102: Remove isatty call during regular open (#124922)
Co-authored-by: Victor Stinner <[email protected]>
1 parent 6e3c70c commit cc9b9be

9 files changed

+47
-5
lines changed

Include/internal/pycore_global_objects_fini_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

+1
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ struct _Py_global_strings {
246246
STRUCT_FOR_ID(_initializing)
247247
STRUCT_FOR_ID(_io)
248248
STRUCT_FOR_ID(_is_text_encoding)
249+
STRUCT_FOR_ID(_isatty_open_only)
249250
STRUCT_FOR_ID(_length_)
250251
STRUCT_FOR_ID(_limbo)
251252
STRUCT_FOR_ID(_lock_unlock_module)

Include/internal/pycore_runtime_init_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_pyio.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
238238
result = raw
239239
try:
240240
line_buffering = False
241-
if buffering == 1 or buffering < 0 and raw.isatty():
241+
if buffering == 1 or buffering < 0 and raw._isatty_open_only():
242242
buffering = -1
243243
line_buffering = True
244244
if buffering < 0:
@@ -1794,6 +1794,21 @@ def isatty(self):
17941794
self._checkClosed()
17951795
return os.isatty(self._fd)
17961796

1797+
def _isatty_open_only(self):
1798+
"""Checks whether the file is a TTY using an open-only optimization.
1799+
1800+
TTYs are always character devices. If the interpreter knows a file is
1801+
not a character device when it would call ``isatty``, can skip that
1802+
call. Inside ``open()`` there is a fresh stat result that contains that
1803+
information. Use the stat result to skip a system call. Outside of that
1804+
context TOCTOU issues (the fd could be arbitrarily modified by
1805+
surrounding code).
1806+
"""
1807+
if (self._stat_atopen is not None
1808+
and not stat.S_ISCHR(self._stat_atopen.st_mode)):
1809+
return True
1810+
return os.isatty(self._fd)
1811+
17971812
@property
17981813
def closefd(self):
17991814
"""True if the file descriptor will be closed by close()."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Skip the ``isatty`` system call during open() when the file is known to not
2+
be a character device. This provides a slight performance improvement when
3+
reading whole files.

Modules/_io/_iomodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
346346

347347
/* buffering */
348348
if (buffering < 0) {
349-
PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(isatty));
349+
PyObject *res = PyObject_CallMethodNoArgs(raw, &_Py_ID(_isatty_open_only));
350350
if (res == NULL)
351351
goto error;
352352
isatty = PyObject_IsTrue(res);

Modules/_io/fileio.c

+19-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
#ifdef HAVE_SYS_TYPES_H
1313
# include <sys/types.h>
1414
#endif
15-
#ifdef HAVE_SYS_STAT_H
16-
# include <sys/stat.h>
17-
#endif
1815
#ifdef HAVE_IO_H
1916
# include <io.h>
2017
#endif
@@ -1218,6 +1215,24 @@ _io_FileIO_isatty_impl(fileio *self)
12181215
return PyBool_FromLong(res);
12191216
}
12201217

1218+
/* Checks whether the file is a TTY using an open-only optimization.
1219+
1220+
TTYs are always character devices. If the interpreter knows a file is
1221+
not a character device when it would call ``isatty``, can skip that
1222+
call. Inside ``open()`` there is a fresh stat result that contains that
1223+
information. Use the stat result to skip a system call. Outside of that
1224+
context TOCTOU issues (the fd could be arbitrarily modified by
1225+
surrounding code). */
1226+
static PyObject *
1227+
_io_FileIO_isatty_open_only(PyObject *op, PyObject *Py_UNUSED(ignored))
1228+
{
1229+
fileio *self = _PyFileIO_CAST(op);
1230+
if (self->stat_atopen != NULL && !S_ISCHR(self->stat_atopen->st_mode)) {
1231+
Py_RETURN_FALSE;
1232+
}
1233+
return _io_FileIO_isatty_impl(self);
1234+
}
1235+
12211236
#include "clinic/fileio.c.h"
12221237

12231238
static PyMethodDef fileio_methods[] = {
@@ -1234,6 +1249,7 @@ static PyMethodDef fileio_methods[] = {
12341249
_IO_FILEIO_WRITABLE_METHODDEF
12351250
_IO_FILEIO_FILENO_METHODDEF
12361251
_IO_FILEIO_ISATTY_METHODDEF
1252+
{"_isatty_open_only", _io_FileIO_isatty_open_only, METH_NOARGS},
12371253
{"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL},
12381254
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
12391255
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},

Modules/_io/winconsoleio.c

+1
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,7 @@ static PyMethodDef winconsoleio_methods[] = {
11281128
_IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
11291129
_IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
11301130
_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
1131+
{"_isatty_open_only", (PyCFunction)_io__WindowsConsoleIO_isatty, METH_NOARGS},
11311132
{NULL, NULL} /* sentinel */
11321133
};
11331134

0 commit comments

Comments
 (0)