exception - 在 Haskell 中处理 UserInterrupt 异常

标签 exception haskell exception-handling read-eval-print-loop interruption

我正在为 Haskell 中的 Scheme 解释器实现 REPL,我想处理一些异步事件,如 UserInterrupt、StackOverflow、HeapOverflow 等......基本上,我想在 UserInterrupt 发生时停止当前计算并打印发生 StackOverflow 和 HeapOverflow 等时的合适消息。我实现如下:

    repl evaluator = forever $ (do
        putStr ">>> " >> hFlush stdout
        out <- getLine >>= evaluator
        if null out
           then return ()
           else putStrLn out)
        `catch`
        onUserInterrupt

    onUserInterrupt UserInterrupt = putStrLn "\nUserInterruption"
    onUserInterrupt e = throw e

    main = do
        interpreter <- getMyLispInterpreter
        handle onAbort (repl $ interpreter "stdin")
        putStrLn "Exiting..."

    onAbort e = do
        let x = show (e :: SomeException)
        putStrLn $ "\nAborted: " ++ x

它按预期工作,但有一个异常(exception)。如果我启动解释器并按 Ctrl-Z + Enter,我会得到:
    >>> ^Z

    Aborted: <stdin>: hGetLine: end of file
    Exiting...

这是正确的。但是,如果我启动解释器并按 Ctrl-C 然后按 Ctrl-Z + Enter 我得到:
    >>>
    UserInterruption
    >>> ^Z

它挂了,我不能再使用解释器了。但是,如果我再次按 Ctrl-C,REPL 就会解除阻塞。我搜索了很多,我无法弄清楚它的原因。谁能解释我?

非常感谢!

最佳答案

Control-C 处理不适用于 catch : 可能与 GHC #2301: Proper handling of SIGINT/SIGQUIT 有关

这是一个有效的测试用例,带有 evaluator删除:

module Main where

import Prelude hiding (catch)

import Control.Exception ( SomeException(..),
                           AsyncException(..)
                         , catch, handle, throw)
import Control.Monad (forever)
import System.IO

repl :: IO ()
repl = forever $ (do
    putStr ">>> " >> hFlush stdout
    out <- getLine
    if null out
       then return ()
       else putStrLn out)
    `catch`
    onUserInterrupt

onUserInterrupt UserInterrupt = putStrLn "\nUserInterruption"
onUserInterrupt e = throw e

main = do
    handle onAbort repl
    putStrLn "Exiting..."

onAbort e = do
    let x = show (e :: SomeException)
    putStrLn $ "\nAborted: " ++ x

在 Linux 上,Control-Z 没有像 Sjoerd 提到的那样被捕获。也许您在 Windows 上,其中 Control-Z 用于 EOF。我们可以在 Linux 上使用 Control-D 发出 EOF 信号,它复制了您看到的行为:
>>> ^D
Aborted: <stdin>: hGetLine: end of file
Exiting...

EOF 由您的 handle/onAbort 处理函数,Control-C 由 catch/onUserInterrupt 处理.这里的问题是您的 repl函数只会捕获第一个 Control-C -- 可以通过删除 handle/onAbort 来简化测试用例功能。如上所述,Control-C 处理不适用于 catch可能与 GHC #2301: Proper handling of SIGINT/SIGQUIT 有关.

以下版本改为使用 Posix API 为 Control-C 安装持久信号处理程序:
module Main where

import Prelude hiding (catch)

import Control.Exception ( SomeException(..),
                           AsyncException(..)
                         , catch, handle, throw)
import Control.Monad (forever)
import System.IO
import System.Posix.Signals

repl :: IO ()
repl = forever $ do
    putStr ">>> " >> hFlush stdout
    out <- getLine
    if null out
       then return ()
       else putStrLn out

reportSignal :: IO ()
reportSignal = putStrLn "\nkeyboardSignal"

main = do
    _ <- installHandler keyboardSignal (Catch reportSignal) Nothing
    handle onAbort repl
    putStrLn "Exiting..."

onAbort e = do
    let x = show (e :: SomeException)
    putStrLn $ "\nAborted: " ++ x

它可以处理多次按下 Control-C:
>>> ^C
keyboardSignal

>>> ^C
keyboardSignal

>>> ^C
keyboardSignal

如果不使用 Posix API,在 Windows 上安装持久信号处理程序需要在每次捕获异常时重新引发异常,如 http://suacommunity.com/dictionary/signals.php 中所述。

关于exception - 在 Haskell 中处理 UserInterrupt 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7115386/

相关文章:

c - OpenGL 异常抛出 : read access violation, 窗口为 0xCCCCCCCC

python - 无法通过用户输入引发异常

haskell - 尝试用 Haskell 构建词法分析程序

haskell - 类型默认为 `floor . sqrt`

list - 将无限列表的表示理解为 haskell 中部分列表的限制

ios - 在 swift 4 中使用 NSSetUncaughtExceptionHandler

objective-c - NSLocking 的使用是否应该始终包含在@try/@finally 中?

java - 错误: Integers added together in successive uses of scanner class

ruby - 如何捕获 EventMachine 服务器上的顶级故障?

java - 没有catch的嵌套try if有什么用吗?