Sorry there’s no link to the source, AFK right now, but files opened with os.Open will be automatically closed once all its references have been collected.
Found out about this behaviour some months ago while debugging some code at work.
Sorry there’s no link to the source, AFK right now, but files opened with os.Open will be automatically closed once all its references have been collected.
Found out about this behaviour some months ago while debugging some code at work.
No, they're not. The idiomatic usage is to defer a Close() call after checking that Open succeeded, like so:
file, err := os.Open("/etc/hostname")
if err != nil {
doSomethingWith(err)
}
defer file.Close()
doSomethingWith(file)
But you'd be forgiven for not knowing that: To my own surprise, I could not find the need for this idiom explained in the package documentation for os.Open, though you can see it in action throughout the std implementation. For example: https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/...From the docs (https://pkg.go.dev/os#File.Fd):
> Fd returns the integer Unix file descriptor referencing the open file. If f is closed, the file descriptor becomes invalid. If f is garbage collected, a finalizer may close the file descriptor, making it invalid; see runtime.SetFinalizer for more information on when a finalizer might be run.
To give more context on how I found this out, we were missing a Close for a couple of files in our codebase. As soon as I realised I added them, but checked in production for file descriptor leaks and there were none. Checking Go’s source code led me to the finalizer and this doc confirmed what I saw in the code.
I tend more towards this line of thought. But I'd still put a finalizer on file handles. I just would have it yell very loudly that there's a bug. Maybe even close the file, but there's no way I wouldn't at the very least be generating a lot of diagnostics saying things were broken.
This isn't the only resource like this. If you write to a lot of temporary files, you'd want to start cleaning those up before the OS returns "no space left on device". If you listen on a lot of TCP ports, you want to clean up stale listeners before you've listened 65535 times. If you span a subprocess, you want to start reaping the children when PID pressure prevents creating future subprocesses.
This all ends up being rather esoteric so many programming languages just say "tell me as soon as you're done" and you pay the cost right then. This isn't optimal (especially "x := Open(thing); defer x.Close(); do something with x; do a bunch of slow stuff without x"). A big downside is that it's possible to have an object in memory that refers to something in an invalid state; you can write "x := Open(thing); x.Close(); x.Read()" just fine in many languages; the code compiles and it runs, but it returns incorrect results. This could be an area of focus for future languages, but I doubt much of this would be general. A lot of nitty gritty specifics depending on what resources you want to track.
It would also be weird to not close files just because someone turned GC off, right? If you tie finalizers to memory management, if the memory management isn't under memory pressure, then you start leaking FDs. Weird! All in all, the memory management subsystem is a weird place to manage non-memory resources. So to get this right, you really need something GC-like for everything that's not memory.
I think people are looking for something like, or are already very comfortable with, C++'s RAII. "I am certain this variable is allocated on the stack, so the compiler should fail to compile if that's untrue, and a Close method should be called when the function returns." But sometimes you will want the resource to be allocated beyond the current stack frame, and now you need special syntax for that. (Newer languages like to avoid exposing stack vs. heap to the programmer and let The Algorithm pick the best place. C++ never considered that an option so you do get some extra flexibility when you need it.)
Anyway this is a long post to say "all options are bad".
It's very hard to debug such bugs.
Imo it's much easier to make the leak more prominent so that people are incentivised to properly close the files