colored-traceback.py icon indicating copy to clipboard operation
colored-traceback.py copied to clipboard

Windows pyinstaller EXE - `_curses.error: lost sys.stdout`

Open C0rn3j opened this issue 11 months ago • 8 comments

Attempting to import the library within Windows EXE opened by double clicking, created by pyinstaller, I get the following:

Traceback (most recent call last):
  File "tauon/t_modules/t_main.py", line 348, in <module>
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "PyInstaller/loader/pyimod02_importers.py", line 384, in exec_module
  File "colored_traceback/always/__init__.py", line 2, in <module>
  File "colored_traceback/colored_traceback.py", line 78, in add_hook
  File "colored_traceback/colored_traceback.py", line 64, in __init__
  File "colored_traceback/colored_traceback.py", line 38, in _determine_formatter
  File "colored_traceback/colored_traceback.py", line 34, in _get_term_color_support
_curses.error: lost sys.stdout

Seems to me this function could use a sys.platform check instead?

https://github.com/staticshock/colored-traceback.py/blob/98e6dea7111f7681db823c518f18026374a832b9/colored_traceback/colored_traceback.py#L28-L35

The app in question is Tauon and current commits at the time - https://github.com/Taiko2k/Tauon/commit/c2420e26d2a41941c36a0f3b74a65551afe9f612 - has a prebuilt Windows binary with the issue.

I also previously managed to land on this but no clue how to repro at the moment, so that could just be some pyinstaller issue or an issue on my end:

(t_main.py:351)
Traceback (most recent call last):
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/tauon/t_modules/t_main.py", line 346, in <module>
    import colored_traceback.always
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/colored_traceback/always/__init__.py", line 2, in <module>
    add_hook(always=True)
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/colored_traceback/colored_traceback.py", line 78, in add_hook
    colorizer = Colorizer(style, colors, debug)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/colored_traceback/colored_traceback.py", line 64, in __init__
    self.formatter = _determine_formatter(style, colors, debug)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/colored_traceback/colored_traceback.py", line 38, in _determine_formatter
    colors = colors or _get_term_color_support()
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:/msys64/home/Yeet/Tauon/.venv/lib/python3.12/site-packages/colored_traceback/colored_traceback.py", line 34, in _get_term_color_support
    curses.setupterm()
_curses.error: setupterm: could not find terminfo database

C0rn3j avatar Dec 27 '24 15:12 C0rn3j

Hm, "lost sys.stdout" sounds vaguely like the stream got closed by whatever process owned it? What interface is even rendering your stdout in that case? Is it something terminal-like, or is it a windows installer output window?

As far as the second error, does this reproduce it: C:/msys64/home/Yeet/Tauon/.venv/Scripts/python.exe -c 'import curses; curses.setupterm()'

staticshock avatar Dec 27 '24 20:12 staticshock

What interface is even rendering your stdout in that case?

I have no idea, I just doubleclick the EXE there

C0rn3j avatar Dec 27 '24 21:12 C0rn3j

As for stdout, it seems to be caused by not checking whether stdout exists before trying to use it.

https://pyinstaller.org/en/stable/common-issues-and-pitfalls.html#sys-stdin-sys-stdout-and-sys-stderr-in-noconsole-windowed-applications-windows-only

C0rn3j avatar Jan 05 '25 11:01 C0rn3j

Adding a platform check seems bad. I'd rather test for specific functionality, if possible. I see two main issues here that are worth addressing:

  1. This package uses stderr throughout, but curses defaults to probing stdout under the hood. That's an unhealthy mismatch, fixable via curses.setupterm(fd=sys.stderr.fileno()).
  2. stdout as well as stderr are likely auto-closed immediately when you double-click on a PyInstaller file. Here's a reproduction of your error in a sterile environment:
$ python -c 'import sys; sys.stderr.close(); print(sys.stderr.fileno())'
object address  : 0x104372800
object refcount : 2
object type     : 0x104a49bc8
object type name: ValueError
object repr     : ValueError('I/O operation on closed file')
lost sys.stderr

Also reproducible via the test script:

$ python -c 'import sys; sys.stderr.close(); import test'
object address  : 0x100b09600
object refcount : 2
object type     : 0x100fd9bc8
object type name: ValueError
object repr     : ValueError('I/O operation on closed file')
lost sys.stderr

staticshock avatar Jan 06 '25 21:01 staticshock

The docs suggest to check if stderr and stdout are available, not to do a platform check:

if sys.stdout is None:
    ...
if sys.stderr is None:
    ...

Though it does look like it would be only necessary to check on Windows.

C0rn3j avatar Jan 06 '25 21:01 C0rn3j

Which docs is this from?

staticshock avatar Jan 06 '25 21:01 staticshock

The one I linked in the comment you replied to - https://github.com/staticshock/colored-traceback.py/issues/25#issuecomment-2571591298

image

C0rn3j avatar Jan 06 '25 21:01 C0rn3j

Haha, ok, yeah, so I guess the code should check for both a closed stream and a nulled out stream.

staticshock avatar Jan 06 '25 22:01 staticshock