From 64613e4ccf73f7e931c5998a32022ac9fd1b74f4 Mon Sep 17 00:00:00 2001 From: Fedor Zhukov Date: Fri, 23 Jan 2026 11:22:33 +0100 Subject: [PATCH] Terminate netbox client threadpool on exit From the mp documentation: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.ThreadPool > resources must also be properly managed, either by using the pool as a context manager or by calling close() and terminate() manually. Without explicit close() or terminate() it can produce exceptions on exit when __del__ is called, before the fds were opened: Exception ignored while calling deallocator : Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/multiprocessing/pool.py", line 271, in __del__ self._change_notifier.put(None) File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/multiprocessing/queues.py", line 397, in put self._writer.send_bytes(obj) File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/multiprocessing/connection.py", line 206, in send_bytes self._send_bytes(m[offset:offset + size]) File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/multiprocessing/connection.py", line 444, in _send_bytes self._send(header + buf) File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/multiprocessing/connection.py", line 400, in _send n = write(self._handle, buf) OSError: [Errno 9] Bad file descriptor Much cleaner would be to a use context manager but BaseNetboxClient does not provide one, instead doing init_pool in the constructor. This leaves atexit as a least involved option to prevent this annoying traceback on the shutdown. --- src/annetbox/base/client_sync.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/annetbox/base/client_sync.py b/src/annetbox/base/client_sync.py index 0115d35..fed8089 100644 --- a/src/annetbox/base/client_sync.py +++ b/src/annetbox/base/client_sync.py @@ -1,5 +1,6 @@ import http import logging +import atexit from abc import abstractmethod from collections.abc import Callable, Iterable from functools import wraps @@ -233,7 +234,10 @@ def _init_session( def _init_pool(self, threads: int) -> _BasePool: if threads > 1: - return ThreadPool(processes=threads) + pool = ThreadPool(processes=threads) + atexit.register(pool.terminate) + return pool + return FakePool()