Я создал действительно простой read-eval-print-loop в Haskell, который ловит Ctrl-C (UserInterrupt). Однако каждый раз, когда я компилирую и запускаю эту программу, она всегда ловит первую Ctrl-C и всегда прерывается на второй Ctrl-C с кодом выхода 130. Не имеет значения, сколько строк входа я даю ему прежде и между этими двумя Управлением-Cs, это всегда происходит этот путь. Я знаю, что должен пропускать что-то простое..., помогите, Спасибо!
Примечание: это - с основой 4 исключения, так Управление. Исключение и не Управление. 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
Отказ от ответственности: я не знаком с внутренним устройством GHC и своим ответом основан на поиске исходного кода, чтении комментариев и предположениях.
Определенная вами функция main
фактически обернута в runMainIO
, определенную в GHC.TopHandler
(это дополнительно подтверждается просмотром 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
А install_interrupt_handler
определяется как:
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 ()
В Linux, stg_sig_install
- это функция C, которая вызывает sigaction
. Параметр STG_SIG_RST
преобразуется в SA_RESETHAND
. В Windows все устроено иначе, что, вероятно, объясняет наблюдение ja.
Самым надежным решением для меня (по крайней мере, в Linux) была установка обработчика сигналов с помощью System.Posix.Signals. Я надеялся на решение, которое не потребует этого, но настоящая причина, по которой я разместил вопрос, заключалась в том, что я хотел знать, почему GHC ведет себя именно так. Как объясняется на #haskell, вероятным объяснением является то, что GHC ведет себя таким образом, поэтому пользователь всегда может нажать Control-C на приложение, если оно зависнет. Тем не менее, было бы неплохо, если бы GHC предоставил способ повлиять на это поведение без несколько более низкоуровневого метода, к которому мы прибегли :).