在 Haskell 中处理 UserInterrupt 异常

发布于 2024-11-30 14:16:32 字数 1200 浏览 0 评论 0原文

我正在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

它按预期工作,但有一个例外。如果我启动解释器并按 Ctrl-Z + Enter,我会得到:

    >>> ^Z

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

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

    >>>
    UserInterruption
    >>> ^Z

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

非常感谢!

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:

    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:

    >>> ^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:

    >>>
    UserInterruption
    >>> ^Z

And it hangs and I can't use the interpreter anymore. However, if I press Ctrl-C again, the REPL unblocks. I searched a lot and I can't figure out the reason of it. Can anyone explain me?

Many thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

苯莒 2024-12-07 14:16:32

Control-C 处理不适用于 catch:可能与 GHC #2301:正确处理 SIGINT/SIGQUIT

这是一个工作测试用例,删除了评估器

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。我们可以使用 Control-D 在 Linux 上发出 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:正确处理 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 中所述

Control-C handling does not work with catch: may be related to GHC #2301: Proper handling of SIGINT/SIGQUIT

Here is a working testcase, with the evaluator removed:

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. Perhaps you are on Windows, where Control-Z is used for EOF. We can signal EOF on Linux with Control-D, which replicates the behavior you saw:

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

EOF is handled by your handle/onAbort function, and Control-C is handled by 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. As noted above, that Control-C handling does not work with catch may be related to GHC #2301: Proper handling of SIGINT/SIGQUIT.

The following version instead uses the Posix API to install a persistent signal handler for 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:

>>> ^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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文