ronf / asyncssh

AsyncSSH is a Python package which provides an asynchronous client and server implementation of the SSHv2 protocol on top of the Python asyncio framework.
Eclipse Public License 2.0
1.55k stars 151 forks source link

RuntimeError: cannot reuse already awaited coroutine #702

Open starflows opened 6 days ago

starflows commented 6 days ago

The code below aborts with RuntimeError: cannot reuse already awaited coroutine as soon as the process ends.

To reproduce:

  1. run the code below
  2. in the "remote" terminal type exit
import os
import sys
import asyncio
import asyncssh

async def main():
    async with asyncssh.connect('localhost') as conn:
        async with conn.create_process(
            stdin=os.dup(sys.stdin.fileno()),
            stdout=os.dup(sys.stdout.fileno()),
            stderr=os.dup(sys.stderr.fileno()),
            term_type=os.environ.get('TERM'),
        ) as process:
            await process.wait()

asyncio.run(main())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "<stdin>", line 3, in main
  File "/home/test/.venv/lib/python3.12/site-packages/asyncssh/misc.py", line 308, in __aexit__
    exit_result = await self._coro_result.__aexit__(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/test/.venv/lib/python3.12/site-packages/asyncssh/process.py", line 831, in __aexit__
    await self.wait_closed()
  File "/home/test/.venv/lib/python3.12/site-packages/asyncssh/process.py", line 1212, in wait_closed
    await task
RuntimeError: cannot reuse already awaited coroutine

The code does not raise a RuntimeError with asyncssh version 2.14.2 It does raise the error with asyncssh version 2.17.0 as well as with the develop and master branch.

Maybe there is another way to connect your local terminal to a remote process?

Environment: asyncssh 2.17.0 python 3.12.6

ronf commented 6 days ago

Thanks for the report!

I'm having trouble reliably reproducing this with 2.17.0, but I am seeing it in the develop branch, so I'll take a closer look at it there. I reverted some changes related to channel cleanup which went into 2.15.0 and took a different approach in the "develop" branch that I thought had fixed it, but this seems to be a slightly different issue which is more closely tied to the process class, not the channel class.

As a potential workaround, I'd recommend not doing a process.wait() from inside an async context handler. Instead, try something like:

    async with asyncssh.connect('localhost') as conn:
        process =  conn.create_process(
            stdin=os.dup(sys.stdin.fileno()),
            stdout=os.dup(sys.stdout.fileno()),
            stderr=os.dup(sys.stderr.fileno()),
            term_type=os.environ.get('TERM'),
        )

        await process.wait()

That way, it's not attempting to run the aexit handler after the process has already been waited on. That SHOULD have been harmless, but it looks like I didn't clear out the _cleanup_tasks list after they are run and that may be what's causing the error you're seeing.

I haven't thoroughly tested this yet, but you might also try the following patch:

diff --git a/asyncssh/process.py b/asyncssh/process.py
index f2dbb3d..4d5d750 100644
--- a/asyncssh/process.py
+++ b/asyncssh/process.py
@@ -1210,6 +1210,8 @@ class SSHProcess(SSHStreamSession, Generic[AnyStr]):
         for task in self._cleanup_tasks:
             await task

+        self._cleanup_tasks = []
+

 class SSHClientProcess(SSHProcess[AnyStr], SSHClientStreamSession[AnyStr]):
     """SSH client process handler"""
starflows commented 2 days ago

Thank you for your reply @ronf !

The workaround and the patch both work and solved my issue.

ronf commented 2 days ago

Thanks for the confirmation -- this is now in the "develop" branch as commit bfa04aa.