-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
Bug report
Checklist
- I am confident this is a bug in CPython, not a bug in a third-party project
- I have searched the CPython issue tracker,
and am confident this bug has not been reported before
CPython versions tested on:
3.11
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.11.5 (main, Aug 26 2023, 00:26:34) [GCC 12.2.1 20220924]
A clear and concise description of the bug:
Using nested multiprocessing (i.e. spawn a child process inside a child process) is broken as of Python 3.11.5, leading to an attribute error.
Process Process-1:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/multiprocessing/process.py", line 314, in _bootstrap
self.run()
File "/usr/local/lib/python3.11/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/io/pyi_multiprocessing_nested_process.py", line 15, in process_function
process.start()
File "/usr/local/lib/python3.11/multiprocessing/process.py", line 121, in start
self._popen = self._Popen(self)
^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/multiprocessing/context.py", line 224, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/multiprocessing/context.py", line 288, in _Popen
return Popen(process_obj)
^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/multiprocessing/popen_spawn_posix.py", line 32, in __init__
super().__init__(process_obj)
File "/usr/local/lib/python3.11/multiprocessing/popen_fork.py", line 19, in __init__
self._launch(process_obj)
File "/usr/local/lib/python3.11/multiprocessing/popen_spawn_posix.py", line 47, in _launch
reduction.dump(process_obj, fp)
File "/usr/local/lib/python3.11/multiprocessing/reduction.py", line 60, in dump
ForkingPickler(file, protocol).dump(obj)
File "/usr/local/lib/python3.11/multiprocessing/synchronize.py", line 106, in __getstate__
if self.is_fork_ctx:
^^^^^^^^^^^^^^^^
AttributeError: 'Lock' object has no attribute 'is_fork_ctx'
Results: [1]Minimal code example below. Invoke with argument fork, forkserver or spawn. fork will work. forkserver and spawn will both raise the above error. All three variants work with Python 3.11.4.
import sys
import multiprocessing
def nested_process_function(queue):
print("Running nested sub-process!")
queue.put(2)
def process_function(queue):
print("Running sub-process!")
queue.put(1)
process = multiprocessing.Process(target=nested_process_function, args=(queue,))
process.start()
process.join()
def main(start_method):
multiprocessing.set_start_method(start_method)
queue = multiprocessing.Queue()
process = multiprocessing.Process(target=process_function, args=(queue,))
process.start()
process.join()
results = []
while not queue.empty():
results.append(queue.get())
print(f"Results: {results}")
assert results == [1, 2]
if __name__ == '__main__':
if len(sys.argv) != 2:
raise SystemExit(f"Usage: {sys.argv[0]} fork|forkserver|spawn")
main(sys.argv[1])I believe that the source of this regression is 34ef75d which adds the attribute is_fork_ctx to multiprocessing.Lock() but doesn't update the pickle methods (__getstate__() and __setstate__()) so after being serialised and deserialised, the Lock() object looses that attribute.
The following patch, adding is_fork_ctx to the pickle methods, makes the above work again.
diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py
index 2328d33212..9c5c2aada6 100644
--- a/Lib/multiprocessing/synchronize.py
+++ b/Lib/multiprocessing/synchronize.py
@@ -109,10 +109,11 @@ def __getstate__(self):
'not supported. Please use the same context to create '
'multiprocessing objects and Process.')
h = sl.handle
- return (h, sl.kind, sl.maxvalue, sl.name)
+ return (h, sl.kind, sl.maxvalue, sl.name, self.is_fork_ctx)
def __setstate__(self, state):
- self._semlock = _multiprocessing.SemLock._rebuild(*state)
+ self._semlock = _multiprocessing.SemLock._rebuild(*state[:4])
+ self.is_fork_ctx = state[4]
util.debug('recreated blocker with handle %r' % state[0])
self._make_methods()### Tasks