10

Tôi đang triển khai REPL cho một trình thông dịch Đề án trong Haskell và tôi muốn xử lý một số sự kiện không đồng bộ như UserInterrupt, StackOverflow, HeapOverflow, v.v ... Về cơ bản, tôi muốn dừng tính toán hiện nay khi UserInterrupt xảy ra và in một thông điệp phù hợp khi StackOverflow và HeapOverflow xảy ra, vv tôi thực hiện điều này như sau:Xử lý ngoại lệ UserInterrupt trong 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 

Nó hoạt động như mong đợi với một ngoại lệ. Nếu tôi bắt đầu phiên dịch và nhấn Ctrl-Z + Enter, tôi nhận được:

>>> ^Z 

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

Đúng vậy. Nhưng nếu tôi bắt đầu phiên dịch và nhấn Ctrl-C, tiếp theo là Ctrl-Z + Enter, tôi nhận được:

>>> 
    UserInterruption 
    >>> ^Z 

Và nó bị treo và tôi không thể sử dụng thông dịch viên nữa. Tuy nhiên, nếu tôi nhấn lại Ctrl-C, thì REPL sẽ mở khóa. Tôi đã tìm kiếm rất nhiều và tôi không thể tìm ra nguyên nhân của nó. Bất cứ ai có thể giải thích cho tôi?

Rất cám ơn! xử lý

+0

Tôi chưa bao giờ thấy Ctrl-Z bị bắt. Ctrl-C đầu tiên được chụp, nhưng cái thứ hai thì không. Đó có lẽ là vấn đề tương tự. Bạn có thể thay đổi mã của mình trong một testcase hoạt động hoàn chỉnh không? F.e. 'return' thay vì 'interpreter' stdin '' và với nhập thích hợp được thêm vào. –

Trả lời

10

Control-C không làm việc với catch: có thể liên quan đến GHC#2301: Proper handling of SIGINT/SIGQUIT

Đây là một testcase làm việc, với evaluator loại bỏ:

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 

Trên Linux, Control-Z là không bị bắt gặp như Sjoerd đã đề cập. Có lẽ bạn đang ở trên Windows, nơi Control-Z được sử dụng cho EOF. Chúng tôi có thể báo hiệu EOF trên Linux với Control-D, mà tái tạo hành vi mà bạn thấy:

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

EOF được xử lý bởi chức năng handle/onAbort của bạn, và Control-C được xử lý bởi catch/onUserInterrupt. Vấn đề ở đây là chức năng repl của bạn sẽ chỉ bắt được Control-C đầu tiên - có thể đơn giản hóa testcase bằng cách xóa hàm handle/onAbort. Như đã nói ở trên, việc xử lý Control-C không hoạt động với catch có thể liên quan đến GHC#2301: Proper handling of SIGINT/SIGQUIT.

Phiên bản sau đây thay vì sử dụng API Posix để cài đặt một handler tín hiệu dai dẳng cho 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 

mà có thể xử lý Control-Cs bị ép nhiều lần:

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

>>> ^C 
keyboardSignal 

Nếu không bằng cách sử dụng API Posix, việc cài đặt trình xử lý tín hiệu liên tục trên Windows yêu cầu phải tăng lại ngoại lệ mỗi khi bị bắt, như được mô tả trong http://suacommunity.com/dictionary/signals.php