The language-puppet website.

Work with your manifests!

Non-strict IO Surprises

The language-puppet library has been created when I started to learn Haskell. As a consequence, it uses the dreaded String type to store all kind of textual values. It also uses the System.IO module for performing I/O. I was aware of the file descriptor leak problem that happens when you use readFile, so I chose for the following implementation for the Puppet file function:

1
2
3
file :: [String] -> IO (Maybe String)
file [] = return Nothing
file (x:xs) = catch (fmap Just (withFile x ReadMode hGetContents)) (\_ -> file xs)

This should return Just the content of the first readable file in the parameter list, or Nothing if there are none, and should not leak any file descriptor. Now that I am finalizing the hspuppetmaster binary, I can use my library to (try to) compute catalogs on my production systems, using the standard puppet agent -t --noop. It turned out that the file function was misbehaving. Testing it in GHCi illustrates the problem:

1
2
3
4
> file ["/nothing"]
Nothing
> file ["/nothing", "/etc/hosts"]
Just ""

It seems to work fine, except all file contents are empty. This behavior seems to be common knowledge among Haskellers, and is due to the fact that the file descriptor is closed before the output is evaluated. This is pretty horrible (and surprising), and what is even worse is my solution:

1
2
3
4
5
6
file (x:xs) = catch
    (fmap Just (withFile x ReadMode (\fh -> do
                                        { y <- hGetContents fh
                                        ; evaluate (length y)
                                        ; return y })))
    ((\_ -> file xs) :: SomeException -> IO (Maybe String))

It is a bit longer because of the use of the non-deprecated version of catch, and because it explicitly forces evaluation of the output of hGetContents. This behavior was extremely surprising to me, and I would like to thank the people on #haskell for their help in devising a correct version (mine was along the lines of !y <- hGetContents, which worked for my simple examples, but was certain to fail at some point). This is the only IRC channel I know of where people are at the same time active, always helpful, and knowledgeable.

Comments