3

I'm trying to invoke an external process which writes to a temporary file which I obtain with withSystemTempFile. After the process exits, I'm able to cat the content of that file, but the application itself fails with openFile: resource busy (file is locked) error when trying to read that file with readFile. I followed suggestions in answers to this question and used the lazy version of readFile. Here's an example code:

module Main where

import Prelude hiding (readFile)
import System.Process (createProcess, shell)
import System.IO (Handle, hShow)
import System.IO.Temp (withSystemTempFile)
import System.IO.Strict (readFile)

main :: IO ()
main = withSystemTempFile "temp.txt" doSomeStuff
    where
        doSomeStuff :: FilePath -> Handle -> IO ()
        doSomeStuff tempFilePath tempFilePathHandle = do
            putStrLn tempFilePath
            createProcess $ shell $ "echo \"test\" >> " ++ tempFilePath
            getLine -- It's here just to stop program while I check that I can actually "cat" the temp file
            -- here I'm able to cat the temp file
            contents <- readFile tempFilePath -- here the command fails with /tmp/temp23900-0.txt: openFile: resource busy (file is locked)
            putStrLn contents

I'm still getting my head around Haskell, so I hope it's not something obvious, as I've run out of ideas. What gives?

mjarosie
  • 3,228
  • 2
  • 20
  • 31

2 Answers2

4

withSystemTempFile opens the temp file that it makes for you, so you don't need to open it again. readFile takes the file's name and not its handle, so you know it must be trying to open it itself. The equivalent of readFile but for a file you already have open is hGetContents, so to fix the problem, replace readFile tempFilePath with hGetContents tempFilePathHandle (and update your import accordingly).

3

This error is because withSystemTempFile locks the file upon entering. It gives you its handle in return (called tempFilePathHandle in your code), so you can read the file using the handle, like this:

contents <- hGetContents tempFilePathHandle

EDIT

This is because GHC internally implements a readers-writer lock to track which files it has opened, and what permissions are required. A readers-writer lock allows either only one writer (exclusive access), or multiple readers (shared access).

In this case, withSystemTempFile gets a writer lock on the temp file, and therefore readFile can't get the reader lock it needs (because, again, the writer lock prevents GHC from getting any reader locks on that file).

Here is a link to the C code implementing the lock. As @luqui suggests in the comment below, it perhaps isn't an optimal solution, especially since GHC doesn't request any OS-level locks, so processes like cat can still access and modify the files. It might make more sense in a lazy context, where reading and writing on a file would produce hard-to-predict results.

MikaelF
  • 3,518
  • 4
  • 20
  • 33
  • @luqui I appreciate your edit, but I wonder why, then, the OS would allow other processes (eg. `cat`) to access the file, but not Haskell. – MikaelF Apr 12 '20 at 21:52
  • 1
    @luqui Looking at [the code responsible for obtaining the lock](https://gitlab.haskell.org/ghc/ghc/-/blob/4898df1cc25132dc9e2599d4fa4e1bbc9423cda5/rts/FileLock.c#L79) in GHC, I can see it's implemented using a readers-writers lock, but it looks like that lock is only used internally, and it doesn't look like Haskell is requesting a lock from the OS, at least not from what I can see looking at the source code and from the output of `lslocks`. – MikaelF Apr 12 '20 at 22:25
  • 1
    I'm sorry, it was an "intuitive" correction because I had a hard time believing that GHC maintained an accounting of which files it had already opened (esp because you might open two "different" files which are hard links of each other, etc). But I appreciate you digging into the guts and defer to your more evidential reasoning. – luqui Apr 12 '20 at 22:45