Skip to content

fix(tools): Python 3.14 compatibility for DaemonThreadPoolExecutor - #57459

Open
gysyl wants to merge 2 commits into
NousResearch:mainfrom
gysyl:fix/python314-daemon-pool
Open

fix(tools): Python 3.14 compatibility for DaemonThreadPoolExecutor#57459
gysyl wants to merge 2 commits into
NousResearch:mainfrom
gysyl:fix/python314-daemon-pool

Conversation

@gysyl

@gysyl gysyl commented Jul 3, 2026

Copy link
Copy Markdown

Problem

Python 3.14 removed the internal _initializer and _initargs attributes from ThreadPoolExecutor. The custom DaemonThreadPoolExecutor class was directly accessing these attributes, causing gateway crashes:

❌ Error during OpenAI-compatible API call #1: 'DaemonThreadPoolExecutor' object has no attribute '_initializer'
hermes-gateway.service: Main process exited, code=exited, status=1/FAILURE

This led to a restart loop on Python 3.14+ systems (exit code 75 TEMPFAIL).

Solution

Use getattr() with None fallback for forward compatibility:

initializer = getattr(self, '_initializer', None)
initargs = getattr(self, '_initargs', None)

Tested on Python 3.14.4 - gateway now runs stable without crashes.

Verification

Python 3.14.4
Has _initializer: False
Has _initargs: False

Gateway status after fix: active (running), memory stable at ~360MB, all platforms connected.

Python 3.14 removed _initializer and _initargs attributes from
ThreadPoolExecutor, causing gateway crashes with:
  'DaemonThreadPoolExecutor' object has no attribute '_initializer'

Use getattr() with None fallback for forward compatibility.

Fixes: gateway restart loop on Python 3.14+
@alt-glitch alt-glitch added type/bug Something isn't working comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have labels Jul 3, 2026
@liuhao1024

Copy link
Copy Markdown
Contributor

Thanks for flagging this. After comparing the two diffs:

Recommend closing #57459 in favor of #58598. The getattr approach doesn't account for the parameter count change in CPython 3.14's _worker.

@enedelko

Copy link
Copy Markdown

The getattr / None fallback in this PR does not actually fix the Python 3.14 issue. Here is why:

In Python 3.14, the internal _worker signature changed from 4 params (executor_ref, work_queue, initializer, initargs) to 3 params (executor_ref, ctx, work_queue). The ctx argument is a WorkerContext object created by self._create_worker_context() (set up in ThreadPoolExecutor.__init__).

The _worker function immediately calls ctx.initialize() on startup. Passing None (via getattr fallback) causes:

AttributeError: 'NoneType' object has no attribute 'initialize'

...which surfaces as:

Error during OpenAI-compatible API call #1: A thread initializer failed, the thread pool is not usable anymore

The correct fix is to pass self._create_worker_context() instead. PR #58699 does it correctly with inspect.signature detection and branching. It would be better to adopt that approach rather than merging a partial getattr fix that silently breaks on Python 3.14.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the Python 3.14 regression. The reported defect is still present on current main: tools/daemon_pool.py:55-60 invokes _worker with the pre-3.14 four-argument tuple.

Problems

  • The new getattr fallback leaves the four-argument _worker call unchanged. CPython 3.14 defines _worker(executor_reference, ctx, work_queue) and calls ctx.initialize() (Lib/concurrent/futures/thread.py:97-100); its executor supplies self._create_worker_context() at thread.py:233-236. This PR therefore does not construct the required WorkerContext or use the compatible three-argument call shape.
  • No regression test accompanies the change. Current tests in tests/tools/test_daemon_pool.py:20-83 do not exercise a Python 3.14 WorkerContext path.

Suggested changes

  • Detect the _worker signature and branch: legacy versions retain the four-item tuple; the 3.14 path must pass (executor_ref, self._create_worker_context(), self._work_queue).
  • Add a mocked 3.14-path test asserting that exact tuple, so CI can cover it before Python 3.14 is the default interpreter.

This is an automated hermes-sweeper review.

Comment thread tools/daemon_pool.py Outdated
# ThreadPoolExecutor. Pass None for compatibility.
initializer = getattr(self, '_initializer', None)
initargs = getattr(self, '_initargs', None)
t = threading.Thread(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still creates a four-argument _worker call. CPython 3.14 expects (executor_ref, ctx, work_queue) and requires ctx to be self._create_worker_context(), so the compatibility path needs a signature-based branch rather than None fallbacks.

…tion

Replace getattr() fallback with explicit sys.version_info check:
- Python ≤3.13: use _worker(ref, queue, initializer, initargs) with
  direct _initializer/_initargs attribute access
- Python ≥3.14: use _worker(ref, ctx, queue) with _create_worker_context()

This is more robust than getattr() because it correctly handles the
new _create_worker_context() API in 3.14 rather than passing None
for initializer/initargs which 3.14 no longer accepts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants