[Haskell-cafe] Preventing leaked open file descriptors whencatching exceptions

Albert Y. C. Lai trebla at vex.net
Thu Feb 23 05:23:12 CET 2012


I cannot reproduce pretty much any claim made in this thread. Unless PIO 
does not mean System.IO.Posix.

I run "mkfifo hello" to create a named pipe. Then I run this program to 
keep trying to open for writing, non-blocking (without anyone at the 
read end initially):

import System.Posix.IO
import System.Posix.Types(Fd(..))
import qualified Control.Exception as E
import Control.Concurrent(threadDelay)

main = do
   E.handle
     (\e -> putStrLn ("caught exception: " ++ show (e :: E.IOException)))
     -- you can change IOException to SomeException too
     (do fd <- openFd "hello" WriteOnly Nothing
                 defaultFileFlags{nonBlock=True}
         case fd of Fd n -> putStrLn ("fd number " ++ show n)
         -- I deliberately leak fd
     )
   threadDelay 1500000
   main

openFd failures are successfully caught as exceptions; it does not 
return an Fd that stands for -1 when it fails. (Check its source code 
and chase down what throwErrnoPathIfMinus1Retry means.)

When it fails, it does not leak file descriptors. "lsof hello" shows 
nothing.

To force file descriptors to be leaked and see what lsof says, I then 
run "cat hello" as the read end while the above program is still 
running, so that openFd succeeds and I have something to leak. "lsof 
hello" successfully shows:

COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
f       3725 trebla    3w  FIFO    8,5      0t0 158922 hello
f       3725 trebla    4w  FIFO    8,5      0t0 158922 hello
f       3725 trebla    5w  FIFO    8,5      0t0 158922 hello
f       3725 trebla    6w  FIFO    8,5      0t0 158922 hello
cat     3726 trebla    3r  FIFO    8,5      0t0 158922 hello

My point is that if "openFd ... WriteOnly" leaks anything, you should be 
seeing 3w, 4w, etc., emphasis on "w". But you're seeing a ton of "r"s. 
Your leaker is some read-end code.

Ubuntu 11.04 x86 32-bit, kernel 2.6.38, GHC 6.12.3, 7.0.4, 7.2.1, 7.4.1

Lastly, the control structure

loop = handle (\e -> ... loop) job

is very problematic. Go to the haddock of Control.Exception, search for 
the string "The difference between using try and catch for recovery" to 
see why. You should use this:

loop = do
   lr <- try job
   case lr of
     Left e -> ... loop
     Right a -> return a

i.e., get out of the exception handler as soon as possible. (Thus, my 
use of putStrLn inside a handler is also questionable. But mine is a 
toy. I wouldn't do it in production code.)



More information about the Haskell-Cafe mailing list