basvandijk edited this page Oct 18, 2011 · 1 revision
Clone this wiki locally

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.