Skip to content

DockerCommandLineCodeExecutor does not safely manage cancellation tasks across multiple event loops (loop mismatch issue) #6395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
SongChiYoung opened this issue Apr 25, 2025 · 0 comments · May be fixed by #6402
Labels
help wanted Extra attention is needed needs-triage

Comments

@SongChiYoung
Copy link
Contributor

What happened?

Describe the bug
When I testing change at test code, I will tested fixture to scope="module", error occured.
When, I check that issue, I could find this error.

DockerCommandLineCodeExecutor raises a RuntimeError when executor.stop() is called after cancellation tasks have been created from a different event loop.

This happens because tasks stored in executor._cancellation_tasks are tied to different event loops and cannot be awaited safely in the current loop, causing a loop mismatch error.

To Reproduce

import asyncio
import tempfile
from autogen_core import CancellationToken
from autogen_core.code_executor import CodeBlock
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor

async def run_cancellation_scenario(executor: DockerCommandLineCodeExecutor):
    token = CancellationToken()
    code_block = CodeBlock(language="bash", code="echo '[Executor] Starting sleep...'; sleep 10; echo '[Executor] Sleep finished.'")
    exec_task = asyncio.create_task(executor.execute_code_blocks([code_block], cancellation_token=token))
    await asyncio.sleep(1)
    token.cancel()
    try:
        await exec_task
    except asyncio.CancelledError:
        pass

def run_scenario_in_new_loop(executor_instance: DockerCommandLineCodeExecutor):
    asyncio.run(run_cancellation_scenario(executor_instance))

async def main():
    temp_dir_obj = tempfile.TemporaryDirectory()
    executor = DockerCommandLineCodeExecutor(work_dir=temp_dir_obj.name)
    await executor.start()
    await asyncio.get_running_loop().run_in_executor(None, run_scenario_in_new_loop, executor)
    await executor.stop()
    temp_dir_obj.cleanup()

asyncio.run(main())

stacktrace:

RuntimeError: Event loop is closed

Traceback:
  File "errortest.py", line 1230, in <module>
    test27()
  File "errortest.py", line 1226, in test27
    asyncio.run(main())
  File "asyncio/runners.py", line 190, in run
    return runner.run(main)
  File "asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
  File "asyncio/base_events.py", line 654, in run_until_complete
    return future.result()
  File "errortest.py", line 1223, in main
    await executor.stop()
  File "_docker_code_executor.py", line 434, in stop
    await asyncio.gather(*self._cancellation_tasks)
  File "asyncio/tasks.py", line 839, in gather
    fut.add_done_callback(_done_callback)
  File "asyncio/base_events.py", line 762, in call_soon
    self._check_closed()
  File "asyncio/base_events.py", line 520, in _check_closed
    raise RuntimeError('Event loop is closed')

Expected behavior
executor.stop() should handle cancellation tasks safely, even if they were created in a different event loop.
Executor should be robust in multi-threaded and multi-loop environments without raising loop mismatch errors.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context

  • The current implementation assumes a single event loop.
  • _cancellation_tasks needs to manage tasks across loops safely or ignore tasks not in the current loop.
  • This may affect multi-agent and multi-session environments in AutoGen where loop context can vary.

(Optional) Suggested Fix

Ideally, the DockerCommandLineCodeExecutor should manage its loop affinity internally. This could involve:

Storing the event loop it was created/started on (e.g., self._loop = asyncio.get_running_loop() in init or start).
Ensuring all internal async operations, especially task creation (self._loop.create_task(...)) and the asyncio.gather call in stop(), explicitly use this stored self._loop.

Which packages was the bug in?

Python Extensions (autogen-ext)

AutoGen library version.

Python dev (main branch)

Other library version.

No response

Model used

No response

Model provider

None

Other model provider

No response

Python version

3.11

.NET version

None

Operating system

MacOS

@ekzhu ekzhu added the help wanted Extra attention is needed label Apr 25, 2025
@SongChiYoung SongChiYoung linked a pull request Apr 26, 2025 that will close this issue
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed needs-triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants