2010-02-27 28 views
13

Tôi đã xây dựng một vòng lặp đọc-in-in thực sự đơn giản trong Haskell để bắt Control-C (UserInterrupt). Tuy nhiên, bất cứ khi nào tôi biên dịch và chạy chương trình này, nó luôn luôn bắt đầu Control-C đầu tiên và luôn luôn hủy bỏ trên Control-C thứ hai với mã thoát 130. Không quan trọng bao nhiêu dòng đầu vào tôi đưa ra trước và giữa hai Control-Cs, nó luôn luôn xảy ra theo cách này. Tôi biết tôi phải thiếu một cái gì đó đơn giản ... xin vui lòng giúp đỡ, cảm ơn!Bắt ngoại lệ Control-C trong GHC (Haskell)

Lưu ý: đây là trường hợp ngoại lệ cơ bản-4, vì vậy Control.Exception chứ không phải Control.OldException.

import Control.Exception as E 
import System.IO 

main :: IO() 
main = do hSetBuffering stdout NoBuffering 
      hSetBuffering stdin NoBuffering 
      repLoop 

repLoop :: IO() 
repLoop 
    = do putStr "> " 
     line <- interruptible "<interrupted>" getLine 
     if line == "exit" 
      then putStrLn "goodbye" 
      else do putStrLn $ "input was: " ++ line 
        repLoop 

interruptible :: a -> IO a -> IO a 
interruptible a m 
    = E.handleJust f return m 
    where 
    f UserInterrupt 
     = Just a 
    f _ 
     = Nothing 
+0

Mã này sẽ không biên dịch với GHC 6.8, nhập 'Control.Exception' và' IO'. –

+0

@Norman, GHC 6.12 * là * out. Nó không được bao gồm trong Nền tảng Haskell, nhưng nó đã có sẵn cho Arch và Debian không ổn định. –

+0

Tại sao không cài đặt trình xử lý tín hiệu của riêng bạn? http://therning.org/magnus/archives/285 –

Trả lời

4

Tuyên bố từ chối: Tôi không quen với nội dung GHC và câu trả lời của tôi dựa trên việc đọc mã nguồn, đọc nhận xét và đoán.

Các main chức năng bạn xác định là trong thực tế được bao bọc bởi runMainIO quy định tại GHC.TopHandler (điều này là tiếp tục khẳng định bằng cách nhìn vào TcRnDriver.lhs):

-- | 'runMainIO' is wrapped around 'Main.main' (or whatever main is 
-- called in the program). It catches otherwise uncaught exceptions, 
-- and also flushes stdout\/stderr before exiting. 
runMainIO :: IO a -> IO a 
runMainIO main = 
    do 
     main_thread_id <- myThreadId 
     weak_tid <- mkWeakThreadId main_thread_id 
     install_interrupt_handler $ do 
      m <- deRefWeak weak_tid 
      case m of 
       Nothing -> return() 
       Just tid -> throwTo tid (toException UserInterrupt) 
     a <- main 
     cleanUp 
     return a 
    `catch` 
     topHandler 

install_interrupt_handler được định nghĩa là:

install_interrupt_handler :: IO() -> IO() 
#ifdef mingw32_HOST_OS 
install_interrupt_handler handler = do 
    _ <- GHC.ConsoleHandler.installHandler $ 
    Catch $ \event -> 
     case event of 
      ControlC -> handler 
      Break -> handler 
      Close -> handler 
      _ -> return() 
    return() 
#else 
#include "rts/Signals.h" 
-- specialised version of System.Posix.Signals.installHandler, which 
-- isn't available here. 
install_interrupt_handler handler = do 
    let sig = CONST_SIGINT :: CInt 
    _ <- setHandler sig (Just (const handler, toDyn handler)) 
    _ <- stg_sig_install sig STG_SIG_RST nullPtr 
    -- STG_SIG_RST: the second ^C kills us for real, just in case the 
    -- RTS or program is unresponsive. 
    return() 

Trên Linux, stg_sig_install là hàm C gọi ra số sigaction. Thông số STG_SIG_RST được dịch sang SA_RESETHAND. Trên Windows, mọi thứ được thực hiện khác nhau, điều này có thể giải thích sự quan sát của ja.

3

Giải pháp đáng tin cậy nhất đối với tôi (ít nhất là trên Linux), đã cài đặt trình xử lý tín hiệu bằng System.Posix.Signals. Tôi đã hy vọng cho một giải pháp mà sẽ không yêu cầu điều này, nhưng lý do thực sự tôi đăng câu hỏi là tôi muốn biết lý do tại sao GHC cư xử theo cách nó đã làm. Như được giải thích trên #haskell, một giải thích có khả năng là GHC hoạt động theo cách này để người dùng luôn có thể Control-C một ứng dụng nếu nó bị treo. Tuy nhiên, sẽ tốt hơn nếu GHC cung cấp một cách để ảnh hưởng đến hành vi này mà không có phương pháp cấp thấp hơn mà chúng tôi đã sử dụng để :).

7

Wei Hu là chính xác; hệ thống thời gian chạy Haskell cố ý hủy bỏ chương trình khi nhấn nút điều khiển thứ hai-C. Để có được hành vi có thể mong đợi:

import Control.Exception as E 
import Control.Concurrent 
import System.Posix.Signals 

main = do 
    tid <- myThreadId 
    installHandler keyboardSignal (Catch (throwTo tid UserInterrupt)) Nothing 
    ... -- rest of program