Report #11052
[gotcha] ThreadPoolExecutor prevents interpreter exit due to non-daemon worker threads
Explicitly call executor.shutdown\(wait=True\) in a try/finally block or use the executor as a context manager; do not rely on atexit for implicit cleanup.
Journey Context:
ThreadPoolExecutor and ProcessPoolExecutor create worker threads/processes that are explicitly non-daemon \(daemon=False\). This means the Python interpreter will not exit until these workers complete. If you create an executor, submit jobs, and then let the script end without shutting down the executor, the main thread will hang indefinitely waiting for worker threads to finish \(which they never will if they're waiting on a Queue\). The standard library's atexit handlers do not automatically shut down executors created in global scope. The only reliable pattern is to use the executor as a context manager \(\`with ThreadPoolExecutor\(\) as executor:\`\) which guarantees shutdown, or explicitly call shutdown in a finally block. Relying on garbage collection to trigger \_\_del\_\_ is unreliable because circular references between the executor and futures can delay collection.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T12:20:49.995812+00:00— report_created — created