4

I'm writing a REPL in Haskell. The basic idea looks like this:

repl :: IO ()
repl = do putStr ">> "
          hFlush stdout
          input <- getLine
          ...
          repl

I'd like to mimic some GHCi behaviour that when CTRL-C is pressed, the REPL discards the current line and starts a new empty line for user input. It has come to my mind that SIGINT raises UserInterrupt of AsyncException, which can be handled by catch. Therefore some modifications on the input line:

repl :: IO ()
repl = do putStr ">> "
          hFlush stdout
          input <- getLine `catch` handleCC
          repl

handleCC :: AsyncException -> IO String
handleCC _ = do putStr "\n>> "
                hFlush stdout
                getLine `catch` handleCC

Within handleCC, AsyncException will be handled once more by recursion, so surely I can interrupt the REPL infinite times, right...?

Of course I can't; a second CTRL-C still terminates the REPL. But why?

>> something^C
>> another^C
[Process exited 130]
Futarimiti
  • 551
  • 2
  • 18
  • See https://stackoverflow.com/a/6009807/1126841. – chepner Jul 23 '23 at 18:51
  • @chepner "When using catch your handler cannot be interrupted by an asynchroneous exception (i.e. thrown from another thread via throwTo). Attempts to raise an asynchroneous exception will block until your handler has finished running." Looks like I need to switch to `try` then? – Futarimiti Jul 24 '23 at 00:22

1 Answers1

1

After digging deeper, I believe I can conclude that catch does not properly handle asynchronous exceptions, neither try or similar. mask seems to be a potential solution but I found its usage a bit of sophisticated even with the documentation.

This answer provides a working solution by installing a persistent signal handler using the POSIX API. Integrated with my code:

import           Control.Monad        (forever)
import           System.IO            (hFlush, stdout)
import           System.Posix.Signals (Handler (Catch), installHandler, sigINT)

repl = do installHandler sigINT (Catch $ putStr "\n>> ") Nothing
          forever $ do putStr ">> "
                       hFlush stdout
                       input <- getLine
                       ...
Futarimiti
  • 551
  • 2
  • 18