mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-113964: Don't prevent new threads until all non-daemon threads exit (#116677)
Starting in Python 3.12, we prevented calling fork() and starting new threads during interpreter finalization (shutdown). This has led to a number of regressions and flaky tests. We should not prevent starting new threads (or `fork()`) until all non-daemon threads exit and finalization starts in earnest. This changes the checks to use `_PyInterpreterState_GetFinalizing(interp)`, which is set immediately before terminating non-daemon threads.
This commit is contained in:
		
							parent
							
								
									025ef7a5f7
								
							
						
					
					
						commit
						60e105c1c1
					
				
					 8 changed files with 57 additions and 27 deletions
				
			
		|  | @ -5357,20 +5357,21 @@ def test_fork_warns_when_non_python_thread_exists(self): | |||
|         self.assertEqual(err.decode("utf-8"), "") | ||||
|         self.assertEqual(out.decode("utf-8"), "") | ||||
| 
 | ||||
|     def test_fork_at_exit(self): | ||||
|     def test_fork_at_finalization(self): | ||||
|         code = """if 1: | ||||
|             import atexit | ||||
|             import os | ||||
| 
 | ||||
|             def exit_handler(): | ||||
|             class AtFinalization: | ||||
|                 def __del__(self): | ||||
|                     print("OK") | ||||
|                     pid = os.fork() | ||||
|                     if pid != 0: | ||||
|                         print("shouldn't be printed") | ||||
| 
 | ||||
|             atexit.register(exit_handler) | ||||
|             at_finalization = AtFinalization() | ||||
|         """ | ||||
|         _, out, err = assert_python_ok("-c", code) | ||||
|         self.assertEqual(b"", out) | ||||
|         self.assertEqual(b"OK\n", out) | ||||
|         self.assertIn(b"can't fork at interpreter shutdown", err) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -3398,14 +3398,15 @@ def test_preexec_at_exit(self): | |||
|         def dummy(): | ||||
|             pass | ||||
| 
 | ||||
|         def exit_handler(): | ||||
|         class AtFinalization: | ||||
|             def __del__(self): | ||||
|                 print("OK") | ||||
|                 subprocess.Popen({ZERO_RETURN_CMD}, preexec_fn=dummy) | ||||
|                 print("shouldn't be printed") | ||||
| 
 | ||||
|         atexit.register(exit_handler) | ||||
|         at_finalization = AtFinalization() | ||||
|         """ | ||||
|         _, out, err = assert_python_ok("-c", code) | ||||
|         self.assertEqual(out, b'') | ||||
|         self.assertEqual(out.strip(), b"OK") | ||||
|         self.assertIn(b"preexec_fn not supported at interpreter shutdown", err) | ||||
| 
 | ||||
|     @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), | ||||
|  |  | |||
|  | @ -1154,21 +1154,21 @@ def import_threading(): | |||
|         self.assertEqual(out, b'') | ||||
|         self.assertEqual(err, b'') | ||||
| 
 | ||||
|     def test_start_new_thread_at_exit(self): | ||||
|     def test_start_new_thread_at_finalization(self): | ||||
|         code = """if 1: | ||||
|             import atexit | ||||
|             import _thread | ||||
| 
 | ||||
|             def f(): | ||||
|                 print("shouldn't be printed") | ||||
| 
 | ||||
|             def exit_handler(): | ||||
|             class AtFinalization: | ||||
|                 def __del__(self): | ||||
|                     print("OK") | ||||
|                     _thread.start_new_thread(f, ()) | ||||
| 
 | ||||
|             atexit.register(exit_handler) | ||||
|             at_finalization = AtFinalization() | ||||
|         """ | ||||
|         _, out, err = assert_python_ok("-c", code) | ||||
|         self.assertEqual(out, b'') | ||||
|         self.assertEqual(out.strip(), b"OK") | ||||
|         self.assertIn(b"can't create new thread at interpreter shutdown", err) | ||||
| 
 | ||||
| class ThreadJoinOnShutdown(BaseTestCase): | ||||
|  | @ -1297,6 +1297,30 @@ def main(): | |||
|         rc, out, err = assert_python_ok('-c', script) | ||||
|         self.assertFalse(err) | ||||
| 
 | ||||
|     def test_thread_from_thread(self): | ||||
|         script = """if True: | ||||
|             import threading | ||||
|             import time | ||||
| 
 | ||||
|             def thread2(): | ||||
|                 time.sleep(0.05) | ||||
|                 print("OK") | ||||
| 
 | ||||
|             def thread1(): | ||||
|                 time.sleep(0.05) | ||||
|                 t2 = threading.Thread(target=thread2) | ||||
|                 t2.start() | ||||
| 
 | ||||
|             t = threading.Thread(target=thread1) | ||||
|             t.start() | ||||
|             # do not join() -- the interpreter waits for non-daemon threads to | ||||
|             # finish. | ||||
|             """ | ||||
|         rc, out, err = assert_python_ok('-c', script) | ||||
|         self.assertEqual(err, b"") | ||||
|         self.assertEqual(out.strip(), b"OK") | ||||
|         self.assertEqual(rc, 0) | ||||
| 
 | ||||
|     @skip_unless_reliable_fork | ||||
|     def test_reinit_tls_after_fork(self): | ||||
|         # Issue #13817: fork() would deadlock in a multithreaded program with | ||||
|  |  | |||
|  | @ -0,0 +1,2 @@ | |||
| Starting new threads and process creation through :func:`os.fork` are now | ||||
| only prevented once all non-daemon threads exit. | ||||
|  | @ -1031,7 +1031,9 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args, | |||
|     Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE(py_fds_to_keep); | ||||
| 
 | ||||
|     PyInterpreterState *interp = _PyInterpreterState_GET(); | ||||
|     if ((preexec_fn != Py_None) && interp->finalizing) { | ||||
|     if ((preexec_fn != Py_None) && | ||||
|         _PyInterpreterState_GetFinalizing(interp) != NULL) | ||||
|     { | ||||
|         PyErr_SetString(PyExc_PythonFinalizationError, | ||||
|                         "preexec_fn not supported at interpreter shutdown"); | ||||
|         return NULL; | ||||
|  |  | |||
|  | @ -1729,7 +1729,7 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args, | |||
|                         "thread is not supported for isolated subinterpreters"); | ||||
|         return -1; | ||||
|     } | ||||
|     if (interp->finalizing) { | ||||
|     if (_PyInterpreterState_GetFinalizing(interp) != NULL) { | ||||
|         PyErr_SetString(PyExc_PythonFinalizationError, | ||||
|                         "can't create new thread at interpreter shutdown"); | ||||
|         return -1; | ||||
|  |  | |||
|  | @ -7842,7 +7842,7 @@ os_fork1_impl(PyObject *module) | |||
|     pid_t pid; | ||||
| 
 | ||||
|     PyInterpreterState *interp = _PyInterpreterState_GET(); | ||||
|     if (interp->finalizing) { | ||||
|     if (_PyInterpreterState_GetFinalizing(interp) != NULL) { | ||||
|         PyErr_SetString(PyExc_PythonFinalizationError, | ||||
|                         "can't fork at interpreter shutdown"); | ||||
|         return NULL; | ||||
|  | @ -7886,7 +7886,7 @@ os_fork_impl(PyObject *module) | |||
| { | ||||
|     pid_t pid; | ||||
|     PyInterpreterState *interp = _PyInterpreterState_GET(); | ||||
|     if (interp->finalizing) { | ||||
|     if (_PyInterpreterState_GetFinalizing(interp) != NULL) { | ||||
|         PyErr_SetString(PyExc_PythonFinalizationError, | ||||
|                         "can't fork at interpreter shutdown"); | ||||
|         return NULL; | ||||
|  | @ -8719,7 +8719,7 @@ os_forkpty_impl(PyObject *module) | |||
|     pid_t pid; | ||||
| 
 | ||||
|     PyInterpreterState *interp = _PyInterpreterState_GET(); | ||||
|     if (interp->finalizing) { | ||||
|     if (_PyInterpreterState_GetFinalizing(interp) != NULL) { | ||||
|         PyErr_SetString(PyExc_PythonFinalizationError, | ||||
|                         "can't fork at interpreter shutdown"); | ||||
|         return NULL; | ||||
|  |  | |||
|  | @ -505,7 +505,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors) | |||
| 
 | ||||
|     /* Disable checks during Python finalization. For example, it allows to
 | ||||
|        call _PyObject_Dump() during finalization for debugging purpose. */ | ||||
|     if (interp->finalizing) { | ||||
|     if (_PyInterpreterState_GetFinalizing(interp) != NULL) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sam Gross
						Sam Gross