Pytanie Wyjątek Catching Control-C w GHC (Haskell)


Zbudowałem naprawdę prostą pętlę odczytu-ewalu w Haskell, która przechwytuje Control-C (UserInterrupt). Jednak za każdym razem, gdy kompiluję i uruchamiam ten program, zawsze przechwytuje on pierwszą Kontrolę C i zawsze przerywa na drugiej Kontroli C z kodem zakończenia 130. Nie ma znaczenia, ile linii wejściowych podaję przed i między dwoma Control-Cs, zawsze tak się dzieje. Wiem, że brakuje mi czegoś prostego ... proszę, pomóż, dzięki!

Uwaga: dotyczy to wyjątków base-4, a więc wyjątku Control.Exception, a nie Control.OldException.

import Control.Exception as E
import System.IO

main :: IO ()
main = do hSetBuffering stdout NoBuffering
          hSetBuffering stdin NoBuffering
          repLoop

repLoop :: IO ()
repLoop
  = do putStr "> "
       line <- interruptible "<interrupted>" getLine
       if line == "exit"
          then putStrLn "goodbye"
          else do putStrLn $ "input was: " ++ line
                  repLoop

interruptible :: a -> IO a -> IO a
interruptible a m
  = E.handleJust f return m
  where
    f UserInterrupt
      = Just a
    f _
      = Nothing

14
2018-02-27 22:46


pochodzenie


Ten kod nie będzie nawet kompilował się z GHC 6.8, importowanie Control.Exception i IO. - Norman Ramsey
@Norman, GHC 6.12 jest na zewnątrz. Nie ma go w platformie Haskella, ale jest już dostępny dla Arch i Debian Unstable. - Wei Hu
Dlaczego nie zainstalować własnego programu obsługi sygnału? therning.org/magnus/archives/285 - Wei Hu
Przykro mi, Norman, że nie uwzględniłem importu. Ale poza tym, kod IS jest poprawny, dziękuję. - pheaver
"base" to jedna z bibliotek zainstalowanych z GHC. Możesz ulepszyć bazę oddzielnie od GHC, ale nie jestem pewien, czy jest to zalecane. W każdym razie GHC 6.10 i 6.12 udostępniają dwie wersje bazy. Wersja 3 używa starych wyjątków, a wersja 4 używa nowych wyjątków, ale zapewnia stare w klasie Control.OldException. - pheaver


Odpowiedzi:


Wei Hu ma rację; system wykonawczy Haskell celowo przerywa program po naciśnięciu drugiego przycisku sterującego C. Aby uzyskać zachowanie, którego można oczekiwać:

import Control.Exception as E
import Control.Concurrent
import System.Posix.Signals

main = do
  tid <- myThreadId
  installHandler keyboardSignal (Catch (throwTo tid UserInterrupt)) Nothing
  ... -- rest of program

7
2017-10-29 19:31





Zrzeczenie się: Nie jestem zaznajomiony z wewnętrznymi GHC, a moja odpowiedź opiera się na grepowaniu kodu źródłowego, czytaniu komentarzy i odgadywaniu.

The main funkcja, którą definiujesz, jest w rzeczywistości owinięta przez runMainIO określone w GHC.TopHandler (jest to dodatkowo potwierdzone przez sprawdzenie TcRnDriver.lhs):

-- | 'runMainIO' is wrapped around 'Main.main' (or whatever main is
-- called in the program).  It catches otherwise uncaught exceptions,
-- and also flushes stdout\/stderr before exiting.
runMainIO :: IO a -> IO a
runMainIO main = 
    do 
      main_thread_id <- myThreadId
      weak_tid <- mkWeakThreadId main_thread_id
      install_interrupt_handler $ do
           m <- deRefWeak weak_tid 
           case m of
               Nothing  -> return ()
               Just tid -> throwTo tid (toException UserInterrupt)
      a <- main
      cleanUp
      return a
    `catch`
      topHandler

I install_interrupt_handler jest zdefiniowany jako:

install_interrupt_handler :: IO () -> IO ()
#ifdef mingw32_HOST_OS
install_interrupt_handler handler = do
  _ <- GHC.ConsoleHandler.installHandler $
     Catch $ \event -> 
        case event of
           ControlC -> handler
           Break    -> handler
           Close    -> handler
           _ -> return ()
  return ()
#else
#include "rts/Signals.h"
-- specialised version of System.Posix.Signals.installHandler, which
-- isn't available here.
install_interrupt_handler handler = do
   let sig = CONST_SIGINT :: CInt
   _ <- setHandler sig (Just (const handler, toDyn handler))
   _ <- stg_sig_install sig STG_SIG_RST nullPtr
     -- STG_SIG_RST: the second ^C kills us for real, just in case the
     -- RTS or program is unresponsive.
   return ()

W systemie Linux stg_sig_install jest funkcją C, która wywołuje sigaction. Parametr STG_SIG_RST jest tłumaczone na SA_RESETHAND. W systemie Windows rzeczy robią się inaczej, co prawdopodobnie wyjaśnia obserwację ja.


4
2018-02-28 22:21





Najbardziej niezawodnym rozwiązaniem dla mnie (przynajmniej na Linuksie) było zainstalowanie obsługi sygnału za pomocą System.Posix.Signals. Miałem nadzieję na rozwiązanie, które nie wymagałoby tego, ale prawdziwym powodem, dla którego napisałem pytanie, było to, że chciałem się dowiedzieć, dlaczego GHC zachowało się tak, jak to zrobiło. Jak wyjaśniono w #haskell, prawdopodobnym wyjaśnieniem jest to, że GHC zachowuje się w ten sposób, aby użytkownik mógł zawsze kontrolować aplikację, jeśli się zawiesi. Mimo wszystko byłoby miło, gdyby GHC dostarczyło sposób, aby wpłynąć na to zachowanie bez nieco niższego poziomu metody, której użyliśmy :).


3
2018-03-01 11:28