Haskell:getProcessStatus 阻塞 SIGINT

Haskell: getProcessStatus blocking SIGINT

我正在尝试在 Haskell 中编写一个简单的 shell,但我无法使信号处理正常工作。如果没有命令是 运行,发送 SIGINT 到 shell 进程会触发信号处理程序。但是当对 getProcessStatus 进行阻塞调用时,该信号将被忽略。立即向子进程发送信号当然会杀死子进程并进行阻塞调用 return。

Control.Concurrent.threadDelay 替换阻塞调用不会阻止信号,即一切都按预期工作。将阻塞标志替换为 getProcessStatusFalse 使函数 return 在子进程完成之前。

参考流程包:https://hackage.haskell.org/package/unix-2.7.1.0/docs/System-Posix-Process.html#v:getProcessStatus

相关代码如下,见(唯一)注释行。

main :: IO ()
main = do
    pidRef <- (newIORef [] :: IO (IORef [ProcessID]))
    setSigHant pidRef
    doPrompt pidRef

printPrompt :: IO ()
printPrompt = fdWrite stdError "λ➔ " >> return ()

doPrompt :: IORef [ProcessID] -> IO ()
doPrompt pidRef = do
    printPrompt
    tryLine <- try getLine :: IO (Either SomeException String)
    case tryLine of 
        Left _ -> do
            putStrLn ""
            exitSuccess
        Right line -> do
            tryCl <- try (parse line) :: IO (Either SomeException [Command])
            case tryCl of 
                Left e -> fdWrite stdError (show e ++ "\n") >> return ()
                Right cl -> 
                    if length cl > 0 && (cmd . head) cl == "cd" then
                        cd (head cl)
                    else do
                        execCommands pidRef cl (stdInput, stdOutput)
                        pids <- readIORef pidRef

                        -- This call to getProcessStatus blocks the signals
                        _ <- sequence $ map (getProcessStatus True False) pids
                        _ <- writeIORef pidRef []
                        return ()
            doPrompt pidRef

setSigHant :: (IORef [ProcessID]) -> IO ()
setSigHant pidRef = do
    let handler = Catch (sigIntHandler pidRef)
    installHandler sigINT handler Nothing
    return ()


sigIntHandler :: (IORef [ProcessID]) -> IO ()
sigIntHandler pidRef = do
    pids <- readIORef pidRef
    sequence_ $ map (signalProcess sigINT) pids
    fdWrite stdError "\n"
    printPrompt

getProcessStatus 在内部使用 interruptible FFI 调用。但为什么 -threaded 是必要的?

This blog post 关于在 Haskell 中处理 ctrl-c 表明信号处理是在一个单独的线程中完成的,该线程使用异步异常杀死主线程:

When the user hits Ctrl-C, GHC raises an async exception of type UserInterrupt on the main thread. This happens because GHC installs an interrupt handler which raises that exception, sending it to the main thread with throwTo.

但是异步包的 documentation 提到:

Different Haskell implementations have different characteristics with regard to which operations block all threads.

Using GHC without the -threaded option, all foreign calls will block all other Haskell threads in the system, although I/O operations will not. With the -threaded option, only foreign calls with the unsafe attribute will block all other threads.

所以也许这就是为什么在存在可中断的 ffi 调用时正确处理 SIGINT 需要 -threaded:否则,抛出异步异常的线程将被阻止 运行。