11

私は Haskell でスキーム インタープリター用の 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

1 つの例外を除いて、期待どおりに動作します。インタープリターを起動して Ctrl-Z + Enter を押すと、次のようになります。

    >>> ^Z

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

そのとおりです。しかし、インタープリターを起動して Ctrl-C を押してから Ctrl-Z + Enter を押すと、次のようになります。

    >>>
    UserInterruption
    >>> ^Z

そして、ハングして、インタープリターを使用できなくなりました。ただし、もう一度 Ctrl-C を押すと、REPL のブロックが解除されます。いろいろ調べたのですが、原因がわかりません。誰でも説明できますか?

どうもありがとう!

4

1 に答える 1

11

Control-C の処理が動作しないcatch: GHC に関連している可能性があります#2301: 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 では、Sjoerd が述べたように、Control-Z はキャッチされません。おそらく、Control-Z が EOF に使用される Windows を使用しているでしょう。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 の処理が機能しないことは、 GHC #2301: SIGINT/SIGQUIT の適切な処理にcatch関連している可能性があります。

次のバージョンでは、代わりに 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で説明されているように、キャッチされるたびに例外を再発生させる必要があります。

于 2011-08-29T02:18:08.140 に答える