10

私はHaskellのSchemeインタプリタのREPLを実装していますが、UserInterrupt、StackOverflow、HeapOverflowなどの非同期イベントを処理したいと思います。現在の演算UserInterruptが発生してStackOverflowのとHeapOverflowが発生したときに適切なメッセージを出力し、等次のように私はこれを実現:HaskellでのUserInterrupt例外の処理

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に続いて開始した場合しかし、私は得る:

>>> 
    UserInterruption 
    >>> ^Z 

そして、それがハングし、私はもう通訳を使用することはできません。しかし、Ctrl-Cをもう一度押すと、REPLはブロックを解除します。私は多くを検索し、私はそれの理由を理解することはできません。誰も私を説明することはできますか?

多くの感謝!

+0

Ctrl-Z捕らえられている。最初のCtrl-Cはキャプチャされますが、2番目のキャプチャはキャプチャされません。それはおそらく同じ問題です。 完全な動作テストケースでコードを変更できますか? F.e. '通訳者'の代わりに 'return'を使用し、適切なインポートが追加されています。 –

答えて

10

はControl-Cの処理はcatchでは動作しません。ここでGHC#2301: Proper handling of SIGINT/SIGQUIT

に関連している可能性がevaluatorを外して、作業テストケースである:Linuxの

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 

、コントロール-ZがされSjoerdが述べたように捕らえられていない。おそらくあなたはControl-ZがEOFのために使われているWindows上にいるでしょう。私たちは、あなたが見た振る舞いを複製コントロール-D、とLinux上でEOFを知らせることができます。

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

EOFは、あなたのhandle/onAbort関数によって処理され、制御-Cはcatch/onUserInterruptによって処理されます。ここでの問題は、repl関数が最初のControl-Cしか捕捉しないことです。handle/onAbort関数を削除することでテストケースを簡略化することができます。上記のとおり、Control-Cの処理はcatchで動作しません。これはGHC#2301: Proper handling of SIGINT/SIGQUITと関連している可能性があります。

は、次のバージョンではなく、コントロール-Cのための永続的なシグナルハンドラをインストールするのPosix APIを使用しています。

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

ない場合:コントロール-Csは複数回押されている処理できる

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 

Posix APIを使用して、Windows上に永続的なシグナルハンドラをインストールするには、次のように、キャッチされるたびに例外を再発生する必要があります。