Opened 2 years ago

Last modified 8 months ago

#10731 new bug

System.IO.openTempFile is not thread safe on Windows

Reported by: NeilMitchell Owned by:
Priority: normal Milestone:
Component: libraries/base Version: 7.10.1
Keywords: Cc: ndmitchell@…, Phyx-
Operating System: Windows Architecture: Unknown/Multiple
Type of failure: Runtime crash Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Given the test program:

import Control.Concurrent
import System.IO
import Control.Monad
import System.Directory

main = do
    putStrLn "Starting"
    var <- newEmptyMVar
    tdir <- getTemporaryDirectory
    xs <- replicateM 10 $ do
        var <- newEmptyMVar
        flip forkFinally (\s -> do print s; putMVar var ()) $ do
            replicateM_ 100000 $ do
                (file, h) <- openTempFile tdir "test.txt"
                hClose h
                removeFile file
        return var
    mapM_ takeMVar xs

If I compile and run that with ghc --make TmpFile.hs -threaded && tmpfile +RTS -N5 I get:

Starting
Left C:\Users\NDMIT_~1\AppData\Local\Temp\: openTempFile: permission denied (Permission denied)
Left C:\Users\NDMIT_~1\AppData\Local\Temp\: openTempFile: permission denied (Permission denied)
...

I've reproduced this on GHC 7.8 and 7.10.1. We hit this in production about twice a day.

Change History (4)

comment:1 Changed 2 years ago by NeilMitchell

Note that the exceptions start happening within seconds - this doesn't seem to be a difficult race condition to hit. I'm on Windows 8.1, but it reproduces just as easily with Windows 7.

comment:2 Changed 2 years ago by NeilMitchell

I've avoided this issue in the extra package (https://github.com/ndmitchell/extra/blob/master/src/System/IO/Extra.hs) with three techniques:

  • To avoid races within a process, I have a single IORef which I increment to find unique names. That guarantees that a process will never clash with itself.
  • To help avoid cross-process races I start that IORef at a random starting point (using the start time plus Process Id would be ideal, but I couldn't get that easily, so I use the time).
  • If all else fails, I retry up to 5 times on IO errors.

With those features, I've yet to get a failure.

comment:3 Changed 8 months ago by rwbarton

Cc: Phyx- added

Phyx-, do you know of a threadsafe way to create and open a temporary file on Windows?

comment:4 Changed 8 months ago by Phyx-

GetTempFileName with uUnique 0 should be thread safe. The call will do most of the steps described in comment:2.

Note: See TracTickets for help on using tickets.