-
Notifications
You must be signed in to change notification settings - Fork 42
Interrupting interpreters #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Using forkIO and killThread explicitly can indeed interrupt the interpreter but will cause memory leaks. Don't know if this is a GHC issue. |
I'm dealing with a similar issue. |
Perhaps surprisingly, that's the expected behaviour for that program! Here is an expanded version of that program which explains what is going on: import Language.Haskell.Interpreter
import System.Timeout
-- |
-- >>> main
-- runInterpreter is done
-- "<timedout>
-- timeout while printing infinite thunk
main :: IO ()
main = do
interpreterResult <- timeout 200000 $ runInterpreter $ do
setImports ["Prelude"]
eval "sum [1..]"
putStrLn "runInterpreter is done"
case interpreterResult of
Nothing -> do
putStrLn "<timedout>"
putStrLn "timeout during runInterpreter"
Just (Left err) -> do
putStrLn "error while evaluating:"
print err
Just (Right infiniteThunk) -> do
maybeUnit <- timeout 200000 $ print infiniteThunk
case maybeUnit of
Nothing -> do
putStrLn "<timedout>"
putStrLn "timeout while printing infinite thunk"
Just () ->
putStrLn "infinite thunk successfully printed??" That is, the reason your |
And here is a variant which demonstrates that it is indeed possible to interrupt the interpreter using import Language.Haskell.Interpreter
import System.Timeout
-- |
-- >>> main
-- "<timedout>
-- timeout during runInterpreter
main :: IO ()
main = do
interpreterResult <- timeout 200000 $ runInterpreter $ do
setImports ["Prelude"]
infiniteThunk <- eval "sum [1..]"
lift $ print infiniteThunk
case interpreterResult of
Nothing -> do
putStrLn "<timedout>"
putStrLn "timeout during runInterpreter"
Just (Left err) -> do
putStrLn "error while evaluating:"
print err
Just (Right ()) -> do
putStrLn "runInterpreter terminated successfully??" @tycho01, did you happen to make the same mistake, or is there another problem with interrupting the interpreter which is not illustrated by @poscat0x04's program? |
@gelisam I'd wanna use the above solution, but I'm calling this in a loop, which somehow triggers #68, I think since So instead, I'd have my whole thing in an ... probably just comes down to #68 I guess? 🤷♀️ |
I don't think that's the problem;
You can't use -- |
-- >>> main
-- hello
main :: IO ()
main = void $ timeout 200000 $ runInterpreter $ do
setImports ["Prelude"]
ioAction <- interpret "putStrLn \"hello\"" (as :: IO ())
lift $ ioAction |
@gelisam so far I hadn't done threading yet. I'll try and see if that branches fixes it tho. I have indeed been using (For context, as I'm doing program synthesis, I'm basically producing a bunch of nonsensical programs that may or may not be well-behaved, such as the above -- that's one of the parts I'm trying to evaluate using |
That's very strange! On my machine, it gives a type error: import Language.Haskell.Interpreter
-- |
-- >>> main
-- won't compile:
-- <interactive>:1:19: error:
-- • Occurs check: cannot construct the infinite type: a ~ b -> a
-- Expected type: a -> a -> b -> a
-- Actual type: a -> a -> a
-- • In the second argument of ‘div’, namely ‘div’
-- In the expression: div (const const) div
main :: IO ()
main = do
interpreterResult <- runInterpreter $ do
setImports ["Prelude"]
typeOf "div (const const) div"
case interpreterResult of
Left (WontCompile [GhcError msg]) -> do
putStrLn "won't compile:"
putStrLn msg
Left err -> do
putStrLn "unexpected error:"
print err
Right type_ -> do
putStrLn "unexpected success:"
print type_ Do you have an example program which freezes for you? |
I'm getting the impression something may have gone wrong in my code somehow. I'll investigate and report back if it is about Hint. Thanks again. |
It seems that using |
@poscat0x04 yes, and this works in general, not just with hint: when you fork a new process (as opposed to a thread), all the memory allocated by that new process belongs to that new process, so the amount of memory allocated to the parent process will not increase and killing the child process will free that memory. There are quite a few disadvantages though: it's more heavyweight (forking a million threads is plausible but a million processes is not), and the parent and child processes are isolated (they can communicate via sockets and files but not via That being said, I have two follow-up questions.
|
It turns out there are no memory leaks :) I thought that hintTest :: IO ()
hintTest = do
interpreterResult <- timeout 2000000 $ runInterpreter $ do
setImports ["Prelude"]
infiniteThunk <- eval "product [1..]"
lift $ print infiniteThunk
case interpreterResult of
Nothing -> do
putStrLn "<timedout>"
putStrLn "timeout during runInterpreter"
Just (Left err) -> do
putStrLn "error while evaluating:"
print err
Just (Right ()) -> do
putStrLn "runInterpreter terminated successfully??"
hFlush stdout
performGC
getLine
pure () |
Let's look at an example program: import GHC.Stats
import System.Mem
printMemoryConsumption :: IO ()
printMemoryConsumption = do
performGC
live_bytes <- gcdetails_live_bytes . gc <$> getRTSStats
putStrLn $ show live_bytes ++ " bytes"
-- |
-- >>> main
-- 53368 bytes
-- 1000000
-- 40079880 bytes
-- 1000000
-- 80136 bytes
main :: IO ()
main = do
printMemoryConsumption
let xs = [1..1000000::Int]
print (last xs) -- force xs
printMemoryConsumption
print (length xs) -- xs is retained until this line
printMemoryConsumption I am using The memory usage is much higher at the second As you can see, the baseline is closer to 50 Kb (50 Mb in ghci) than to zero, because the runtime system needs to keep track of a bunch of things such as which threads are running and which blocks of memory are available for allocation. And once the memory is released, the memory doesn't go all the way back to the baseline. I'm not sure why that happens; if I duplicate that block of code several times (being careful to use a different number than 1000000 so that ghc doesn't optimize the other copies away), we soon reach a fixpoint, so maybe the runtime is simply keeping track of more blocks of memory it has acquired from the OS? Anyway, even a simple |
I'm trying to implement an "eval" command for a chatbot and I would like to set a timeout for the interpreter. But unfortunately, async exceptions don't work. For example, the following program will keep running until it gets killed by the oom killer:
I assume this is because the underlying GHC API uses foreign calls that cannot be interrupted. So are there any other ways of interrupting the interpreter?
The text was updated successfully, but these errors were encountered: