Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

writer thread #1104

Merged
merged 10 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from contextlib import redirect_stderr, redirect_stdout
from datetime import datetime
from pathlib import Path, PurePath
from queue import Queue
from ._profile import timer
import threading
from time import perf_counter
from typing import (
Any,
Expand Down Expand Up @@ -135,6 +138,45 @@ def flush(self) -> None:
pass


class _WriterThread(threading.Thread):
willmcgugan marked this conversation as resolved.
Show resolved Hide resolved
"""A thread / file-like to do writes to stdout in the background."""

def __init__(self) -> None:
super().__init__(daemon=True)
self._queue: Queue[str | None | threading.Event] = Queue(16)
self._file = sys.__stdout__

def write(self, text: str) -> None:
self._queue.put(text)

def isatty(self) -> bool:
return True

def fileno(self) -> int:
return self._file.fileno()

def flush(self) -> None:
event = threading.Event()
self._queue.put(event)
event.wait()
self._file.flush()

def run(self) -> None:
write = self._file.write
while True:
text: str | threading.Event | None = self._queue.get()
if isinstance(text, threading.Event):
text.set()
continue
if text is None:
break
write(text)

def stop(self) -> None:
self._queue.put(None)
self.join()


CSSPathType = Union[str, PurePath, List[Union[str, PurePath]], None]


Expand Down Expand Up @@ -192,8 +234,17 @@ def __init__(
no_color = environ.pop("NO_COLOR", None)
if no_color is not None:
self._filter = Monochrome()

self._writer_thread: _WriterThread | None = None
if sys.__stdout__ is None:
file = _NullFile()
else:
self._writer_thread = _WriterThread()
self._writer_thread.start()
file = self._writer_thread

self.console = Console(
file=sys.__stdout__ if sys.__stdout__ is not None else _NullFile(),
file=file,
markup=False,
highlight=False,
emoji=False,
Expand Down Expand Up @@ -1502,6 +1553,9 @@ async def _shutdown(self) -> None:
if self.devtools is not None and self.devtools.is_connected:
await self._disconnect_devtools()

if self._writer_thread is not None:
self._writer_thread.stop()

async def _on_exit_app(self) -> None:
await self._message_queue.put(None)

Expand Down
2 changes: 1 addition & 1 deletion src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


# Screen updates will be batched so that they don't happen more often than 60 times per second:
willmcgugan marked this conversation as resolved.
Show resolved Hide resolved
UPDATE_PERIOD: Final = 1 / 60
UPDATE_PERIOD: Final[float] = 1 / 120


@rich.repr.auto
Expand Down