10

Estoy implementando un REPL para un intérprete Scheme en Haskell y me gustaría manejar algunos eventos asincrónicos como UserInterrupt, StackOverflow, HeapOverflow, etc. Básicamente, me gustaría detener el cómputo actual cuando se produce UserInterrupt e imprimir un mensaje adecuado cuando se producen Stackoverflow y desbordamiento de montículo, etc. he implementado de la siguiente manera:Manejo de la excepción de UserInterrupt en Haskell

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 

funciona como se esperaba, con una excepción. Si inicio el intérprete y presiono Ctrl + Z + Entrar, obtengo:

>>> ^Z 

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

Correcto. Pero si comienzo el intérprete y pulse Ctrl-C seguido de Ctrl-Z + Enter consigo:

>>> 
    UserInterruption 
    >>> ^Z 

Y se cuelga y no puedo usar el intérprete más. Sin embargo, si presiono Ctrl-C nuevamente, el REPL se desbloquea. Busqué mucho y no puedo entender el motivo. ¿Alguien puede explicarme?

¡Muchas gracias!

+0

Nunca veo Ctrl-Z ser atrapado. Se captura la primera Ctrl-C, pero la segunda no. Ese es probablemente el mismo problema. ¿Podría cambiar su código en un testcase de trabajo completo? F.e. 'return' en lugar de 'interpreter' stdin '' y con las importaciones apropiadas agregadas. –

Respuesta

10

manejo Control-C no funciona con catch: puede estar relacionado con GHC#2301: Proper handling of SIGINT/SIGQUIT

Aquí es un caso de prueba de trabajo, con el evaluator eliminado:

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 

En Linux, Control-Z es no atrapado como lo mencionó Sjoerd. Quizás esté en Windows, donde Control-Z se usa para EOF. Podemos indicar EOF en Linux con Control-D, que replica el comportamiento que viste:

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

EOF es manejado por su función handle/onAbort, y Control-C es manejado por catch/onUserInterrupt. El problema aquí es que su función repl solo captará el primer Control-C; el caso de prueba se puede simplificar eliminando la función handle/onAbort. Como se indicó anteriormente, que el manejo de Control-C no funciona con catch puede estar relacionado con GHC#2301: Proper handling of SIGINT/SIGQUIT.

La siguiente versión en su lugar utiliza la API Posix a instalar un controlador de señal persistente para el 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 

que puede manejar Control-Cs siendo presionado varias veces:

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

Si no utilizando la API de Posix, la instalación de un controlador de señal persistente en Windows requiere volver a generar la excepción cada vez que se detecta, como se describe en http://suacommunity.com/dictionary/signals.php

Cuestiones relacionadas