Обработка исключений UserInterrupt в Haskell - PullRequest
10 голосов
/ 19 августа 2011

Я реализую REPL для интерпретатора Scheme в Haskell, и я хотел бы обрабатывать некоторые асинхронные события, такие как 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 разблокируется.Я много искал и не могу понять причину этого.Кто-нибудь может мне объяснить?

Большое спасибо!

1 Ответ

10 голосов
/ 29 августа 2011

Обработка 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 Control-Z не перехватывается, как упоминал Sjoerd.Возможно, вы находитесь в Windows, где Control-Z используется для EOF.Мы можем сигнализировать EOF в Linux с помощью Control-D, который повторяет поведение, которое вы видели:

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

В следующей версии вместо этого используется PosixAPI для установки обработчика постоянных сигналов для 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

Если не используется API Posix, установитеОбработчик постоянных сигналов в Windows требует повторного вызова исключения каждый раз, когда оно перехватывается, как описано в http://suacommunity.com/dictionary/signals.php

...