14

我在 Haskell 中构建了一个非常简单的 read-eval-print-loop 来捕获 Control-C(UserInterrupt)。但是,每当我编译和运行这个程序时,它总是会捕获第一个 Control-C 并总是在第二个 Control-C 上中止,退出代码为 130。无论我在两者之前和之间输入多少行输入都无关紧要Control-Cs,它总是以这种方式发生。我知道我一定遗漏了一些简单的东西......请帮忙,谢谢!

注意:这是基于 4 的异常,所以是 Control.Exception 而不是 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
4

3 回答 3

9

卫虎是对的;当按下第二个 control-C 时,Haskell 运行时系统会故意中止程序。要获得人们可能期望的行为:

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
于 2011-10-29T19:31:17.673 回答
5

免责声明:我不熟悉 GHC 的内部结构,我的回答是基于对源代码的 grep、阅读评论和猜测。

您定义的main函数实际上是由runMainIO定义的 in包装的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是一个调用sigaction. 参数STG_SIG_RST转换为SA_RESETHAND. 在 Windows 上,事情的处理方式不同,这可能解释了 ja 的观察。

于 2010-02-28T22:21:37.860 回答
3

对我来说最可靠的解决方案(至少在 Linux 上)是使用 System.Posix.Signals 安装信号处理程序。我希望有一个不需要这样做的解决方案,但我发布问题的真正原因是我想知道 GHC 的行为方式为何如此。正如#haskell 中所解释的那样,一个可能的解释是 GHC 的行为方式是这样的,因此如果应用程序挂起,用户始终可以 Control-C 应用程序。尽管如此,如果 GHC 提供了一种方法来影响这种行为,而不需要我们采用的较低级别的方法,那就太好了:)。

于 2010-03-01T11:28:29.020 回答