diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1042,6 +1042,37 @@ self.assertEqual(out, b'') self.assertNotIn("Unhandled exception", err.decode()) + def test_sys_exit_stderr(self): + # Documentation states that sys.exit(arg) writes + # arg to stderr, as long as it is not an integer + # or None. Ensure that this statement is true + # when sys.exit() is called from within threads + # other than the main thread. See issue 6634. + template = r"""if True: + import sys, threading + def run(): + %s + t = threading.Thread(target=run) + t.start() + t.join() + """ + + script = template % 'sys.exit("error message")' + rc, out, err = assert_python_ok("-c", script) + self.assertEqual(err.decode(), "error message") + + script = template % 'sys.exit(None)' + rc, out, err = assert_python_ok("-c", script) + self.assertEqual(err, b'') + + script = template % 'sys.exit(1)' + rc, out, err = assert_python_ok("-c", script) + self.assertEqual(err, b'') + + script = template % 'raise SystemExit("error message")' + rc, out, err = assert_python_ok("-c", script) + self.assertEqual(err.decode(), "error message") + class TimerTests(BaseTestCase): diff --git a/Lib/threading.py b/Lib/threading.py --- a/Lib/threading.py +++ b/Lib/threading.py @@ -921,8 +921,16 @@ try: self.run() - except SystemExit: - pass + except SystemExit as e: + # Handle "exit code" (provided via argument to SystemExit, if + # raised manually, or via argument to sys.exit()). Ignore None + # and integer. Attempt to write any other object to stderr, as + # is the behavior of sys.exit() called from the main thread. + if e.args: + code = e.args[0] + if code is not None and not isinstance(code, int): + if _sys and _sys.stderr is not None: + print(code, file=_sys.stderr) except: # If sys.stderr is no more (most likely from interpreter # shutdown) use self._stderr. Otherwise still use sys (as in