
Hi, I recently spent a while debugging a problem where a program deadlocked in the non-threaded runtime, but ran fine in the threaded runtime, despite the program having no blocking FFI calls, and boiled it down to the following test case: module Main(main) where import System import System.IO import System.Process main = do (ih, oh, _, _) <- runInteractiveProcess "cat" [] Nothing Nothing comphszp <- hGetContents oh print (length comphszp) -- hClose ih -- with this line they both deadlock The reason for the deadlock is fairly straightforward; since ih isn't closed before comphszp is fully demanded, no progress can be made. My guess is that the threaded runtime is fine because ih is dead at that point, and the finalizer for ih gets a chance to run, closing ih. If I'm right, is it really sensible for every handle to have this finalizer? Closing a pipe has externally visible side effects beyond just the release of resources, so it doesn't seem like it should happen non-deterministically. Cheers, Ganesh

On 20/06/2009 21:46, Ganesh Sittampalam wrote:
I recently spent a while debugging a problem where a program deadlocked in the non-threaded runtime, but ran fine in the threaded runtime, despite the program having no blocking FFI calls, and boiled it down to the following test case:
module Main(main) where
import System import System.IO import System.Process
main = do (ih, oh, _, _) <- runInteractiveProcess "cat" [] Nothing Nothing comphszp <- hGetContents oh print (length comphszp) -- hClose ih -- with this line they both deadlock
The reason for the deadlock is fairly straightforward; since ih isn't closed before comphszp is fully demanded, no progress can be made. My guess is that the threaded runtime is fine because ih is dead at that point, and the finalizer for ih gets a chance to run, closing ih.
If I'm right, is it really sensible for every handle to have this finalizer? Closing a pipe has externally visible side effects beyond just the release of resources, so it doesn't seem like it should happen non-deterministically.
Interesting example. So what should we do? Don't finalize an FD if it might have an externally visible effect? That seems a bit extreme. Anyone else have any thoughts on this? Cheers, Simon

Simon Marlow wrote:
On 20/06/2009 21:46, Ganesh Sittampalam wrote:
If I'm right, is it really sensible for every handle to have this finalizer? Closing a pipe has externally visible side effects beyond just the release of resources, so it doesn't seem like it should happen non-deterministically.
Interesting example. So what should we do? Don't finalize an FD if it might have an externally visible effect? That seems a bit extreme.
I'm not really sure what to do either, otherwise I'd have suggested something concrete in my first email :-) One problem is that any move away from having the finalizer on every handle could easily lead to much more confusion than the current sitation. I guess an important question is what the benefits of having the finalizer are. Presumably it really is needed in the hGetContents case, since users should be able to just drop the lazy stream and have everything associated with it go away. Since other file operations are done eagerly, isn't it also reasonable to expect the user to exert explicit control over the handle in those cases? Cheers, Ganesh =============================================================================== Please access the attached hyperlink for an important electronic communications disclaimer: http://www.credit-suisse.com/legal/en/disclaimer_email_ib.html ===============================================================================

On Jun 23, 2009, at 09:41 , Simon Marlow wrote:
main = do (ih, oh, _, _) <- runInteractiveProcess "cat" [] Nothing Nothing comphszp <- hGetContents oh print (length comphszp) -- hClose ih -- with this line they both deadlock
Note that you can trigger this in any language; it's a classic beginner error with pipes (see, for example, the documentation for open2/open3 in Perl or Python for other examples). Detecting this in the type system would be an interesting idea, but de facto I think this is a "it hurts when I do this". -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Tue, 23 Jun 2009, Brandon S. Allbery KF8NH wrote:
On Jun 23, 2009, at 09:41 , Simon Marlow wrote:
main = do (ih, oh, _, _) <- runInteractiveProcess "cat" [] Nothing Nothing comphszp <- hGetContents oh print (length comphszp) -- hClose ih -- with this line they both deadlock
Note that you can trigger this in any language; it's a classic beginner error with pipes (see, for example, the documentation for open2/open3 in Perl or Python for other examples). Detecting this in the type system would be an interesting idea, but de facto I think this is a "it hurts when I do this".
Sure - but it hurts more when in some environments you get away with it and others you don't. Ganesh

On Jun 24, 2009, at 00:52 , Ganesh Sittampalam wrote:
On Tue, 23 Jun 2009, Brandon S. Allbery KF8NH wrote: On Jun 23, 2009, at 09:41 , Simon Marlow wrote:
main = do (ih, oh, _, _) <- runInteractiveProcess "cat" [] Nothing Nothing comphszp <- hGetContents oh print (length comphszp) -- hClose ih -- with this line they both deadlock
Note that you can trigger this in any language; it's a classic beginner error with pipes (see, for example, the documentation for open2/open3 in Perl or Python for other examples). Detecting this in the type system would be an interesting idea, but de facto I think this is a "it hurts when I do this".
Sure - but it hurts more when in some environments you get away with it and others you don't.
You'll still have that though, it'll just be a different set of environments. The next failure mode after this one is writing more than _PIPE_BUF down ih; you'll deadlock in the write when in blocking mode, in non-blocking mode it depends on how the runtime handles partial writes. -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

Brandon S. Allbery KF8NH wrote:
Sure - but it hurts more when in some environments you get away with it and others you don't.
You'll still have that though, it'll just be a different set of environments. The next failure mode after this one is writing more than _PIPE_BUF down ih; you'll deadlock in the write when in blocking mode, in non-blocking mode it depends on how the runtime handles partial writes.
I'd hope for consistency across the GHC runtimes though. Ganesh =============================================================================== Please access the attached hyperlink for an important electronic communications disclaimer: http://www.credit-suisse.com/legal/en/disclaimer_email_ib.html ===============================================================================

On Jun 24, 2009, at 02:27 , Sittampalam, Ganesh wrote:
Brandon S. Allbery KF8NH wrote:
Sure - but it hurts more when in some environments you get away with it and others you don't.
You'll still have that though, it'll just be a different set of environments. The next failure mode after this one is writing more than _PIPE_BUF down ih; you'll deadlock in the write when in blocking mode, in non-blocking mode it depends on how the runtime handles partial writes.
I'd hope for consistency across the GHC runtimes though.
You can hope, but I get the impression blocking/non-blocking maps to threaded/non-threaded respectively in which case all bets are off. (Unless the ghc runtime guarantees some specific behavior here.) -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

Brandon S. Allbery KF8NH wrote:
On Jun 24, 2009, at 02:27 , Sittampalam, Ganesh wrote:
Brandon S. Allbery KF8NH wrote:
Sure - but it hurts more when in some environments you get away with it and others you don't.
You'll still have that though, it'll just be a different set of environments. The next failure mode after this one is writing more than _PIPE_BUF down ih; you'll deadlock in the write when in blocking mode, in non-blocking mode it depends on how the runtime handles partial writes.
I'd hope for consistency across the GHC runtimes though.
You can hope, but I get the impression blocking/non-blocking maps to threaded/non-threaded respectively in which case all bets are off. (Unless the ghc runtime guarantees some specific behavior here.)
I believe that from the point of view of each Haskell thread, the IO will always be blocking (unless you explicitly used a non-blocking call), but the GHC runtime actually handles the IO using select and non-blocking calls, and so avoids blocking the entire program, and that this doesn't vary between the threaded and non-threaded runtimes. That's why I found it confusing that a program that just did IO could behave differently on the two runtimes, until I found the bug in it. Ideally, the program would have deadlocked on both runtimes. Ganesh =============================================================================== Please access the attached hyperlink for an important electronic communications disclaimer: http://www.credit-suisse.com/legal/en/disclaimer_email_ib.html ===============================================================================

On 24/06/2009 07:33, Brandon S. Allbery KF8NH wrote:
On Jun 24, 2009, at 02:27 , Sittampalam, Ganesh wrote:
Brandon S. Allbery KF8NH wrote:
Sure - but it hurts more when in some environments you get away with it and others you don't.
You'll still have that though, it'll just be a different set of environments. The next failure mode after this one is writing more than _PIPE_BUF down ih; you'll deadlock in the write when in blocking mode, in non-blocking mode it depends on how the runtime handles partial writes.
I'd hope for consistency across the GHC runtimes though.
You can hope, but I get the impression blocking/non-blocking maps to threaded/non-threaded respectively in which case all bets are off. (Unless the ghc runtime guarantees some specific behavior here.)
GHC uses non-blocking mode for all FDs that are "internal", i.e. not shared with external processes. This is the case for both the threaded and non-threaded RTSs. The difference between blocking/non-blocking mode and the size of PIPE_BUF should be mostly invisible to the Haskell programmer (although see http://hackage.haskell.org/trac/ghc/ticket/3316 for a bug we have in this area, which I fixed yesterday). There's one exception: if GHC is forced to use blocking mode on a particular FD, and you're using the non-threaded RTS, then a large write using hPutBuf may block all Haskell threads. There doesn't seem to be much we can do about this, except to use the threaded RTS or lobby the Linux kernel guys for a better API. Cheers, Simon

On Jun 24, 2009, at 05:04 , Simon Marlow wrote:
There's one exception: if GHC is forced to use blocking mode on a particular FD, and you're using the non-threaded RTS, then a large write using hPutBuf may block all Haskell threads. There doesn't seem to be much we can do about this, except to use the threaded RTS or lobby the Linux kernel guys for a better API.
POSIX async IO API? (Although last I checked many distributions built kernels without it.) -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On 24/06/2009 18:30, Brandon S. Allbery KF8NH wrote:
On Jun 24, 2009, at 05:04 , Simon Marlow wrote:
There's one exception: if GHC is forced to use blocking mode on a particular FD, and you're using the non-threaded RTS, then a large write using hPutBuf may block all Haskell threads. There doesn't seem to be much we can do about this, except to use the threaded RTS or lobby the Linux kernel guys for a better API.
POSIX async IO API? (Although last I checked many distributions built kernels without it.)
Yes, that would solve the problem, if it was available. Cheers, Simon
participants (4)
-
Brandon S. Allbery KF8NH
-
Ganesh Sittampalam
-
Simon Marlow
-
Sittampalam, Ganesh