Skip to content

RuntimeError: reentrant call inside _io.BufferedWriter when stopping server via Ctrl+C on Windows #15740

Description

@PierrunoYT

Bug: RuntimeError: reentrant call inside <_io.BufferedWriter> when stopping the server via Ctrl+C on Windows

Summary

On Windows, when the Streamlit server is stopped via Ctrl+C (SIGINT), the signal handler calls server.stop() which tries to write a "Stopping..." message to the console via click.secho → colorama's AnsiToWin32. This can race with the asyncio event loop's select.select() → buffered write to the same console handle, causing a RuntimeError: reentrant call inside <_io.BufferedWriter>.

Error

RuntimeError: reentrant call inside <_io.BufferedWriter>

Full traceback

Traceback (most recent call last):
  File "bootstrap.py", line 43, in signal_handler
    server.stop()
  File "server.py", line 529, in stop
    cli_util.print_to_cli("  Stopping...", fg="blue")
  File "cli_util.py", line 34, in print_to_cli
    click.secho(message, **kwargs)
  File "termui.py", line 690, in secho
    return echo(message, file=file, nl=nl, err=err, color=color)
  File "utils.py", line 321, in echo
    file.write(out)  # type: ignore
  File "_compat.py", line 543, in _safe_write
    return _write(s)
  File "ansitowin32.py", line 47, in write
    self.__convertor.write(text)
  File "ansitowin32.py", line 177, in write
    self.write_and_convert(text)
  File "ansitowin32.py", line 202, in write_and_convert
    self.write_plain_text(text, cursor, start)
  File "ansitowin32.py", line 211, in write_plain_text
    self.wrapped.flush()
  File "_winconsole.py", line 176, in write
    buf = get_buffer(b)
  File "_winconsole.py", line 112, in get_buffer
    out: Array[c_char] = buffer_type.from_address(buf.buf)
RuntimeError: reentrant call inside <_io.BufferedWriter>

Note: the signal handler at bootstrap.py:43 is invoked inside the asyncio event loop's _run_onceselect.select() call, so the reentrant write happens while the loop is already inside a buffered I/O operation.

Environment

  • OS: Windows 10/11 (10.0.26200)
  • Python: 3.11.3
  • Streamlit: latest (reproduced via aider --gui which launches Streamlit)
  • Virtual environment: No

Root cause

The signal handler (signal_handler in bootstrap.py) runs synchronously inside the asyncio event loop's select.select() call. It calls server.stop()cli_util.print_to_cli(" Stopping...", fg="blue")click.secho → colorama AnsiToWin32.writewrapped.flush()_winconsole.py get_buffer. This writes to the same _io.BufferedWriter that the event loop may already be writing to, triggering CPython's reentrancy guard: RuntimeError: reentrant call inside <_io.BufferedWriter>.

This is a race condition — it doesn't happen every time, only when the signal arrives while the event loop is mid-write to the console buffer.

Suggested fix

Avoid writing to the console from inside the signal handler. Options:

  1. Set a flag in the signal handler and do the actual stop + console output from the event loop. E.g.:

    def signal_handler(signum, frame):
        # Don't do I/O inside the signal handler; schedule the stop
        loop.call_soon_threadsafe(server.stop)
  2. Wrap the cli_util.print_to_cli call in a try/except to swallow RuntimeError during shutdown (less clean, but minimal change).

  3. Use signal.signal() with a coroutine-aware handler that doesn't write to the console synchronously.

Option 1 is the cleanest — signal handlers should never do I/O.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:serverRelated to the web serverarea:windowsRelated to Windows compatibilityfeature:cliRelated to the command line interfacepriority:P3Medium prioritystatus:confirmedBug has been confirmed by the Streamlit teamtype:bugSomething isn't working as expected

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions