20017740 multiprocessing module susceptible to race condition during exit
authorErik Trauschke <Erik.Trauschke@oracle.com>
Mon, 17 Nov 2014 15:12:43 -0800
changeset 2212 0b6092e8d4e7
parent 2211 a99bbd5139c0
child 2213 b87f3edd5dd7
20017740 multiprocessing module susceptible to race condition during exit
components/python/python26/patches/Python26-37-multiproc-race.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/python26/patches/Python26-37-multiproc-race.patch	Mon Nov 17 15:12:43 2014 -0800
@@ -0,0 +1,69 @@
+This patch comes from upstream: http://bugs.python.org/issue15881
+
+It fixes a race condition in multiprocessing/util.py in which globals are
+deleted during exit but still used during the exit.
+
+diff -ur a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
+--- a/Lib/multiprocessing/util.py	2014-11-12 08:02:39.431360950 -0800
++++ b/Lib/multiprocessing/util.py	2014-11-12 08:03:56.970704872 -0800
+@@ -221,6 +221,12 @@
+     Finalizers with highest priority are called first; finalizers with
+     the same priority will be called in reverse order of creation.
+     '''
++    if _finalizer_registry is None:
++        # This function may be called after this module's globals are
++        # destroyed.  See the _exit_function function in this module for more
++        # notes.
++        return
++    
+     if minpriority is None:
+         f = lambda p : p[0][0] is not None
+     else:
+@@ -252,21 +258,38 @@
+ 
+ _exiting = False
+ 
+-def _exit_function():
++def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
++                   active_children=active_children,
++                   current_process=current_process):
++    # NB: we hold on to references to functions in the arglist due to the
++    # situation described below, where this function is called after this
++    # module's globals are destroyed.
++
+     global _exiting
+ 
+     info('process shutting down')
+     debug('running all "atexit" finalizers with priority >= 0')
+     _run_finalizers(0)
+ 
+-    for p in active_children():
+-        if p._daemonic:
+-            info('calling terminate() for daemon %s', p.name)
+-            p._popen.terminate()
+-
+-    for p in active_children():
+-        info('calling join() for process %s', p.name)
+-        p.join()
++    if current_process() is not None:
++        # NB: we check if the current process is None here because if
++        # it's None, any call to ``active_children()`` will throw an
++        # AttributeError (active_children winds up trying to get
++        # attributes from util._current_process).  This happens in a
++        # variety of shutdown circumstances that are not well-understood
++        # because module-scope variables are not apparently supposed to
++        # be destroyed until after this function is called.  However,
++        # they are indeed destroyed before this function is called.  See
++        # issues 9775 and 15881.  Also related: 4106, 9205, and 9207.
++
++        for p in active_children():
++            if p._daemonic:
++                info('calling terminate() for daemon %s', p.name)
++                p._popen.terminate()
++
++        for p in active_children():
++            info('calling join() for process %s', p.name)
++            p.join()
+ 
+     debug('running the remaining "atexit" finalizers')
+     _run_finalizers()