简体   繁体   中英

Detecting I/O exceptions in a lazy String from hGetContents?

hGetContents returns a lazy String object that can be used in purely functional code to read from a file handle. If an I/O exception occurs while reading this lazy string, the underlying file handle is closed silently and no additional characters are added to the lazy string.

How can this I/O exception be detected?

As a concrete example, consider the following program:

import System.IO    -- for stdin

lengthOfFirstLine :: String -> Int
lengthOfFirstLine "" = 0
lengthOfFirstLine s  = (length . head . lines) s

main :: IO ()
main = do
    lazyStdin <- hGetContents stdin
    print (lengthOfFirstLine lazyStdin)

If an exception occurs while reading the first line of the file, this program will print the number of characters until the I/O exception occurs. Instead I want the program to crash with the appropriate I/O exception. How could this program be modified to have that behavior?

Edit: Upon closer inspection of the hGetContents implementation, it appears that the I/O exception is not ignored but rather bubbles up through the calling pure functional code to whatever IO code happened to trigger evaluation, which has the opportunity to then handle it. (I was not previously aware that pure functional code could raise exceptions.) Thus this question is a misunderstanding.

Aside: It would be best if this exceptional behavior were verified empirically. Unfortunately it is difficult to simulate a low level I/O error.

Lazy IO is considered to be a pitfall by many haskellers and as such is advised to keep away from. Your case colorfully describes why.

There is a non-lazy alternative of hGetContents function . It works on Text , but Text is also a generally preferred alternative to String . For convenience, there are modern preludes, replacing the String with Text : basic-prelude and classy-prelude .

Aside: It would be best if this exceptional behavior were verified empirically. Unfortunately it is difficult to simulate a low level I/O error.

I was wondering about the same thing, found this old question, and decided to perform an experiment.

I ran this little program in Windows, that listens for a connection and reads from it lazily:

import System.IO
import Network
import Control.Concurrent

main :: IO ()
main = withSocketsDo (do
    socket <- listenOn (PortNumber 19999)
    print "created socket"
    (h,_,_) <- accept socket
    print "accepted connection"
    contents <- hGetContents h
    print contents)

From a Linux machine, I opened a connection using nc :

nc -v mymachine 19999
Connection to mymachine 19999 port [tcp/*] succeeded!

And then used Windows Sysinternal's TCPView utility to forcibly close the connection. The result was:

Main.exe: <socket: 348>: hGetContents: failed (Unknown error)

It appears that I/O exceptions do bubble up.

A further experiment: I added a delay just after the hGetContents call:

...
contents <- hGetContents h
threadDelay (60 * 1000^2)   
print contents)

With this change, killing the connection doesn't immediately raise an exception because, thanks to lazy I/O, nothing is actually read until print executes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM