Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Include/internal/pycore_exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ extern void _PyExc_Fini(PyInterpreterState *);
struct _Py_exc_state {
// The dict mapping from errno codes to OSError subclasses
PyObject *errnomap;
// errno code -> _Py_strerror() message
PyObject *strerror_cache;
PyBaseExceptionObject *memerrors_freelist;
int memerrors_numfree;
#ifdef Py_GIL_DISABLED
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_exception_hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import unittest
import errno
from errno import EEXIST
from test.support import cpython_only


class SubOSError(OSError):
Expand Down Expand Up @@ -136,6 +137,16 @@ def test_posix_error(self):
if os.name == "nt":
self.assertEqual(e.winerror, None)

@cpython_only
@unittest.skipIf(os.name == "nt", "POSIX-specific test")
def test_strerror_cached(self):
def strerror():
with self.assertRaises(FileNotFoundError) as cm:
open("some_hopefully_non_existing_file")
return cm.exception.strerror
self.assertIs(strerror(), strerror())
self.assertIs(strerror(), os.strerror(errno.ENOENT))

@unittest.skipUnless(os.name == "nt", "Windows-specific test")
def test_errno_translation(self):
# ERROR_ALREADY_EXISTS (183) -> EEXIST
Expand Down
3 changes: 3 additions & 0 deletions Modules/_localemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This software comes with no warranty. Use at your own risk.
#include "Python.h"
#include "pycore_fileutils.h" // _Py_GetLocaleconvNumeric()
#include "pycore_pymem.h" // _PyMem_Strdup()
#include "pycore_pystate.h" // _PyInterpreterState_GET()

#include <locale.h> // setlocale()
#include <string.h> // strlen()
Expand Down Expand Up @@ -169,6 +170,8 @@ _locale_setlocale_impl(PyObject *module, int category, const char *locale)
"unsupported locale setting");
return NULL;
}
// strerror() messages depend on LC_MESSAGES and LC_CTYPE
PyDict_Clear(_PyInterpreterState_GET()->exc_state.strerror_cache);
result_object = PyUnicode_DecodeLocale(result, NULL);
if (!result_object)
return NULL;
Expand Down
4 changes: 3 additions & 1 deletion Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -4608,7 +4608,8 @@ _PyExc_InitState(PyInterpreterState *interp)
/* Add exceptions to errnomap */
assert(state->errnomap == NULL);
state->errnomap = PyDict_New();
if (!state->errnomap) {
state->strerror_cache = PyDict_New();
if (!state->errnomap || !state->strerror_cache) {
return _PyStatus_NO_MEMORY();
}

Expand Down Expand Up @@ -4699,6 +4700,7 @@ _PyExc_Fini(PyInterpreterState *interp)
struct _Py_exc_state *state = &interp->exc_state;
free_preallocated_memerrors(state);
Py_CLEAR(state->errnomap);
Py_CLEAR(state->strerror_cache);

_PyExc_FiniTypes(interp);
}
Expand Down
36 changes: 29 additions & 7 deletions Python/fileutils.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Python.h"
#include "pycore_fileutils.h" // fileutils definitions
#include "pycore_long.h" // _PyLong_FromUnsignedChar()
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_pystate.h" // _Py_AssertHoldsTstate()
#include "pycore_unicodeobject.h" // _Py_MAX_UNICODE
Expand Down Expand Up @@ -3304,13 +3305,8 @@ _Py_IsValidFD(int fd)
}


// Call strerror_r(code) if available, or use strerror() otherwise. Decode the
// result from the locale encoding using surrogateescape error handler.
//
// On success, return a Unicode string. On error, set an exception and return
// NULL.
PyObject*
_Py_strerror(int code)
static PyObject *
decode_strerror(int code)
/*[clinic end generated code: output=baebf09fa02a78f2 input=75a8673d97915a91]*/
{
const char *errors = "surrogateescape";
Expand Down Expand Up @@ -3396,3 +3392,29 @@ _Py_strerror(int code)
return PyUnicode_DecodeLocale(message, errors);
#endif
}


// Call strerror_r(code) if available, or use strerror() otherwise. Decode the
// result from the locale encoding using surrogateescape error handler.
// Messages of codes 0-255 are cached until the next locale.setlocale():
// decoding is a large part of the cost of raising OSError.
//
// On success, return a Unicode string. On error, set an exception and return
// NULL.
PyObject*
_Py_strerror(int code)
{
PyObject *cache = _PyInterpreterState_GET()->exc_state.strerror_cache;
if (cache == NULL || code < 0 || code >= 256) {
return decode_strerror(code);
}
PyObject *key = _PyLong_FromUnsignedChar((unsigned char)code);
PyObject *message;
if (PyDict_GetItemRef(cache, key, &message) == 0) {
message = decode_strerror(code);
if (message != NULL && PyDict_SetItem(cache, key, message) < 0) {
Py_CLEAR(message);
}
}
return message;
}
Loading