mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #22423: Unhandled exception in thread no longer causes unhandled
AttributeError when sys.stderr is None.
This commit is contained in:
parent
3f40c40dea
commit
52005c2e13
3 changed files with 94 additions and 10 deletions
|
@ -4,7 +4,7 @@ Tests for the threading module.
|
||||||
|
|
||||||
import test.support
|
import test.support
|
||||||
from test.support import verbose, strip_python_stderr, import_module, cpython_only
|
from test.support import verbose, strip_python_stderr, import_module, cpython_only
|
||||||
from test.script_helper import assert_python_ok
|
from test.script_helper import assert_python_ok, assert_python_failure
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -15,7 +15,6 @@ import time
|
||||||
import unittest
|
import unittest
|
||||||
import weakref
|
import weakref
|
||||||
import os
|
import os
|
||||||
from test.script_helper import assert_python_ok, assert_python_failure
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from test import lock_tests
|
from test import lock_tests
|
||||||
|
@ -962,6 +961,88 @@ class ThreadingExceptionTests(BaseTestCase):
|
||||||
self.assertEqual(p.returncode, 0, "Unexpected error: " + stderr.decode())
|
self.assertEqual(p.returncode, 0, "Unexpected error: " + stderr.decode())
|
||||||
self.assertEqual(data, expected_output)
|
self.assertEqual(data, expected_output)
|
||||||
|
|
||||||
|
def test_print_exception(self):
|
||||||
|
script = r"""if True:
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
running = False
|
||||||
|
def run():
|
||||||
|
global running
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
1/0
|
||||||
|
t = threading.Thread(target=run)
|
||||||
|
t.start()
|
||||||
|
while not running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
running = False
|
||||||
|
t.join()
|
||||||
|
"""
|
||||||
|
rc, out, err = assert_python_ok("-c", script)
|
||||||
|
self.assertEqual(out, b'')
|
||||||
|
err = err.decode()
|
||||||
|
self.assertIn("Exception in thread", err)
|
||||||
|
self.assertIn("Traceback (most recent call last):", err)
|
||||||
|
self.assertIn("ZeroDivisionError", err)
|
||||||
|
self.assertNotIn("Unhandled exception", err)
|
||||||
|
|
||||||
|
def test_print_exception_stderr_is_none_1(self):
|
||||||
|
script = r"""if True:
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
running = False
|
||||||
|
def run():
|
||||||
|
global running
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
1/0
|
||||||
|
t = threading.Thread(target=run)
|
||||||
|
t.start()
|
||||||
|
while not running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
sys.stderr = None
|
||||||
|
running = False
|
||||||
|
t.join()
|
||||||
|
"""
|
||||||
|
rc, out, err = assert_python_ok("-c", script)
|
||||||
|
self.assertEqual(out, b'')
|
||||||
|
err = err.decode()
|
||||||
|
self.assertIn("Exception in thread", err)
|
||||||
|
self.assertIn("Traceback (most recent call last):", err)
|
||||||
|
self.assertIn("ZeroDivisionError", err)
|
||||||
|
self.assertNotIn("Unhandled exception", err)
|
||||||
|
|
||||||
|
def test_print_exception_stderr_is_none_2(self):
|
||||||
|
script = r"""if True:
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
running = False
|
||||||
|
def run():
|
||||||
|
global running
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
1/0
|
||||||
|
sys.stderr = None
|
||||||
|
t = threading.Thread(target=run)
|
||||||
|
t.start()
|
||||||
|
while not running:
|
||||||
|
time.sleep(0.01)
|
||||||
|
running = False
|
||||||
|
t.join()
|
||||||
|
"""
|
||||||
|
rc, out, err = assert_python_ok("-c", script)
|
||||||
|
self.assertEqual(out, b'')
|
||||||
|
self.assertNotIn("Unhandled exception", err.decode())
|
||||||
|
|
||||||
|
|
||||||
class TimerTests(BaseTestCase):
|
class TimerTests(BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -248,7 +248,7 @@ class Condition:
|
||||||
|
|
||||||
def _is_owned(self):
|
def _is_owned(self):
|
||||||
# Return True if lock is owned by current_thread.
|
# Return True if lock is owned by current_thread.
|
||||||
# This method is called only if __lock doesn't have _is_owned().
|
# This method is called only if _lock doesn't have _is_owned().
|
||||||
if self._lock.acquire(0):
|
if self._lock.acquire(0):
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
return False
|
return False
|
||||||
|
@ -749,12 +749,12 @@ class Thread:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__initialized = False
|
_initialized = False
|
||||||
# Need to store a reference to sys.exc_info for printing
|
# Need to store a reference to sys.exc_info for printing
|
||||||
# out exceptions when a thread tries to use a global var. during interp.
|
# out exceptions when a thread tries to use a global var. during interp.
|
||||||
# shutdown and thus raises an exception about trying to perform some
|
# shutdown and thus raises an exception about trying to perform some
|
||||||
# operation on/with a NoneType
|
# operation on/with a NoneType
|
||||||
__exc_info = _sys.exc_info
|
_exc_info = _sys.exc_info
|
||||||
# Keep sys.exc_clear too to clear the exception just before
|
# Keep sys.exc_clear too to clear the exception just before
|
||||||
# allowing .join() to return.
|
# allowing .join() to return.
|
||||||
#XXX __exc_clear = _sys.exc_clear
|
#XXX __exc_clear = _sys.exc_clear
|
||||||
|
@ -926,10 +926,10 @@ class Thread:
|
||||||
# shutdown) use self._stderr. Otherwise still use sys (as in
|
# shutdown) use self._stderr. Otherwise still use sys (as in
|
||||||
# _sys) in case sys.stderr was redefined since the creation of
|
# _sys) in case sys.stderr was redefined since the creation of
|
||||||
# self.
|
# self.
|
||||||
if _sys:
|
if _sys and _sys.stderr is not None:
|
||||||
_sys.stderr.write("Exception in thread %s:\n%s\n" %
|
print("Exception in thread %s:\n%s" %
|
||||||
(self.name, _format_exc()))
|
(self.name, _format_exc()), file=self._stderr)
|
||||||
else:
|
elif self._stderr is not None:
|
||||||
# Do the best job possible w/o a huge amt. of code to
|
# Do the best job possible w/o a huge amt. of code to
|
||||||
# approximate a traceback (code ideas from
|
# approximate a traceback (code ideas from
|
||||||
# Lib/traceback.py)
|
# Lib/traceback.py)
|
||||||
|
@ -957,7 +957,7 @@ class Thread:
|
||||||
# test_threading.test_no_refcycle_through_target when
|
# test_threading.test_no_refcycle_through_target when
|
||||||
# the exception keeps the target alive past when we
|
# the exception keeps the target alive past when we
|
||||||
# assert that it's dead.
|
# assert that it's dead.
|
||||||
#XXX self.__exc_clear()
|
#XXX self._exc_clear()
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
with _active_limbo_lock:
|
with _active_limbo_lock:
|
||||||
|
|
|
@ -32,6 +32,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #22423: Unhandled exception in thread no longer causes unhandled
|
||||||
|
AttributeError when sys.stderr is None.
|
||||||
|
|
||||||
- Issue #21091: Fix API bug: email.message.EmailMessage.is_attachment is now
|
- Issue #21091: Fix API bug: email.message.EmailMessage.is_attachment is now
|
||||||
a method. Since EmailMessage is provisional, we can change the API in a
|
a method. Since EmailMessage is provisional, we can change the API in a
|
||||||
maintenance release, but we use a trick to remain backward compatible with
|
maintenance release, but we use a trick to remain backward compatible with
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue