Hacker News new | ask | show | jobs
by pcwalton 4597 days ago
> This appears to be a helper function used exclusively by the standard library to handle file descriptor closing (incidentally, the one issue I've had with Golang's concurrency model).

But it's part of the public API. You can add a finalizer to any object. The semantics of Go say that finalizers are run automatically when the GC reclaims an object. So this statement is wrong: "In Go, the only way to execute a block of code is to make a function call. There are no constructors, destructors, or any other kind of side effect to allocating or deallocating data structures." It would be more correct to say "idiomatically, in Go people tend to prefer calling functions explicitly, and 'defer' encourages this."

I think the fact that it's used by the standard library to close file descriptors is actually really illustrative: you need finalizers in a GC'd language, otherwise you'll leak resources. Not all resources are stack-scoped. So implicitly running functions on deallocation is a necessary evil. You might as well embrace it in your language design.

1 comments

It may be part of the "public API" solely because it needs to be made available to several different components of the standard library, which is itself at pains to implement itself primarily in Golang.

SetFinalizer feels like a low blow, here.

I don't see why it's relevant that the standard library as opposed to user code needs it. The standard library is a library like any other. It needs finalization functionality because you always need that functionality in a GC'd language.

File descriptors are just one case of resources that need finalization functionality to not leak: the same applies to GPU textures, X server resources, etc. etc.

You don't need finalization functionality in a GC'd language. If you imagine a language that lacks finalization functionality but has automatic memory reclamation, things turn out okay. Finalization functionality isn't something that sane programs depend on -- garbage collection of memory makes sense because if you run out of memory or allocate and remove pointers a lot of stuff, the garbage collector can naturally kick in and find you some more memory to use. If you allocate a bunch of file handles, does the garbage collector kick in when your OS tells you that you've run out of file descriptors?
> You don't need finalization functionality in a GC'd language. If you imagine a language that lacks finalization functionality but has automatic memory reclamation, things turn out okay.

Not in fault-tolerant message passing systems, to name just one obvious example. Suppose that you put a bunch of file objects into a buffered channel, and then the goroutine that was supposed to receive those objects panics. Your program wants to recover from panics with recover(). Who closes the file descriptors in those channels? Nobody owns them yet: they were in a channel and the goroutine that was supposed to receive them died.

You might be able to solve this by handing out references to the channel to another goroutine that is supposed to clean up the file descriptors, but this gets really complicated. This sort of thing is why Go is GC'd in the first place. It's much easier to just have the GC clean up the file descriptors in channels in which one endpoint has gone dead, and that's the sort of thing finalizers are for and I assume it's the reason that finalizers were built into Go.

one option is for the goroutine to defer a cleanup closure that closes every file in that channel. Panics will cause all deferreds to be called all the way up to the deferred which recover()s.

Another option is to crash the app instead of excessive recover()ing. (obviously there are good reasons to use runtime error handling, but imho fewer than what one would think).

Have the things in the channel be the equivalent of C#'s IDisposable or something, then the goroutine can clean them up itself.
Have you read pcwalton example?

How can the goroutine clean them if it is dead?

Finalization is not needed but it is nice to have. For example releasing shared IPC resources.

> Finalization functionality isn't something that sane programs depend on

One definitely wouldn't want to use it _if they don't have to_, but sometimes there is no choice. Finalization is one of those things one does reluctantly because they end up having to use shared memory for example. Or standard library want to help you not leak file descriptors.

> Garbage collection of memory makes sense because if you run out of memory or allocate and remove pointers a lot of stuff, the garbage collector can naturally kick in and find you some more memory to use.

Well collection of anything unused and limited probably makes sense because otherwise you'd run out of them eventually. Garbage collection doesn't magically add RAM sticks to the machine though. If you've used all the memory and still hold references to all the objects, (in a GCed language), there is nothing GC can do. [Well I guess you can have weak refs like in Python...].

> If you allocate a bunch of file handles, does the garbage collector kick in when your OS tells you that you've run out of file descriptors?

Well in a high level language that has GC you'd expect to also probably deal with File _objects_ not file _descriptors_. In that case I would expect those _File_ objects when and if they are GC-ed to also close their file descriptor appropriately. That is where being able to have finalizers helps. Because a finalizer of a File object would close the file descriptor.

I only saw this implemented in OS based on Native Oberon, where everything on the OS was GC enabled, from file handles to GUI widgets.

If your applications needs to communicate with the outside world in a OS implemented in a systems language without GC support, then the GC needs a little help to give back those resources back to the OS.