-
Notifications
You must be signed in to change notification settings - Fork 7
unsafeLiftControlIO
The Control.Monad.Trans.Region.Unsafe
module exports the unsafeLiftControlIO
function and related functions. This function is very useful because it allows you to lift control operators like (alloca
, catch
, etc.) into a region. So why is this function unsafe?
It turns out there's only one situation where unsafeLiftControlIO
is unsafe: using it to lift forkIO
into a region. If you use it to lift other control operators your safe.
The following is an example what can go wrong when you lift forkIO
using unsafeLiftControlIO
into a region:
{-# LANGUAGE UnicodeSyntax #-}
module Main where
-- from base:
import Control.Concurrent ( ThreadId, forkIO, threadDelay )
import Control.Monad ( void )
import Prelude hiding ( putStrLn )
-- from transformers:
import Control.Monad.IO.Class ( liftIO )
-- from regions:
import Control.Monad.Trans.Region ( RegionT, runRegionT, RegionControlIO )
import Control.Monad.Trans.Region.Unsafe ( unsafeLiftControlIO )
-- from safer-file-handles:
import System.IO.SaferFileHandles ( openFile
, IOMode(ReadMode)
, hGetContents
, putStrLn
)
-- from pathtype:
import System.Path.Posix ( asAbsFile )
main ∷ IO ()
main = do
runRegionT region
threadDelay 1500000
region ∷ RegionControlIO pr ⇒ RegionT s pr ()
region = do
putStrLn "Running region"
h ← openFile (asAbsFile "/etc/passwd") ReadMode
_ ← unsafeForkIO $ do
putStrLn "Forked region"
liftIO $ threadDelay 1000000
putStrLn "Performing: hGetContents h..."
hGetContents h >>= putStrLn
liftIO $ threadDelay 500000
putStrLn "Exiting region"
unsafeForkIO ∷ RegionControlIO m ⇒ m α → m ThreadId
unsafeForkIO m = unsafeLiftControlIO $ \runInIO →
forkIO $ void $ runInIO m
When you run this program you get the following output:
$ ./unsafe
Running region
Forked region
Exiting region
Performing: hGetContents h...
unsafe: /etc/passwd: hGetContents: illegal operation (handle is closed)
Oops! What happens is that the main region
terminates earlier than the forked region, thereby automatically closing the resource h
as it should. However the forked region will later try to use the resource when it's already closed triggering the exception.
This is the reason why RegionT
doesn't directly have an instance for MonadControlIO
but uses the type class RegionControlIO
with the "hidden" method (from normal users) unsafeLiftControlIO
.
Conclusion: unsafeLiftControlIO
is safe to lift any control operator other than forkIO
.