mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #20037: Avoid crashes when doing text I/O late at interpreter shutdown.
This commit is contained in:
parent
5255b86fba
commit
712cb734bd
9 changed files with 96 additions and 8 deletions
|
@ -36,6 +36,7 @@ import _testcapi
|
||||||
from collections import deque, UserList
|
from collections import deque, UserList
|
||||||
from itertools import cycle, count
|
from itertools import cycle, count
|
||||||
from test import support
|
from test import support
|
||||||
|
from test.script_helper import assert_python_ok
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
import io # C implementation of io
|
import io # C implementation of io
|
||||||
|
@ -2589,8 +2590,46 @@ class TextIOWrapperTest(unittest.TestCase):
|
||||||
encoding='quopri_codec')
|
encoding='quopri_codec')
|
||||||
self.assertRaises(TypeError, t.read)
|
self.assertRaises(TypeError, t.read)
|
||||||
|
|
||||||
|
def _check_create_at_shutdown(self, **kwargs):
|
||||||
|
# Issue #20037: creating a TextIOWrapper at shutdown
|
||||||
|
# shouldn't crash the interpreter.
|
||||||
|
iomod = self.io.__name__
|
||||||
|
code = """if 1:
|
||||||
|
import codecs
|
||||||
|
import {iomod} as io
|
||||||
|
|
||||||
|
# Avoid looking up codecs at shutdown
|
||||||
|
codecs.lookup('utf-8')
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def __init__(self):
|
||||||
|
self.buf = io.BytesIO()
|
||||||
|
def __del__(self):
|
||||||
|
io.TextIOWrapper(self.buf, **{kwargs})
|
||||||
|
print("ok")
|
||||||
|
c = C()
|
||||||
|
""".format(iomod=iomod, kwargs=kwargs)
|
||||||
|
return assert_python_ok("-c", code)
|
||||||
|
|
||||||
|
def test_create_at_shutdown_without_encoding(self):
|
||||||
|
rc, out, err = self._check_create_at_shutdown()
|
||||||
|
if err:
|
||||||
|
# Can error out with a RuntimeError if the module state
|
||||||
|
# isn't found.
|
||||||
|
self.assertIn("RuntimeError: could not find io module state",
|
||||||
|
err.decode())
|
||||||
|
else:
|
||||||
|
self.assertEqual("ok", out.decode().strip())
|
||||||
|
|
||||||
|
def test_create_at_shutdown_with_encoding(self):
|
||||||
|
rc, out, err = self._check_create_at_shutdown(encoding='utf-8',
|
||||||
|
errors='strict')
|
||||||
|
self.assertFalse(err)
|
||||||
|
self.assertEqual("ok", out.decode().strip())
|
||||||
|
|
||||||
|
|
||||||
class CTextIOWrapperTest(TextIOWrapperTest):
|
class CTextIOWrapperTest(TextIOWrapperTest):
|
||||||
|
io = io
|
||||||
|
|
||||||
def test_initialization(self):
|
def test_initialization(self):
|
||||||
r = self.BytesIO(b"\xc3\xa9\n\n")
|
r = self.BytesIO(b"\xc3\xa9\n\n")
|
||||||
|
@ -2634,7 +2673,7 @@ class CTextIOWrapperTest(TextIOWrapperTest):
|
||||||
|
|
||||||
|
|
||||||
class PyTextIOWrapperTest(TextIOWrapperTest):
|
class PyTextIOWrapperTest(TextIOWrapperTest):
|
||||||
pass
|
io = pyio
|
||||||
|
|
||||||
|
|
||||||
class IncrementalNewlineDecoderTest(unittest.TestCase):
|
class IncrementalNewlineDecoderTest(unittest.TestCase):
|
||||||
|
|
|
@ -41,6 +41,7 @@ import socket
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from test.script_helper import assert_python_ok
|
||||||
from test.support import (captured_stdout, run_with_locale, run_unittest,
|
from test.support import (captured_stdout, run_with_locale, run_unittest,
|
||||||
patch, requires_zlib, TestHandler, Matcher)
|
patch, requires_zlib, TestHandler, Matcher)
|
||||||
import textwrap
|
import textwrap
|
||||||
|
@ -3397,6 +3398,25 @@ class ModuleLevelMiscTest(BaseTest):
|
||||||
logging.setLoggerClass(logging.Logger)
|
logging.setLoggerClass(logging.Logger)
|
||||||
self.assertEqual(logging.getLoggerClass(), logging.Logger)
|
self.assertEqual(logging.getLoggerClass(), logging.Logger)
|
||||||
|
|
||||||
|
def test_logging_at_shutdown(self):
|
||||||
|
# Issue #20037
|
||||||
|
code = """if 1:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
raise ValueError("some error")
|
||||||
|
except Exception:
|
||||||
|
logging.exception("exception in __del__")
|
||||||
|
|
||||||
|
a = A()"""
|
||||||
|
rc, out, err = assert_python_ok("-c", code)
|
||||||
|
err = err.decode()
|
||||||
|
self.assertIn("exception in __del__", err)
|
||||||
|
self.assertIn("ValueError: some error", err)
|
||||||
|
|
||||||
|
|
||||||
class LogRecordTest(BaseTest):
|
class LogRecordTest(BaseTest):
|
||||||
def test_str_rep(self):
|
def test_str_rep(self):
|
||||||
r = logging.makeLogRecord({})
|
r = logging.makeLogRecord({})
|
||||||
|
|
|
@ -44,6 +44,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #20037: Avoid crashes when opening a text file late at interpreter
|
||||||
|
shutdown.
|
||||||
|
|
||||||
- Issue #19967: Thanks to the PEP 442, asyncio.Future now uses a
|
- Issue #19967: Thanks to the PEP 442, asyncio.Future now uses a
|
||||||
destructor to log uncaught exceptions, instead of the dedicated
|
destructor to log uncaught exceptions, instead of the dedicated
|
||||||
_TracebackLogger class.
|
_TracebackLogger class.
|
||||||
|
|
|
@ -539,6 +539,20 @@ _PyIO_ConvertSsize_t(PyObject *obj, void *result) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_PyIO_State *
|
||||||
|
_PyIO_get_module_state(void)
|
||||||
|
{
|
||||||
|
PyObject *mod = PyState_FindModule(&_PyIO_Module);
|
||||||
|
_PyIO_State *state;
|
||||||
|
if (mod == NULL || (state = IO_MOD_STATE(mod)) == NULL) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"could not find io module state "
|
||||||
|
"(interpreter shutdown?)");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyIO_get_locale_module(_PyIO_State *state)
|
_PyIO_get_locale_module(_PyIO_State *state)
|
||||||
{
|
{
|
||||||
|
|
|
@ -135,8 +135,9 @@ typedef struct {
|
||||||
} _PyIO_State;
|
} _PyIO_State;
|
||||||
|
|
||||||
#define IO_MOD_STATE(mod) ((_PyIO_State *)PyModule_GetState(mod))
|
#define IO_MOD_STATE(mod) ((_PyIO_State *)PyModule_GetState(mod))
|
||||||
#define IO_STATE IO_MOD_STATE(PyState_FindModule(&_PyIO_Module))
|
#define IO_STATE() _PyIO_get_module_state()
|
||||||
|
|
||||||
|
extern _PyIO_State *_PyIO_get_module_state(void);
|
||||||
extern PyObject *_PyIO_get_locale_module(_PyIO_State *);
|
extern PyObject *_PyIO_get_locale_module(_PyIO_State *);
|
||||||
|
|
||||||
extern PyObject *_PyIO_str_close;
|
extern PyObject *_PyIO_str_close;
|
||||||
|
|
|
@ -91,7 +91,9 @@ bufferediobase_readinto(PyObject *self, PyObject *args)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
bufferediobase_unsupported(const char *message)
|
bufferediobase_unsupported(const char *message)
|
||||||
{
|
{
|
||||||
PyErr_SetString(IO_STATE->unsupported_operation, message);
|
_PyIO_State *state = IO_STATE();
|
||||||
|
if (state != NULL)
|
||||||
|
PyErr_SetString(state->unsupported_operation, message);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -493,7 +493,9 @@ err_closed(void)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
err_mode(char *action)
|
err_mode(char *action)
|
||||||
{
|
{
|
||||||
PyErr_Format(IO_STATE->unsupported_operation,
|
_PyIO_State *state = IO_STATE();
|
||||||
|
if (state != NULL)
|
||||||
|
PyErr_Format(state->unsupported_operation,
|
||||||
"File not open for %s", action);
|
"File not open for %s", action);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,9 @@ _Py_IDENTIFIER(read);
|
||||||
static PyObject *
|
static PyObject *
|
||||||
iobase_unsupported(const char *message)
|
iobase_unsupported(const char *message)
|
||||||
{
|
{
|
||||||
PyErr_SetString(IO_STATE->unsupported_operation, message);
|
_PyIO_State *state = IO_STATE();
|
||||||
|
if (state != NULL)
|
||||||
|
PyErr_SetString(state->unsupported_operation, message);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,9 @@ PyDoc_STRVAR(textiobase_doc,
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_unsupported(const char *message)
|
_unsupported(const char *message)
|
||||||
{
|
{
|
||||||
PyErr_SetString(IO_STATE->unsupported_operation, message);
|
_PyIO_State *state = IO_STATE();
|
||||||
|
if (state != NULL)
|
||||||
|
PyErr_SetString(state->unsupported_operation, message);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,7 +854,7 @@ textiowrapper_init(textio *self, PyObject *args, PyObject *kwds)
|
||||||
char *errors = NULL;
|
char *errors = NULL;
|
||||||
char *newline = NULL;
|
char *newline = NULL;
|
||||||
int line_buffering = 0, write_through = 0;
|
int line_buffering = 0, write_through = 0;
|
||||||
_PyIO_State *state = IO_STATE;
|
_PyIO_State *state = NULL;
|
||||||
|
|
||||||
PyObject *res;
|
PyObject *res;
|
||||||
int r;
|
int r;
|
||||||
|
@ -891,6 +893,9 @@ textiowrapper_init(textio *self, PyObject *args, PyObject *kwds)
|
||||||
if (encoding == NULL) {
|
if (encoding == NULL) {
|
||||||
/* Try os.device_encoding(fileno) */
|
/* Try os.device_encoding(fileno) */
|
||||||
PyObject *fileno;
|
PyObject *fileno;
|
||||||
|
state = IO_STATE();
|
||||||
|
if (state == NULL)
|
||||||
|
goto error;
|
||||||
fileno = _PyObject_CallMethodId(buffer, &PyId_fileno, NULL);
|
fileno = _PyObject_CallMethodId(buffer, &PyId_fileno, NULL);
|
||||||
/* Ignore only AttributeError and UnsupportedOperation */
|
/* Ignore only AttributeError and UnsupportedOperation */
|
||||||
if (fileno == NULL) {
|
if (fileno == NULL) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue