Я запускаю программу ниже, а затем pkill ssh, чтобы завершить процесс ssh.
AFAICT все асинхронные работники:
- runcommand
- read_from_process
- write_to_process
все ушли. Программа печатает:
finally... worker 2 done
t1, t2 done
finally
finally .. DONE returncode=255
Но «ALL DONE» не выводится до тех пор, пока не будет напечатан возврат (и ps не подтвердит, что процесс Python не завершился).
import asyncio
import os
import pty
import sys
import termios
import traceback
import tty
from dataclass_boom.debug import asExceptionReporting
@asExceptionReporting
async def run_command(command):
# Create a pseudoterminal pair (master and slave)
master_fd, slave_fd = pty.openpty()
old_tty = termios.tcgetattr(sys.stdin)
try:
# Set the terminal in raw mode for capturing control characters
tty.setraw(sys.stdin.fileno())
# Start the process using the slave as stdin, stdout, stderr
process = await asyncio.create_subprocess_exec(
*command,
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd
)
# Close the slave in the parent process
os.close(slave_fd)
def cancel_task(task):
async def cancel():
task.cancel()
asyncio.create_task(cancel())
# Start tasks to handle reading from and writing to the terminal
async def onCancel(coro, proc):
try:
await coro
except asyncio.CancelledError as e:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
except OSError as e:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
# Cancel other end.
if proc == 1:
t2.cancel()
else:
t1.cancel()
# process.kill()
# print("".join(traceback.format_exception(type(e), e, e.__traceback__)), file=sys.stderr)
# cancel_task(t1)
# cancel_task(t2)
# print("".join(traceback.format_exception(type(e), e, e.__traceback__)), file=sys.stderr)
except Exception as e:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
print("".join(traceback.format_exception(type(e), e, e.__traceback__)), file=sys.stderr)
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
print(f"finally... worker {proc} done")
# cancel_task(t1)
# cancel_task(t2)
t1 = asyncio.create_task(onCancel(read_from_process(master_fd), 1))
t2 = asyncio.create_task(onCancel(write_to_process(master_fd), 2))
await asyncio.gather(t1, t2)
print("t1, t2 done")
await process.wait()
finally:
print("finally")
# Restore the terminal to its previous state
os.close(master_fd)
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
print(f"finally .. DONE returncode={process.returncode}")
async def read_from_process(master_fd):
"""Asynchronously read from the master side of the PTY and print to stdout. Note that stdin/stdout are combined."""
loop = asyncio.get_event_loop()
while True:
data = await loop.run_in_executor(None, os.read, master_fd, 1024)
if not data: # EOF
break
sys.stdout.write(data.decode())
sys.stdout.flush()
async def write_to_process(master_fd):
"""Asynchronously read from stdin and send input to the process."""
try:
loop = asyncio.get_event_loop()
while True:
try:
user_input = await loop.run_in_executor(None, sys.stdin.read, 1) # Read one character
if user_input == "\x04": # CTRL+D to signal EOF
os.write(master_fd, user_input.encode())
# print("CTRL+D")
break
if user_input == "":
break # EOF, such as CTRL-D
os.write(master_fd, user_input.encode())
except EOFError:
break
except Exception as e:
print("".join(traceback.format_exception(type(e), e, e.__traceback__)), file=sys.stderr)
# Run the main event loop with the command
command = ["ssh", "localhost"] # replace with your SSH command
asyncio.run(run_command(command))
print("ALL DONE")
sys.stdout.flush()
Подробнее здесь: https://stackoverflow.com/questions/791 ... run-return