# HG changeset patch # User Martin Panter # Date 1448850101 0 # Mon Nov 30 02:21:41 2015 +0000 # Branch 3.4 # Node ID d9843442f5ff82ab698f5b65b2fc5dd55009a08a # Parent c852c7d8d681aed89c11fc8e8ea6f66b61f9591d Issue #25764: Preserve subprocess fork exception when preexec_fn used Also fix import lock release failure handling. diff -r c852c7d8d681 Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py Sun Nov 29 13:14:00 2015 +0200 +++ b/Lib/test/test_subprocess.py Mon Nov 30 23:01:56 2015 +0000 @@ -1512,6 +1512,22 @@ if not enabled: gc.disable() + def test_preexec_fork_failure(self): + # The internal code did not preserve the previous exception when + # re-enabling garbage collection + try: + from resource import getrlimit, setrlimit, RLIMIT_NPROC + except ImportError as err: + self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD + limits = getrlimit(RLIMIT_NPROC) + [_, hard] = limits + setrlimit(RLIMIT_NPROC, (0, hard)) + self.addCleanup(setrlimit, RLIMIT_NPROC, limits) + # Forking should raise EAGAIN, translated to BlockingIOError + with self.assertRaises(BlockingIOError): + subprocess.call([sys.executable, '-c', ''], + preexec_fn=lambda: None) + def test_args_string(self): # args is a string fd, fname = tempfile.mkstemp() diff -r c852c7d8d681 Misc/NEWS --- a/Misc/NEWS Sun Nov 29 13:14:00 2015 +0200 +++ b/Misc/NEWS Mon Nov 30 23:01:56 2015 +0000 @@ -104,6 +104,9 @@ Library ------- +- Issue #25764: In the subprocess module, preserve any exception caused by + fork() failure when preexec_fn is used. + - Issue #10131: Fixed deep copying of minidom documents. Based on patch by Marian Ganisin. diff -r c852c7d8d681 Modules/_posixsubprocess.c --- a/Modules/_posixsubprocess.c Sun Nov 29 13:14:00 2015 +0200 +++ b/Modules/_posixsubprocess.c Mon Nov 30 23:01:56 2015 +0000 @@ -47,17 +47,25 @@ #define POSIX_CALL(call) do { if ((call) == -1) goto error; } while (0) -/* Given the gc module call gc.enable() and return 0 on success. */ +/* If gc was disabled, call gc.enable(). Return 0 on success. */ static int -_enable_gc(PyObject *gc_module) +_enable_gc(int need_to_reenable_gc, PyObject *gc_module) { PyObject *result; _Py_IDENTIFIER(enable); + PyObject *exctype, *val, *tb; - result = _PyObject_CallMethodId(gc_module, &PyId_enable, NULL); - if (result == NULL) - return 1; - Py_DECREF(result); + if (need_to_reenable_gc) { + PyErr_Fetch(&exctype, &val, &tb); + result = _PyObject_CallMethodId(gc_module, &PyId_enable, NULL); + if (exctype != NULL) { + PyErr_Restore(exctype, val, tb); + } + if (result == NULL) { + return 1; + } + Py_DECREF(result); + } return 0; } @@ -698,6 +706,7 @@ && _PyImport_ReleaseLock() < 0 && !PyErr_Occurred()) { PyErr_SetString(PyExc_RuntimeError, "not holding the import lock"); + pid = -1; } import_lock_held = 0; #endif @@ -710,9 +719,8 @@ _Py_FreeCharPArray(exec_array); /* Reenable gc in the parent process (or if fork failed). */ - if (need_to_reenable_gc && _enable_gc(gc_module)) { - Py_XDECREF(gc_module); - return NULL; + if (_enable_gc(need_to_reenable_gc, gc_module)) { + pid = -1; } Py_XDECREF(preexec_fn_args_tuple); Py_XDECREF(gc_module); @@ -736,14 +744,7 @@ Py_XDECREF(converted_args); Py_XDECREF(fast_args); Py_XDECREF(preexec_fn_args_tuple); - - /* Reenable gc if it was disabled. */ - if (need_to_reenable_gc) { - PyObject *exctype, *val, *tb; - PyErr_Fetch(&exctype, &val, &tb); - _enable_gc(gc_module); - PyErr_Restore(exctype, val, tb); - } + _enable_gc(need_to_reenable_gc, gc_module); Py_XDECREF(gc_module); return NULL; }