prompt-toolkit / prompt-toolkit/python-prompt-toolkit

PromptToolkitSSHServer fails with uvloop

Open
#951 6 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
10.6k
Forks
815
PR merge metrics
No merged PRs in 30d

Description

I found that running the example script with uvloop instead of the selector event loop fails to properly communicate with the client. Primary failure is that the buffer text does not get sent to the client until after a single line is processed. The other UI elements (e.g. progress bar, etc.) also behave unexpectedly. A number of errors are also logged. Lastly, I tested this with a ptpython repl. When a keyboard interrupt is used, this signal gets passed to the event loop, which causes the server to stop, which makes it unusable.

The following is a set of some of the errors displayed for the progress bar and UI elements:

Unhandled exception in event loop:                                                                           
  File "uvloop/cbhandles.pyx", line 66, in uvloop.loop.Handle._run                                           
  File "/home/derek/repo/python-prompt-toolkit/prompt_toolkit/eventloop/utils.py", line 68, in schedule      
    if not loop2._ready:  # type: ignore                                                                     
                                                                                                             
Exception 'Loop' object has no attribute '_ready'                                                            
                                                                                                             
Unhandled exception in event loop:                                                                           
  File "uvloop/cbhandles.pyx", line 66, in uvloop.loop.Handle._run                                           
  File "/home/derek/repo/python-prompt-toolkit/prompt_toolkit/eventloop/utils.py", line 68, in schedule      
    if not loop2._ready:  # type: ignore                                                                     
                                                                                                             
Exception 'Loop' object has no attribute '_ready'                                                            
ERROR:asyncio:Exception in callback <function call_soon_threadsafe.<locals>.schedule at 0x7f56c8071e18>      
handle: <Handle call_soon_threadsafe.<locals>.schedule>                                                      
Traceback (most recent call last):                                                                           
  File "uvloop/cbhandles.pyx", line 66, in uvloop.loop.Handle._run                                           
  File "/home/derek/repo/python-prompt-toolkit/prompt_toolkit/eventloop/utils.py", line 68, in schedule      
    if not loop2._ready:  # type: ignore                                                                     
AttributeError: 'Loop' object has no attribute '_ready'                                                      

The following is the test script that I used. I added a ptpython repl for good measure.

#!/usr/bin/env python
"""
Example of running a prompt_toolkit application in an asyncssh server.
"""
import asyncio
import logging

import asyncssh

from prompt_toolkit.shortcuts.dialogs import yes_no_dialog, input_dialog
from prompt_toolkit.shortcuts.prompt import PromptSession
from prompt_toolkit.shortcuts import print_formatted_text
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.contrib.ssh import PromptToolkitSSHServer

from pygments.lexers.html import HtmlLexer

from prompt_toolkit.lexers import PygmentsLexer

from prompt_toolkit.completion import WordCompleter

animal_completer = WordCompleter([
    'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison',
    'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphin',
    'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangaroo',
    'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey',
    'turtle',
], ignore_case=True)


async def interact() -> None:
    """
    The application interaction.

    This will run automatically in a prompt_toolkit AppSession, which means
    that any prompt_toolkit application (dialogs, prompts, etc...) will use the
    SSH channel for input and output.
    """
    prompt_session = PromptSession()

    # # Alias 'print_formatted_text', so that 'print' calls go to the SSH client.
    print = print_formatted_text

    print('We will be running a few prompt_toolkit applications through this ')
    print('SSH connection.\n')

    # Simple progress bar.
    with ProgressBar() as pb:
        for i in pb(range(50)):
            await asyncio.sleep(.1)

    # Normal prompt.
    text = await prompt_session.prompt_async("(normal prompt) Type something: ")
    print("You typed", text)

    # Prompt with auto completion.
    text = await prompt_session.prompt_async(
        "(autocompletion) Type an animal: ", completer=animal_completer)
    print("You typed", text)

    # prompt with syntax highlighting.
    text = await prompt_session.prompt_async("(HTML syntax highlighting) Type something: ",
                                             lexer=PygmentsLexer(HtmlLexer))
    print("You typed", text)

    # Show yes/no dialog.
    await prompt_session.prompt_async('Showing yes/no dialog... [ENTER]')
    await yes_no_dialog("Yes/no dialog", "Running over asyncssh").run_async()

    # Show input dialog
    await prompt_session.prompt_async('Showing input dialog... [ENTER]')
    await input_dialog("Input dialog", "Running over asyncssh").run_async()

    ##################### Add test for ptpython prompt
    from ptpython.repl import PythonRepl
    from prompt_toolkit.document import Document

    env = {
        "print": print
    }

    repl = PythonRepl(
        get_globals=lambda: env,
    )
    print("hello there!")
    while True:
        try:
            text = await asyncio.shield(
                repl.app.run_async()
            )
        except KeyboardInterrupt:
            # Abort - try again.
            repl.default_buffer.document = Document()
        except (EOFError, ValueError):
            return
        else:
            repl._process_text(text)


def main(port=8222):
    import uvloop
    uvloop.install()
    # Set up logging.
    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)

    loop = asyncio.get_event_loop()
    loop.run_until_complete(
        asyncssh.create_server(
            lambda: PromptToolkitSSHServer(interact),
            "",
            port,
            server_host_keys=["./foo.key"],
        )
    )
    loop.run_forever()


if __name__ == "__main__":
    main()

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided SSH example and prompt_toolkit/eventloop/utils.py, especially the schedule logic reported at line 68. Run the example with uvloop and observe the delayed buffer output, UI errors, and KeyboardInterrupt behavior in the ptpython REPL. Done means the SSH client communicates promptly, UI elements work without logged event-loop errors, and interrupts do not stop the server.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.