bracket doesn't release resource when inside thread
using throwTo
Apparently the thread created with forkFinally
never gets an exception thrown at and thus the resource-releasing code of bracket
never gets executed.
We can fix this by doing this manually using throwTo threadId ThreadKilled
:
import Control.Exception ( bracket
, throwTo
, AsyncException(ThreadKilled)
)
import Control.Concurrent ( forkFinally
, threadDelay
)
main = do
threadId <- forkFinally
(writeToFile "first_file")
(\ex -> putStrLn $ "Exception occurred: " ++ show ex)
putStrLn "Press enter to exit"
_ <- getLine
throwTo threadId ThreadKilled
putStrLn "Bye!"
The root cause of the problem here is that when main
exits, your process just dies. It doesn't wait on any other threads that you've created to finish. So in your original code, you created a thread to write to the file, but it was not allowed to finish.
If you want to kill the thread but force it to clean up, then use throwTo
as you did here. If you want the thread to finish, you'll need to wait for that before main
returns. See How to force main thread to wait for all its child threads finish in Haskell
using async
Making getLine
block the main thread indefinitely doesn't play nice with nohup
: It will fail with
<stdin>: hGetLine: invalid argument (Bad file descriptor)
As an alternative to getLine
and throwTo
, you can use async
's functions:
import Control.Concurrent.Async ( withAsync, wait )
main = withAsync (writeToFile "first_file") wait
This enables running the program with nohup ./theProgram-exe &
¹, for example on a server via SSH.
async
also shines when running multiple tasks concurrently:
import Control.Concurrent.Async ( race_ )
main = race_ (writeToFile "first_file") (writeToFile "second_file")
The function race_
runs two tasks concurrently and waits until the first result arrives. With our non-terminating writeToFile
there won't ever be a regular result, but if one of the tasks throws an exception, the other will be cancelled too. This is useful for running an HTTP and an HTTPS server simultaneously, for example.
To shut down the program cleanly — giving threads a chance to free resources in bracket
— I send it the SIGINT signal:
pkill --signal SIGINT theProgram-exe
Handling SIGTERM
To also end threads gracefully on a SIGTERM, we can install a handler that will catch the signal:
import Control.Concurrent.Async ( withAsync
, wait
, cancel
, Async
)
import System.Posix.Signals
main = withAsync
(writeToFile "first_file")
(\asy -> do
cancelOnSigTerm asy
wait asy
)
cancelOnSigTerm :: Async a -> IO Handler
cancelOnSigTerm asy = installHandler
sigTERM
(Catch $ do
putStrLn "Caught SIGTERM"
-- Throws an AsyncCancelled exception to the forked thread, allowing
-- it to release resources via bracket
cancel asy
)
Nothing
Now, our program will release its resources in bracket
when receiving SIGTERM:
pkill theProgram-exe
Here's the equivalent for two concurrent tasks supporting SIGTERM:
import Control.Concurrent.Async ( withAsync
, wait
, cancel
, Async
, waitEither_
)
import System.Posix.Signals
main = raceWith_ cancelOnSigTerm
(writeToFile "first_file")
(writeToFile "second_file")
raceWith_ :: (Async a -> IO b) -> IO a -> IO a -> IO ()
raceWith_ f left right = withAsync left $ \a -> withAsync right $ \b -> do
f a
f b
waitEither_ a b
For more on the topic of asynchronous Haskell, have a peek at Parallel and Concurrent Programming in Haskell by Simon Marlow.
¹Call stack build
to get an executable at, for example, .stack-work/dist/x86_64-linux-tinfo6/Cabal-2.4.0.1/build/theProgram-exe/theProgram-exe
. You can get the path of this directory with stack path --local-install-root
.