2

I've seen lots of code like the following example. It's in Python, but the same mistake is made in all languages with managed resources:

f = open('foo.txt', 'rb')
for line in f: print line

That's it. The error is that close(f) wasn't called so the file handle is kept open until some indeterministic time in the future when the runtimes memory management decides to reclaim memory.

Python has with and c# has using to help make resource cleanup easier, but let's disregard those features for a minute. Given that:

  1. It's a programming error not to explicitly close open files.
  2. The runtime can detect that an open file has not been closed.

Why then doesn't the runtime throw an error instead of being "helpful" and closing the file for the programmer? That would be the fail fast and fail early strategy. Is there a technical reason why it can't? Is there any languages that does it? Has the idea been considered before (I've googled but not found anything)?

Here is how you almost implement the feature in Python:

class mustclose:
    def __init__(self, f):
        self.f = f
    def __del__(self):
        if not self.f.closed:
            raise Exception("You forgot to close() me!")
k = mustclose(open('foo.txt', 'wb'))
#k.f.close()

Two problems: It requires wrappers, Python doesn't like to throw exceptions from destructors.

(Read this https://stackoverflow.com/questions/2807241/what-does-the-expression-fail-early-mean-and-when-would-you-want-to-do-so SO question for background on why failing fast is often desirable)

4
  • Some language implementations have a garbage collector with finalization able to release some resources (e.g. close a file when a value containing that file is dead). Commented Sep 2, 2014 at 17:20
  • Basile, that's the point. Why isn't the garbage collector throwing an error instead of closing the file? Commented Sep 2, 2014 at 17:41
  • Technically speaking the program doesn't close the handle rather than the OS deletes all resources allocated by the program on termination. There is also a performance aspect since the OS is so much better at this since it can just delete everything at one big swoop rather than one handle at a time. Commented Sep 2, 2014 at 18:10
  • How would the runtime know you're not done with the resource unless it knows that all references to it have gone out of scope?
    – Blrfl
    Commented Sep 2, 2014 at 18:27

7 Answers 7

3

In Java 7 and later, resources that implement AutoCloseable will cause the compiler to emit a warning when a resource is not managed with try-with-resources.

Combined with garbage collection, this goes a long way toward ensuring that resources are freed at some point.

As Robert Harvey mentioned, the operating system also disposes of a process's entire memory space and frees dangling resources. However, this does not necessarily ensure that resources are properly closed (e.g. sending a "goodbye" message on a socket to a remote host to free remote resources). This also relies on the process being shut down. A long-running process might leak resources such as remote connections, file handles, etc.

Java 7's try-with-resources comes as close as close as I have seen toward guaranteeing that even non-local resources are freed during program execution, assuming that developers heed the compiler warnings.

2

The runtime can detect that an open file has not been closed.

The only time the runtime can unambiguously determine that an open file should have been closed is upon process shutdown, particularly with event driven frameworks that may reuse open file handles in callbacks, etc. Unless the handle has been scoped (and you said ignore C# using {} and Java Try-With-Resources runtime hints), the runtime CANNOT determine if an error has occurred.

Since the runtime is disposing of the whole process, for system stability purposes, its only real recourse is to close all open stream and release the associated resources.

2
  • 1
    No. I believe it can also determine that, unambiguously, if the open file is eligible for garbage collection. If you can prove otherwise, please explain. :) Commented Sep 2, 2014 at 17:56
  • Ah, fair enough. I think most all of the discussions have revolved around scope, and when the scope is unbounded (e.g., global), the runtime is not able to determine usage error. I over simplified.
    – Kristian H
    Commented Sep 2, 2014 at 18:46
2

What would happen if a language threw run-time errors e.g. on leaked file handles? Coders would hate it with a flaming passion, and it would go nowhere.

The point of resource management is to aid you in producing a valuable program with less effort. "Valuable" doesn't mean "perfect": a program that leaks a file handle is bad, but often it is nowhere near as bad as a program that deliberately blows up instead of wasting some resources, which might or might not cause problem for your use case.

If the only goal worth aiming at was perfection, them sure, every compiler, run-time and OS should be as ruthless as possible in detecting and terminating processes that commit errors, since they are by definition worthless. Arguably it would be a better world if customers didn't accept anything short of perfection and ruthlessly sued the pants of any vendor who sold them an application with a resource leak in it, but let's be quite clear: this is not such a world.

That is only the business side of things; others have pointed out that it's not easy to decide whether a handle has truly been leaked or not. Languages with fully automatic garbage collection are in theory capable of adding any desired behaviour to the 'dispose' hook; for instance, Java initially assumed that you would clean up resources in a class's finalize method. This is now widely considered a deplorable mistake, as it turns out having things happen in unpredictable order causes more problems than it solves, particularly if they have effects on the external environment (and not just the strictly process-private memory pool). Ultimately it just wasn't worth making the garbage collection process deterministic just to allow this pattern of resource management, when the language already has finally (and now scoped resources) to achieve the same thing.

2

While it has almost certainly been considered, it has been discarded because, in the general case, it is impossible to do.

What happens if f is not a local variable, or is returned from the function?

At that point, you have an equivalent to the Halting Problem. There is no way (in the general case) for static analysis to prove one way or another if the file is closed somewhere else in your program (or worse, in someone calling this code in a library).

7
  • I'm not asking about static analysis, I'm asking about runtime errors. Commented Sep 2, 2014 at 17:52
  • 2
    @BjörnLindqvist - great, so at what point does a file become "unclosed" instead of "just not closed yet"?
    – Telastyn
    Commented Sep 2, 2014 at 18:02
  • Exactly (and only) when the garbage collector tries to reclaim its memory. Commented Sep 2, 2014 at 18:08
  • @Telastyn I think the asker meant at program termination since he said the program is helpful and closes the handle. Commented Sep 2, 2014 at 18:08
  • 2
    @BjörnLindqvist - Great, so you're going to throw an exception out of the garbage collector then and crash the app at runtime?
    – Telastyn
    Commented Sep 2, 2014 at 18:10
1

When a process is terminated, all of the allocated memory gets automatically released back to the operating system, and any open file handles are closed. That's your safety valve.

The problem, of course, is that the operating system (and your compiler, for that matter) doesn't actually know that your example code is an error. You have to state, by your intent when you write your code, when and where that file handle gets disposed. You might have a reason for holding the file handle open.

The using statement and the IDisposable interface in C# are specifically designed to clean up unmanaged resources like file handles.

// FileStream inherits from IDisposable, and implements the Dispose() method.
using (FileStream fs = File.Create(path))
{
    fs.Write(...);
}
// The File Stream is automatically closed here, because the Dispose() method on the 
// IDisposable interface is automatically called when the "using" block goes out of scope.

In a managed environment, memory is typically disposed automatically when garbage collection occurs. Implementing using and IDisposable insures that file handles and other unmanaged resources get properly disposed before the memory is released. You can also state your specific intent by calling Close() explicitly on your files before the enclosing object goes out of scope.

In some situations, such as repositories that return an IQueryable, it can be a hindrance to put a code block in a using statement, because the connection to the database can actually close before the query is executed.

In short, the OS and the compiler can't read your mind.

2
  • All true, but my question is why the gc cleans up non-memory resources instead of throwing an error. Commented Sep 2, 2014 at 17:50
  • 1
    To be clear, the GC does not know anything about file handles, other than they are memory objects. You give the GC the ability to deal with the closing of file handles by implementing finalizers, using, and IDisposable, so that when the GC disposes of the object, the object can clean up after itself first. Commented Sep 2, 2014 at 17:59
0

The destructor is called after the object goes out of scope.

If f is global, then that means at program exit.

Someone could use that resource f again, and the compiler / runtime is not generally in a position to know for sure that that cannot happen in any particular case.

Its good practice to scope variables as tightly as possible, and avoid globals. But these dynamic languages are also used as scripting languages, and scoping can be verbose and detract form readability too.

You can typically have thousands of handles open, and all are closed automatically by the OS on process exit (graceful or otherwise) anyway, so programmers are very rarely bitten by unnecessarily-long-lived resources.

0

Runtime errors are not a tool to discipline programmers. they are an ultima ratio to deal with unexpected errors which, if not handled, would leave an unstable state. Which is not the case with open file handles. In the grand scheme of things, it doesn't matter when they get closed.

Should the compiler issue a warning, if it finds unmanaged resources going out of scope without dispose? Probably. But it is still the programmers decision whether it is acceptable to let the GC handle finalisation at an arbitrary time, or whether those handles need to be closed now.

Not the answer you're looking for? Browse other questions tagged or ask your own question.