Haskell cross-platform getChar with NoBuffering

Viewed 188

Haskell has a bug in Windows that seems fixed with WinIO in GHC9: getChar ignores NoBuffering mode - it does not evaluate until Enter is pressed.

Somebody suggested a workaround via a foreign call:

{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Char
import Foreign.C.Types
getHiddenChar = fmap (chr.fromEnum) c_getch
foreign import ccall unsafe "conio.h getch"
  c_getch :: IO CInt

It kind of works, but the problem is that it seems to block the output to console until some key is pressed, and I am concurrently reading key presses and writing to console from different threads.

Could you please help find a way (e.g. write a foreign function call - I do not know much about it, unfortunately) to read characters/keys from console, without buffering and echo, and without blocking output to console, in GHC 8.8.x (or at least 8.10.x), on Windows?

Thank you!

(Ideally I need a cross-platform way, but I can do it via conditional compilation, so if it only works on Windows it is ok. Above foreign call is not cross-platform already).

2 Answers

The essential problem with that particular FFI call is the unsafe. That makes the getch happen in the calling (system) thread. As a rule, foreign calls that may take some time should be marked safe. Even that is a bit problematic, because exceptions (e.g., from the user hitting Ctrl-C) are masked.

The true solution for such FFI calls is to mark them interruptible and then deal with interruption properly. You do this by checking whether the call was successful (based on its return value). If it wasn't, then you'd check errno for EINTR and try again in that case, as for any foreign call of that sort. But for an interruptible one, if you get EINTR you should deliver asynchronous exceptions using allowInterrupt before retrying.

Caveat: the above approach only works for foreign calls that are (mostly) making interruptible system calls, and that terminate with a failure status and set EINTR when interrupted. A foreign function performing an expensive mathematical computation, for example, is not generally interruptible at all.

Caution: mixing Handle-based I/O with raw FFI I/O on the same file descriptor sounds like a recipe for trouble.

Related