Can anyone please help understand why a future's done()
never returns True
(even when future is complete) when created from nested thread (i.e. using ThreadPoolExecutor
within another ThreadPoolExecutor
, which is required in my business usecase), but
a future's done()
returns True
when created one one level ThreadPoolExecutor
Here is the common code that is used for getting metrics of ThreadPoolExecutor
(primarily looking at completed_tasks_cnt
which is equal to number of futures with done() = True
:
import time
from typing import Callable, Tuple
from concurrent.futures.thread import ThreadPoolExecutor
import logging
import asyncio
import pprint
import traceback
from asyncio.futures import Future
log = logging.getLogger(__name__)
logging.basicConfig(format='[%(asctime)s][%(threadName)s][%(process)d][%(name)s][%(levelname)s] %(message)s', level=logging.NOTSET)
def run_fnc_in_background_thread(executor: ThreadPoolExecutor, fnc: Callable, fnc_args: Tuple=(), loop=None) -> Future:
"""Run fnc (performing a IO bound task) in a daemon Thread
Args:
executor: ThreadPoolExecutor (see https://pymotw.com/3/asyncio/executors.html)
fnc: Callable function to run in thread
fnc_args: (Tuple): List of function args
loop: Event loop (Get loop from `asyncio.get_event_loop()` or `asyncio.new_event_loop()`)
Returns:
Future: A future object of the task. You can cancel/stop the future
(https://docs.python.org/3/library/asyncio-future.html#asyncio.Future)
"""
loop = loop or asyncio.get_event_loop()
f: Future = loop.run_in_executor(executor, fnc, *fnc_args)
asyncio.ensure_future(f)
return f
def slow_running_task(taskid):
log.info(f"[{taskid}]slow_running_task sleeping for 2sec")
time.sleep(2)
log.info(f"[{taskid}]slow_running_task COMPLETED")
def check_status(executor, futures):
while True:
# https://stackoverflow.com/a/25474898/558397
pending_tasks_to_be_started = executor._work_queue.qsize()
active_threads_cnt = len(executor._threads)
active_tasks_cnt = 0
completed_tasks_cnt = 0
cancelled_tasks_cnt = 0
exception_raised_tasks_cnt = 0
for f in futures:
if f.done():
completed_tasks_cnt += 1
if f.cancelled():
cancelled_tasks_cnt += 1
if f.exception():
exception_raised_tasks_cnt += 1
else:
active_tasks_cnt += 1
thread_pool_metrics = {
"pending_tasks_to_be_started": pending_tasks_to_be_started,
"active_threads_cnt": active_threads_cnt,
"active_tasks_cnt": active_tasks_cnt,
"completed_tasks_cnt": completed_tasks_cnt,
"cancelled_tasks_cnt": cancelled_tasks_cnt,
"exception_raised_tasks_cnt": exception_raised_tasks_cnt
}
log.info(f"thread_pool_metrics:\n{pprint.pformat(thread_pool_metrics)}")
time.sleep(1)
executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="MyThread")
def big_thread():
# run_fnc_in_background_thread(executor, slow_running_task)
futures = []
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
f1 = run_fnc_in_background_thread(executor, check_status, fnc_args=(executor, futures), loop=loop)
for taskid in range(2):
futures.append(run_fnc_in_background_thread(executor, slow_running_task, fnc_args=(taskid,), loop=loop))
As you can see, when I use nested ThreadPoolExecutor
(because of my requirement), I get the following output (i.e. completed_tasks_cnt
is never incremented: 'completed_tasks_cnt': 0,
):
def main():
run_fnc_in_background_thread(executor, big_thread)
# big_thread()
loop = asyncio.get_event_loop()
loop.run_forever()
main()
Logs:
[2019-08-19 13:42:02,187][MainThread][32025][asyncio][DEBUG] Using selector: EpollSelector
[2019-08-19 13:42:02,189][MyThread_0][32025][asyncio][DEBUG] Using selector: EpollSelector
[2019-08-19 13:42:02,189][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 0,
'active_threads_cnt': 1,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:02,190][MyThread_2][32025][__main__][INFO] [0]slow_running_task sleeping for 2sec
[2019-08-19 13:42:02,190][MyThread_0][32025][__main__][INFO] [1]slow_running_task sleeping for 2sec
[2019-08-19 13:42:03,195][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:04,195][MyThread_2][32025][__main__][INFO] [0]slow_running_task COMPLETED
[2019-08-19 13:42:04,196][MyThread_0][32025][__main__][INFO] [1]slow_running_task COMPLETED
[2019-08-19 13:42:04,199][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:05,203][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:06,209][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:07,214][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:08,220][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:42:09,225][MyThread_1][32025][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
But when I call the future from just one level depth of ThreadPoolExecutor
, it works: (see 'completed_tasks_cnt': 2,
):
def main():
# run_fnc_in_background_thread(executor, big_thread)
big_thread()
loop = asyncio.get_event_loop()
loop.run_forever()
main()
[2019-08-19 13:43:49,709][MainThread][32239][asyncio][DEBUG] Using selector: EpollSelector
[2019-08-19 13:43:49,710][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 0,
'active_threads_cnt': 0,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:43:49,711][MyThread_1][32239][__main__][INFO] [0]slow_running_task sleeping for 2sec
[2019-08-19 13:43:49,711][MyThread_2][32239][__main__][INFO] [1]slow_running_task sleeping for 2sec
[2019-08-19 13:43:50,715][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:43:51,719][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 2,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 0,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:43:51,719][MyThread_2][32239][__main__][INFO] [1]slow_running_task COMPLETED
[2019-08-19 13:43:51,719][MyThread_1][32239][__main__][INFO] [0]slow_running_task COMPLETED
[2019-08-19 13:43:52,724][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 0,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 2,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:43:53,730][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 0,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 2,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}
[2019-08-19 13:43:54,735][MyThread_0][32239][__main__][INFO] thread_pool_metrics:
{'active_tasks_cnt': 0,
'active_threads_cnt': 3,
'cancelled_tasks_cnt': 0,
'completed_tasks_cnt': 2,
'exception_raised_tasks_cnt': 0,
'pending_tasks_to_be_started': 0}