Why does the following Python code using the concurrent.futures
module hang forever?
import concurrent.futures
class A:
def f(self):
print("called")
class B(A):
def f(self):
executor = concurrent.futures.ProcessPoolExecutor(max_workers=2)
executor.submit(super().f)
if __name__ == "__main__":
B().f()
The call raises an invisible exception [Errno 24] Too many open files
(to see it, replace the line executor.submit(super().f)
with print(executor.submit(super().f).exception())
).
However, replacing ProcessPoolExecutor
with ThreadPoolExecutor
prints "called" as expected.
Why does the following Python code using the multiprocessing.pool
module raise the exception AssertionError: daemonic processes are not allowed to have children
?
import multiprocessing.pool
class A:
def f(self):
print("called")
class B(A):
def f(self):
pool = multiprocessing.pool.Pool(2)
pool.apply(super().f)
if __name__ == "__main__":
B().f()
However, replacing Pool
with ThreadPool
prints "called" as expected.
Environment: CPython 3.7, MacOS 10.14.
concurrent.futures.ProcessPoolExecutor
andmultiprocessing.pool.Pool
usesmultiprocessing.queues.Queue
to pass the work function object from caller to worker process,Queue
usespickle
module to serialize/unserialize, but it failed to proper processing bound method object with child class instance:outputs:
A.f
becomesB.f
, this effectly creates infinite recursive callingB.f
toB.f
in the worker process.pickle.dumps
utilize__reduce__
method of bound method object, IMO, its implementation, has no consideration of this scenario, which does not take care of the realfunc
object, but only try to get back from instanceself
obj (B()
) with the simple name (f
), which resultingB.f
, very likely a bug.good news is, as we know where the issue is, we could fix it by implementing our own reduction function that tries to recreate the bound method object from the original function (
A.f
) and instance obj (B()
):we could do this because bound method is a descriptor.
ps: I have filed a bug report.