This commit is contained in:
Joey Hess 2020-11-17 17:30:43 -04:00
parent 6b63278f31
commit 3dda21d292
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38

View file

@ -0,0 +1,57 @@
[[!comment format=mdwn
username="joey"
subject="""comment 3"""
date="2020-11-17T19:04:26Z"
content="""
One approach would be:
cancelOnExit :: ProcessHandle -> IO a -> IO (Maybe a)
The versions of this currently being used to work around the past
occurances of the problem wait for the process to exit, and then wait 2
seconds before canceling the action, to give it time to read anything that
was buffered in the pipe.
That's annoyingly racey, what if the system somehow massively slows
down for 2 seconds just after the process exits, and so buffered output is
lost? Doing this in a lot of places, not just a few targeted places where
maybe we didn't care too much about stderr anyway, does not feel safe.
There's GHC.IO.Handle.hWaitForInput that does not consume input from a
Handle, but checks if there is any available to read. Digging into what
that really means -- as well as checking its internal buffers, it calls a
[fdReady C function](https://gitlab.haskell.org/ghc/ghc/-/blob/ba4dcc7cb77a37486368911fec854d112a1db93c/libraries/base/cbits/inputReady.c)
which boils down to a `poll()`. So that seems like it could be used to
avoid the race. I think it would be safest to use it with 0, which avoids a
case where, it interrupted too long, it can return false when there's still
input.
It should be possible to use that to implement this:
readUntilExit :: ProcessHandle -> (Handle -> IO a) -> Handle -> IO a
Wait for the process to exit, and then check hWaitForInput, and as long as
there's input left, sleep breifly and loop. Once all buffered input as been
consumed by the action, hClose the handle to get the action to finish
reading from it.
One problem with this is that eg, this would throw an exception if it
closed the handle out from under hGetLine before it read anything:
unlessM (hIsEOF h) $
readUntilExit ph hGetLine h
So it's not entirely safe to drop this in just anywhere. Maybe it should
catch the EOF exception?
readUntilExitOrEOF :: ProcessHandle -> (Handle -> IO a) -> Handle -> IO (Maybe a)
That should be fine for things like hGetLine, but would still be unsafe
for hGetContents, which does *not* like having the handle it's reading
closed out from under it ("hGetContents: illegal operation (delayed read on closed handle)").
So maybe best to specialize it:
hGetLineUntilExitOrEOF :: ProcessHandle -> Handle -> IO (Maybe String)
I have a partial implementation on the ssh-hates-me branch.
"""]]