TwelvestoneProjects and Theory

Non blocking io for Haskell's hGetLine function


Sign in

  • Waiting for Godot ( 760 k posts )
    Just conversation.
  • Thunder Dome ( 23 k posts )
    Photoshop Tennis and Collabs.
  • Photography ( 5.8 k posts )
    For all you shutterbugs, sh...
  • Flash ( 18 k posts )
    ActionScripting to tweens, ...
  • Front End ( 6.1 k posts )
    general front end design an...
  • Back End ( 10 k posts )
    serverside scripting, progr...
  • Projects and Theory ( 13 k posts )
    This forum is for discussio...
  • FAQ ( 269 posts )
    All those nagging questions...
  • Design ( 17 k posts )
    graphics & all aspects of g...
  • Purgatory ( 3.6 k posts )
    12stone Jail, feel free to ...
Stinky
 
2006-08-10

While developing Depot we've run into a number of issues here and there. The latest one was quite frustrating but quite satisfying to solve. Haskell doesn't provide a library function for non blocking IO that is cross platform (hGetBufNonBlocking is posix only). What that means is that when a client connected to the depot server the server had to wait until either there was no content left OR the client disconnected. I.e. no forced timeouts.

That means that if a number of clients connected to depot at once and simply didn't transmit any data they could completely freeze the server-- bad news.

The hGetLine function reads input from a handle until it reaches a newline character. It's the ideal way to read in HTTP headers since they are terminated by CRLF (\r\n) characters. So I had to work out a non blocking version of hGetLine, here's what I came up with:

hGetLineNB h timeout = do buff <- atomically newEmptyTMVar tout <- atomically newEmptyTMVar rthr <- forkIO $ hgl h buff tthr <- forkIO $ countDown tout rthr line <- atomically (waitForData buff tout) catch (\e -> error toutmsg) finally mapM_ (killThread) [rthr,tthr] return line where toutmsg = "Timed out reading line in " ++ show timeout ++ "ms" hgl h buff = hGetLine h >>= atomically . putTMVar buff countDown tout rthr = threadDelay (timeout * 1000) >> atomically (putTMVar tout True) getData buff = takeTMVar buff >>= return checkTimeout tout = takeTMVar tout >> error "Time out" waitForData buff tout = getData buff orElse checkTimeout tout

What this function does is create two empty transaction variables: One for the line that is to be read in and one to hold a boolean timeout value; Then it starts two threads. One begins to read from the handle, putting the contents into the "buff" variable. The other waits for the length of time specified by the "timeout" value passed to the function.

The waitForData function checks the values of the two transaction variables, blocking the current thread of execution until one of them stops blocking. If the buff variable is filled then the contents are returned, if the tout variable is filled the checkTimeout function falls through and causes an error.

The waitForData call is wrapped in a "catch...finally" pair of functions that handle any exceptions by rethrowing them as timeout errors and ensure that both of the two threads that were started are killed before proceeding. The result is a reliable non-blocking getline function for Depot. I was quite happy with this although I'm sure that a more experienced Haskell programmer could point out ways to do this better.

Sorry, you must be a member to post to a conversation. Either log in or sign up to get involved.
TwelvestoneProjects and Theory

Non blocking io for Haskell's hGetLine function