prompt-toolkit / python-prompt-toolkit

Library for building powerful interactive command line applications in Python
https://python-prompt-toolkit.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
9.1k stars 717 forks source link

`ValueError: I/O operation on closed file` while embedding an IPython within a subprocess #1662

Open bspdev opened 1 year ago

bspdev commented 1 year ago

Hello,

I'm having problems embedding an IPython console inside a subprocess when using prompt-toolkit. I am developing an application using a TUI based on pt. For the subprocesses I am using concurrent.futures.ProcessPoolExecutor.

It seems that, for some reason, the subprocesses close sys.stdin and I can't embed an IPython in them. It only works on the parent process. The following snippet is a minimal reproducible example. It creates a dummy pt Application, spawns a subprocess that tries to execute IPython.embed(). I left the call to stdin_from_terminal() since it is needed when not using the TUI.

$ python -V
Python 3.10.4
$ pip freeze | grep prompt-toolkit
prompt-toolkit==3.0.29
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.layout.containers import HSplit, Window
from prompt_toolkit.application import Application
from prompt_toolkit.layout.layout import Layout

from concurrent.futures import ProcessPoolExecutor, wait
import contextlib
import threading
import IPython
import sys
import os

# --------------------------------------- #

TTYNAMES = {"posix": "/dev/tty", "nt": "con"}

@contextlib.contextmanager
def stdin_from_terminal():
    try:
        ttyname = TTYNAMES[os.name]
    except KeyError:
        raise OSError(f"{os.name} does not support manually reading from the terminal")
    with open(ttyname) as tty:
        sys.stdin, oldstdin = tty, sys.stdin
        try:
            yield
        finally:
            sys.stdin = oldstdin

# --------------------------------------- #

def worker_fcn():
    print(f"Worker process => PID: {os.getpid()}")
    with stdin_from_terminal():
        IPython.embed()

def init_fcn(args, kwargs):
    pass

def main():

    app = Application(
        layout=Layout(HSplit(
            children = [Window(always_hide_cursor=True)],
            height=LayoutDimension.exact(0),
        )),
        mouse_support=False,
        full_screen=False,
    )
    # app.run()
    tui_thread = threading.Thread(
        target=app.run,
        daemon=True,
        kwargs={'in_thread': False},
    )
    tui_thread.start()

    print(f"Parent process => PID: {os.getpid()}")
    executor = ProcessPoolExecutor(
        max_workers=1,
        initializer=init_fcn,
        initargs=([], {})
    )

    job = executor.submit(worker_fcn)
    ex = job.exception()
    if ex is not None:
        print(f"Exception catched: {ex}")

    executor.shutdown(wait=True)
    app.exit()
    tui_thread.join()

    return 0

if __name__ == '__main__':
    main()
$ python ipython-subprocess.py
Parent process => PID: 12161
Worker process => PID: 12162
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.2.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: "It works as expected"
Out[1]: 'It works as expected'

In [2]: exit

$ python ipython-subprocess.py TUI
Parent process => PID: 12168
Worker process => PID: 12170
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.2.0 -- An enhanced Interactive Python. Type '?' for help.

Exception catched: I/O operation on closed file

Is it a Prompt Toolkit error? Is there an option I need to specify that I missed? Thank you very much in advance.

patrick-kidger commented 3 weeks ago

A couple years later, but I came across what looks like the same issue and found the solution: https://github.com/prompt-toolkit/ptpython/issues/581