Skip to content

BUG: LocalPythonExecutor timeout waits for worker completion before returning #2197

@shaun0927

Description

@shaun0927

Problem
LocalPythonExecutor exposes a timeout feature, but a timed-out call can still block until the underlying work finishes.

The current implementation uses future.result(timeout=...) inside a with ThreadPoolExecutor(...) block. When the timeout is hit, control leaves the with block and executor shutdown waits for the worker to finish, so callers do not regain control near the configured timeout boundary.

This is not a security-boundary claim: I understand from the docs that LocalPythonExecutor is best-effort only and not a sandbox. But it does look like a correctness / availability bug, because PR #1910 introduced a "robust timeout mechanism" and the current behavior does not match that expectation.

Steps to reproduce
Please provide a minimal, self-contained, and reproducible example of the bug.

import time
from smolagents.local_python_executor import evaluate_python_code, ExecutionTimeoutError

code = """
import time
time.sleep(3)
result = 42
"""

start = time.monotonic()
try:
    evaluate_python_code(code, authorized_imports=["time"], timeout_seconds=1)
except ExecutionTimeoutError as e:
    print(type(e).__name__)
    print(f"elapsed={time.monotonic() - start:.2f}s")

Actual behavior and error logs
On the current main checkout, I consistently see the timeout exception only after the 3-second sleep finishes:

ExecutionTimeoutError
elapsed=3.00s

I reproduced the same late return through both:

  • evaluate_python_code(..., timeout_seconds=1)
  • LocalPythonExecutor(..., timeout_seconds=1)

The root cause appears to be the executor shutdown path waiting for the worker thread after the timeout has already been raised.

Expected behavior
The caller should regain control near the configured timeout boundary (for example, around 1 second in the repro above), even if the background worker cannot be force-killed.

In other words:

  • it is fine if the worker thread continues running in the background until completion,
  • but the public timeout API should return promptly instead of waiting for the original task duration.

Environment:
Please complete the following information:

  • OS: macOS
  • Python version: 3.12.12
  • Package version: repository checkout at dc13bbb on main (post-v1.24.0, 2026-04-15)
pytest 9.0.2
smolagents source checkout from main

Additional context (optional)
I searched existing issues/PRs before filing. The closest current item I found is #2108, but that one addresses contextvars / OpenTelemetry propagation in the timeout decorator, not this late-return behavior.

I also validated a minimal local patch that switches timeout cleanup to explicit executor management (shutdown(wait=False, cancel_futures=True) on timeout). With that patch, the same repro returns in about 1.00s, and the targeted timeout tests pass.


Checklist

  • I have searched the existing issues and have not found a similar bug report.
  • I have provided a minimal, reproducible example.
  • I have provided the full traceback of the error.
  • I have provided my environment details.
  • I am willing to work on this issue and submit a pull request. (optional)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions