You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
hGetContents does not close handle on exception #712
The documentation for Data.Text.IO.hGetContents promises
The 'Handle' is closed once the contents have been read, or if an exception is thrown.
However, this seems to not be the case:
importControl.ExceptionimportData.Text.IOqualifiedasTimportSystem.IOmain::IO()
main =do
handle <- openFile "/bin/bash"ReadMode
result <- try @IOException$T.hGetContents handle
closed <- hIsClosed handle
print handle >>print result >>print closed
On my system (ghc-9.12.2, text-2.1.4), this yields
{handle: /bin/bash}
Left /bin/bash: hGetContents: invalid argument (cannot decode byte sequence starting from 224)
False
As far as I can tell, this also affects Data.Text.IO.readFile, which uses openFile instead of withFile and so the handle is not closed there either. System.IO.readFile' uses withFile and has a comment acknowledging that this should not be necessary:
There's a bit of overkill here—both withFile and hGetContents' will close the file in the end.
I cannot say with 100% certainty, but I believe this to be the root cause of a "resource exhausted (Too many open files)" exception in my application.
There is no promise that the handle will be closed promptly, only that it will be closed eventually (namely, when the garbage collector will trigger finalisers).
But I agree that Data.Text.IO.readFile should use withFile instead of openFile. Patches are welcome.
Ah, I guess System.IO.hGetContents' also does not close the handle promptly. Is there a reason why both System.IO.hGetContents' and Data.Text.IO.hGetContents should not do so, given that they aim for non-lazy IO semantics? From what I can tell, this is what Data.ByteString.hGetContents does, but it is difficult to test since there is no easy way to provoke failure. I feel like the immediate closing behavior would be less surprising, but maybe there is a good reason for the way things are.
I guess this also means that the comment on System.IO.readFile' is not quite accurate. Using withFile is not overkill, as it ensures prompt closure of the handle which the underlying hGetContents' by itself does not guarantee.
I think I might have misled you above, I was not aware that hGetContents sets semi-closed mode.
I'm not an expert in this area: hGetContents almost immediately goes into withHandle and code there gets too dense to skim through. Perhaps the precise semantics in the presence of exceptions a good question for GHC issue tracker.
Anyway, Data.Text.IO.readFile should be modelled after System.IO.readFile' (using withFile) and not System.IO.readFile (which leaves it to hGetContents to close the handle). The current implementation is most likely a copy-paste without much analysis. Fancy to fire a PR?
Activity
Bodigrim commentedon Sep 26, 2026
There is no promise that the handle will be closed promptly, only that it will be closed eventually (namely, when the garbage collector will trigger finalisers).
But I agree that
Data.Text.IO.readFileshould usewithFileinstead ofopenFile. Patches are welcome.julmb commentedon Sep 26, 2026
Ah, I guess
System.IO.hGetContents'also does not close the handle promptly. Is there a reason why bothSystem.IO.hGetContents'andData.Text.IO.hGetContentsshould not do so, given that they aim for non-lazy IO semantics? From what I can tell, this is whatData.ByteString.hGetContentsdoes, but it is difficult to test since there is no easy way to provoke failure. I feel like the immediate closing behavior would be less surprising, but maybe there is a good reason for the way things are.I guess this also means that the comment on
System.IO.readFile'is not quite accurate. UsingwithFileis not overkill, as it ensures prompt closure of the handle which the underlyinghGetContents'by itself does not guarantee.Bodigrim commentedon Sep 27, 2026
I think I might have misled you above, I was not aware that
hGetContentssets semi-closed mode.I'm not an expert in this area:
hGetContentsalmost immediately goes intowithHandleand code there gets too dense to skim through. Perhaps the precise semantics in the presence of exceptions a good question for GHC issue tracker.Anyway,
Data.Text.IO.readFileshould be modelled afterSystem.IO.readFile'(usingwithFile) and notSystem.IO.readFile(which leaves it tohGetContentsto close the handle). The current implementation is most likely a copy-paste without much analysis. Fancy to fire a PR?Lysxia commentedon Oct 6, 2026
We've fixed
readFilebut I think the behavior ofhGetContentsandhGetContents'here is a bug, or at the very least undesirable.Looking at
System.IOfor comparison:hGetContentscloses the handle on decoding exception, which is technically anIOException, so this arguably respects its spec:hGetContents'does not close the handle on error. I believe this is a bug. I've opened a GHC ticket to get another opinion https://gitlab.haskell.org/ghc/ghc/-/work_items/27905