Haskell:如何超时运行外部命令的函数 [英] Haskell: How to timeout a function that runs an external command
问题描述
我在一个函数中调用一个外部程序。现在我想超时这个功能,而不仅仅是外部程序。但是在函数超时之后,外部程序仍然在我的计算机上运行(我使用的是debian),直到完成其计算,之后它的线程仍然保留在进程表中,作为主程序的一个子线程,直到主程序终止。
I call an external program inside a function. Now i would like to timeout this function and not just the external program. But after the function times out, the external program is still running on my computer (i'm using debian) until it finishes its computation, after that its thread still remains in the process table as a subthread of my main program until the main program terminates.
下面是两个最简单的例子,说明我想要做什么。第一个使用unsafePerformIO,第二个完全在IO monad中。我并不真正依赖unsafePerformIO,但希望尽可能保留它。
Here are two minimal examples which illustrates what i would like to do. The first uses unsafePerformIO, the second is completely in the IO monad. I don't really depend on the unsafePerformIO but would like to keep it if possible. The described problem occures with and without it.
module Main where
import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process
main = do
x <- time $ timeoutP (1 * 1000000) $ mytest 2
y <- getLine
putStrLn $ show x ++ y
timeoutP :: Int -> a -> IO (Maybe a)
timeoutP t fun = timeout t $ return $! fun
mytest :: Int -> String
mytest n =
let
x = runOnExternalProgram $ n * 1000
in
x ++ ". Indeed."
runOnExternalProgram :: Int -> String
runOnExternalProgram n = unsafePerformIO $ do
-- convert the input to a parameter of the external program
let x = show $ n + 12
-- run the external program
-- (here i use "sleep" to indicate a slow computation)
answer <- readProcess "sleep" [x] ""
-- convert the output as needed
let verboseAnswer = "External program answered: " ++ answer
return verboseAnswer
没有unsafePerformIO
Without unsafePerformIO
module Main where
import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process
main = do
x <- time $ timeout (1 * 1000000) $ mytest 2
y <- getLine
putStrLn $ show x ++ y
mytest :: Int -> IO String
mytest n = do
x <- runOnExternalProgram $ n * 1000
return $ x ++ ". Indeed."
runOnExternalProgram :: Int -> IO String
runOnExternalProgram n = do
-- convert the input to a parameter for the external program:
let x = show $ n + 12
-- run the external program
-- (here i use "sleep" to indicate a slow computation):
answer <- readProcess "sleep" [x] ""
-- convert the output as needed:
let verboseAnswer = "External program answered: " ++ answer
return verboseAnswer
也许括号可以在这里帮助,但我真的不知道如何。
Maybe bracket can be of help here, but i don't really know how.
import Control.Concurrent
import Control.Exception
import System.Exit
import System.IO
import System.IO.Error
import System.Posix.Signals
import System.Process
import System.Process.Internals
safeCreateProcess :: String -> [String] -> StdStream -> StdStream -> StdStream
-> ( ( Maybe Handle
, Maybe Handle
, Maybe Handle
, ProcessHandle
) -> IO a )
-> IO a
safeCreateProcess prog args streamIn streamOut streamErr fun = bracket
( do
h <- createProcess (proc prog args)
{ std_in = streamIn
, std_out = streamOut
, std_err = streamErr
, create_group = True }
return h
)
-- "interruptProcessGroupOf" is in the new System.Process. Since some
-- programs return funny exit codes i implemented a "terminateProcessGroupOf".
-- (\(_, _, _, ph) -> interruptProcessGroupOf ph >> waitForProcess ph)
(\(_, _, _, ph) -> terminateProcessGroup ph >> waitForProcess ph)
fun
{-# NOINLINE safeCreateProcess #-}
safeReadProcess :: String -> [String] -> String -> IO String
safeReadProcess prog args str =
safeCreateProcess prog args CreatePipe CreatePipe Inherit
(\(Just inh, Just outh, _, ph) -> do
hPutStr inh str
hClose inh
-- fork a thread to consume output
output <- hGetContents outh
outMVar <- newEmptyMVar
forkIO $ evaluate (length output) >> putMVar outMVar ()
-- wait on output
takeMVar outMVar
hClose outh
return output
-- The following would be great, if some programs did not return funny
-- exit codes!
-- ex <- waitForProcess ph
-- case ex of
-- ExitSuccess -> return output
-- ExitFailure r ->
-- fail ("spawned process " ++ prog ++ " exit: " ++ show r)
)
terminateProcessGroup :: ProcessHandle -> IO ()
terminateProcessGroup ph = do
let (ProcessHandle pmvar) = ph
ph_ <- readMVar pmvar
case ph_ of
OpenHandle pid -> do -- pid is a POSIX pid
signalProcessGroup 15 pid
otherwise -> return ()
这解决了我的问题。它杀死了产卵过程中的所有子进程,并在正确的时间。
This solves my problem. It kills all child processes of the spawned process and that at the right time.
亲切的问候。
推荐答案
编辑:可以获得派生进程的pid。您可以使用以下代码来完成此操作:
it is possible to get the pid of the spawned process. You can do so with code like the following:
-- highly non-portable, and liable to change between versions
import System.Process.Internals
-- from the finalizer of the bracketed function
-- `ph` is a ProcessHandle as returned by createProcess
(\(_,_,_,ph) -> do
let (ProcessHandle pmvar) = ph
ph_ <- takeMVar pmvar
case ph_ of
OpenHandle pid -> do -- pid is a POSIX pid
... -- do stuff
putMVar pmvar ph_
<如果你杀了这个进程,而不是把开放的 ph _
放入mvar中,你应该创建一个合适的 ClosedHandle
和
If you kill the process, instead of putting the open ph_
into the mvar you should create an appropriate ClosedHandle
and put that back instead. It's important that this code executes masked (bracket will do this for you).
既然你有一个POSIX ID,你可以使用系统调用或者shell如果需要的话就杀掉。只要注意你的Haskell可执行文件不在同一个过程中ss组,如果你走这条路。
Now that you have a POSIX id you can use system calls or shell out to kill as necessary. Just be careful that your Haskell executable isn't in the same process group if you go that route.
/ end edit
/end edit
这种行为似乎有点合理。对于 timeout
的文档声称它对于非Haskell代码根本不起作用,事实上我没有看到它可以通用的任何方式。发生了什么是 readProcess
产生了一个新进程,但是在等待该进程的输出时超时。看起来, readProcess
在异常中止时不会终止产生的进程。这可能是 readProcess
中的一个错误,或者可能是设计中的错误。
This behavior seems sort of sensible. The documentation for timeout
claims that it doesn't work at all for non-Haskell code, and indeed I don't see any way that it could generically. What's happening is that readProcess
spawns a new process, but then is timed out while waiting for output from that process. It seems that readProcess
doesn't terminate the spawned process when it's aborted abnormally. This could be a bug in readProcess
, or it could be by design.
作为解决方法,我认为你你需要自己实现一些。 timeout
通过在派生线程中引发异步异常来工作。如果在异常处理程序中包装 runOnExternalProgram
,则会得到您想要的行为。
As a workaround, I think you'll need to implement some of this yourself. timeout
works by raising an async exception in a spawned thread. If you wrap your runOnExternalProgram
in an exception handler, you'll get the behavior you want.
关键函数这里是新的 runOnExternalProgram
,它是你的原始函数和 readProcess
的组合。如果产生一个新的 readProcess
会导致引发异常时产生的进程会更好(更模块化,更可重用,更易于维护),但我会将其作为
The key function here is the new runOnExternalProgram
, which is a combination of your original function and readProcess
. It would be better (more modular, more reusable, more maintainable) to make a new readProcess
that kills the spawned process when an exception is raised, but I'll leave that as an exercise.
module Main where
import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process
import Control.Exception
import System.IO
import System.IO.Error
import GHC.IO.Exception
import System.Exit
import Control.Concurrent.MVar
import Control.Concurrent
main = do
x <- time $ timeoutP (1 * 1000000) $ mytest 2
y <- getLine
putStrLn $ show x ++ y
timeoutP :: Int -> IO a -> IO (Maybe a)
timeoutP t fun = timeout t $ fun
mytest :: Int -> IO String
mytest n = do
x <- runOnExternalProgram $ n * 1000
return $ x ++ ". Indeed."
runOnExternalProgram :: Int -> IO String
runOnExternalProgram n =
-- convert the input to a parameter of the external program
let x = show $ n + 12
in bracketOnError
(createProcess (proc "sleep" [x]){std_in = CreatePipe
,std_out = CreatePipe
,std_err = Inherit})
(\(Just inh, Just outh, _, pid) -> terminateProcess pid >> waitForProcess pid)
(\(Just inh, Just outh, _, pid) -> do
-- fork a thread to consume output
output <- hGetContents outh
outMVar <- newEmptyMVar
forkIO $ evaluate (length output) >> putMVar outMVar ()
-- no input in this case
hClose inh
-- wait on output
takeMVar outMVar
hClose outh
-- wait for process
ex <- waitForProcess pid
case ex of
ExitSuccess -> do
-- convert the output as needed
let verboseAnswer = "External program answered: " ++ output
return verboseAnswer
ExitFailure r ->
ioError (mkIOError OtherError ("spawned process exit: " ++ show r) Nothing Nothing) )
这篇关于Haskell:如何超时运行外部命令的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!