2

I am getting the little documented and less present in the web "BlockingIOError" when writing to the tty from a program where I need the terminal to be in "raw" mode.

Switching the terminal to raw in Unix (Linux, Mac) is the way to get lines to show without printing a newline, and, more important, to read whatever is typed without having to wait for the <enter> key.

Check https://stackoverflow.com/a/6599441/108205 for a reliable way to do it in Python.

However, when printing data to the raw terminal, I would, at random times, have my program terminate with a BlockingIOError exception.

(I had even built a retry mechanism to workaround it, but sometimes it was not work-aroundable at all).

The exception shows up in some issues on github, with no assertion on how to fix it. The way to trigger it with my project is:

import terminedia as TM

with TM.keyboard:  # enables the use of TM.inkey() for realtime keyboard reading
    print("*" * 100_000)

(you can pip install terminedia to try)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • the fix/workaround on the accepted answer is now on the project, and used in a seamless way for all output. Install the old version terminedia==0.2.1 to be able to trigger the issue as in the question text. – jsbueno Jan 13 '22 at 12:53

1 Answers1

2

This snippet, creating a context manager which momentarily switches the tty back to "normal" blocking mode wrapping the parts that have a lot of output fixed the issue for me:

import fcntl
import os
...

class UnblockTTY:

    def __enter__(self):
        self.fd = sys.stdin.fileno()
        self.flags_save = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        flags = self.flags_save & ~os.O_NONBLOCK
        fcntl.fcntl(self.fd, fcntl.F_SETFL, flags)

    def __exit__(self, *args):
        fcntl.fcntl(self.fd, fcntl.F_SETFL, self.flags_save)

For completeness, the snippet in the question would be fixed with:

import terminedia as TM

with TM.keyboard:  
    with TM.terminal.UnblockTTY():
        print("*" * 100_000)

(although when using terminedia one should be using the "Screen" class rather or TM.print which are already wrapped)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • If unblocked, won't the write call return that not all bytes were written and some of the output will be lost? – user48956 Nov 10 '22 at 17:49
  • 1
    Maybe the name is reversed in the code there: the blocking mode is what works - Unblocking mode raises the exception. – jsbueno Nov 10 '22 at 17:57