Search code examples
pythonposixnonblockingtty

Getting a BlockingIOError when printing or writting to stdout


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)


Solution

  • 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)