简体   繁体   English

在Haskell中处理UserInterrupt异常

[英]Handling UserInterrupt exception in Haskell

I'm implementing a REPL for a Scheme interpreter in Haskell and I'd like to handle some async events like UserInterrupt, StackOverflow, HeapOverflow, etc... Basically, I'd like to stop the current computation when UserInterrupt occurs and print a suitable message when StackOverflow and HeapOverflow occur, etc. I implemented this as follows: 我正在为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

It works as expected with one exception. 它按预期工作,但有一个例外。 If I start the interpreter and press Ctrl-Z + Enter, I get: 如果我启动解释器并按Ctrl-Z + Enter,我会得到:

    >>> ^Z

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

That's correct. 那是对的。 But if I start the interpreter and press Ctrl-C followed by Ctrl-Z + Enter I get: 但是如果我启动解释器并按Ctrl-C然后按Ctrl-Z + Enter我得到:

    >>>
    UserInterruption
    >>> ^Z

And it hangs and I can't use the interpreter anymore. 它挂了,我不能再使用解释器了。 However, if I press Ctrl-C again, the REPL unblocks. 但是,如果我再次按Ctrl-C,则REPL解除阻止。 I searched a lot and I can't figure out the reason of it. 我搜索了很多,我无法弄清楚它的原因。 Can anyone explain me? 有人能解释一下吗?

Many thanks! 非常感谢!

Control-C handling does not work with catch : may be related to GHC #2301: Proper handling of SIGINT/SIGQUIT Control-C处理不适用于catch :可能与GHC#2301有关:正确处理SIGINT / SIGQUIT

Here is a working testcase, with the evaluator removed: 这是一个工作测试用例,删除了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

On Linux, Control-Z is not caught as Sjoerd mentioned. 在Linux上,Control-Z没有像Sjoerd所提到的那样被捕获。 Perhaps you are on Windows, where Control-Z is used for EOF. 也许你在Windows上,Control-Z用于EOF。 We can signal EOF on Linux with Control-D, which replicates the behavior you saw: 我们可以使用Control-D在Linux上发出EOF信号,它复制了您看到的行为:

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

EOF is handled by your handle/onAbort function, and Control-C is handled by catch/onUserInterrupt . EOF由handle/onAbort函数handle/onAbort ,Control-C由catch/onUserInterrupt处理。 The issue here is that your repl function will only catch the first Control-C -- the testcase can be simplified by removing the handle/onAbort function. 这里的问题是你的repl函数只会捕获第一个Control-C - 通过删除handle/onAbort函数可以简化测试用例。 As noted above, that Control-C handling does not work with catch may be related to GHC #2301: Proper handling of SIGINT/SIGQUIT . 如上所述,Control-C处理不适用于catch可能与GHC#2301:正确处理SIGINT / SIGQUIT有关

The following version instead uses the Posix API to install a persistent signal handler for Control-C: 以下版本使用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

which can handle Control-Cs being pressed multiple times: 可以处理多次按下Control-C:

>>> ^C
keyboardSignal

>>> ^C
keyboardSignal

>>> ^C
keyboardSignal

If not using the Posix API, installing a persistent signal handler on Windows requires re-raising the exception each time it is caught, as described in http://suacommunity.com/dictionary/signals.php 如果不使用Posix API,则在Windows上安装持久性信号处理程序需要在每次捕获时重新引发异常,如http://suacommunity.com/dictionary/signals.php中所述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM