我正在尝试在Haskell中编写一个简单的shell,但是我无法使信号处理工作。如果没有命令正在运行,则将SIGINT
发送到shell进程会触发信号处理程序。但是当对getProcessStatus
进行阻塞调用时,将忽略该信号。立即向子进程发送信号当然会杀死子进程并使阻塞呼叫返回。
用Control.Concurrent.threadDelay
替换阻止呼叫并不会阻止信号,即一切都按预期工作。使用getProcessStatus
将阻止标志替换为False
会使子函数在子进程完成之前返回。
参考流程包: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
答案 0 :(得分:1)
getProcessStatus
在内部使用interruptible FFI来电。但为什么-threaded
必要?
This blog post建议信号处理在一个单独的线程中完成,该线程使用异步异常杀死主线程:
当用户按Ctrl-C时,GHC会引发类型的异步异常 主线程上的UserInterrupt。这是因为GHC安装了一个 中断处理程序,它引发该异常,将其发送到main 与throwTo的线程。
但异步包的documentation提到:
不同的Haskell实现具有不同的特性 关于哪些操作阻止所有线程。
使用没有-threaded选项的GHC,所有外部调用都将被阻止 系统中所有其他Haskell线程,尽管I / O操作会 不。使用-threaded选项,只有外部调用具有不安全性 属性将阻止所有其他线程。
也许这就是为什么在存在可中断的ffi调用时正确处理SIGINT需要-threaded
的原因:否则,将阻止抛出异步异常的线程运行。