16
$\begingroup$

It seems like people got tired of manual memory management, so they invented garbage collection, and life was reasonably good. But what about every other resource types? File descriptors, sockets, or even user created data like database connections?

This feels like a naive question but I cannot find any place where anyone has asked it. Let's consider file descriptors. Say a program knows that it will only be allowed to have 4000 fds available when it starts. Whenever it performs an operation that will open a file descriptor, what if it would

  1. Check to make sure that it isn't about to run out.
  2. If it is, trigger the garbage collector, which will free a bunch of memory.
  3. If some of the memory freed held references to file descriptors, close them immediately. It knows the memory belonged to a resource because the memory tied to that resource was registered into a 'file descriptor registry', for lack of a better term, when it was first opened.
  4. Open a new file descriptor, copy it into new memory, register that memory location into the 'file descriptor registry' and return it to the user.

So the resource would not be freed promptly, but it would be freed whenever the gc ran which includes at the very least, right before the resource was about to run out, assuming it isn't being entirely utilized.

And it seems like that would be sufficient for many user defined resource cleanup issues. I managed to find a single comment here that references doing cleanup similar to this in C++ with a thread that contains a reference to a resource and cleans it up when only it has a single reference remaining (from the cleanup thread), but I can't find any evidence of this being a library or part of any existing language.

$\endgroup$

6 Answers 6

4
$\begingroup$

GC deals with a predictable and reserved resource. The VM has total control over it and has total control over what instances are created and when. The keywords here are "reserved" and "total control". Handles are allocated by the OS, and pointers are... well pointers to resources allocated outside the managed space. Because of that, handles and pointers are not restricted to be used inside managed code. They can be used - and often are - by managed and unmanaged code running on the same process.

A "Resource Collector" would be able to verify if a handle/pointer is being used within a managed space or not, but it by definition is unaware of what's happening outside it's memory space (and, to make things worse some handles can be used across process boundaries).

A practical example is the .NET CLR. One can use flavored C++ to write code which works with both managed and unmanaged memory spaces; handles, pointers and references can be passed around between managed and unmanaged code. The unmanaged code must use special constructs/types to allow the CLR to keep tracking of references being made to it's managed resources. But that's the best it can do. It cannot do the same with handles and pointers, and because of that said Resource Collector would not know if it's ok to release a particular handle or pointer.

edit: Regarding the .NET CLR, I'm not experienced with C++ development with the .NET platform. Maybe there are special mechanisms in place that allows the CLR to keep tracking of references to handles/pointers between managed and unmanaged code. If that's the case then the CLR could take care of the lifetime of those resources and release them when there all references to them are cleared (well, at least in some scenarios it could). Either way, best practices dictate that handles (especially those pointing to files) and pointers should be released as soon as they are not needed. A Resource Collector would be not-complying with that, that's another reason to not have one.

edit 2: It's relatively trivial on the CLR/JVM/VMs-in-general to write some code to free up a particular handle if it's used only inside the managed space. In .NET would be something like:

// This class offends many best practices, but it would do the job.
public class AutoReleaseFileHandle {
    // keeps track of how many instances of this class is in memory
    private static int _toBeReleased = 0;

    // the threshold when a garbage collection should be forced
    private const int MAX_FILES = 100;

    public AutoReleaseFileHandle(FileStream fileStream) {
       // Force garbage collection if max files are reached.
       if (_toBeReleased >= MAX_FILES) {
          GC.Collect();
       }
       // increment counter
       Interlocked.Increment(ref _toBeReleased);
       FileStream = fileStream;
    }

    public FileStream { get; private set; }

    private void ReleaseFileStream(FileStream fs) {
       // decrement counter
       Interlocked.Decrement(ref _toBeReleased);
       FileStream.Close();
       FileStream.Dispose();
       FileStream = null;
    }

    // Close and Dispose the Stream when this class is collected by the GC.
    ~AutoReleaseFileHandle() {
       ReleaseFileStream(FileStream);
    }

    // because it's .NET this class should also implement IDisposable
    // to allow the user to dispose the resources imperatively if s/he wants 
    // to.
    private bool _disposed = false;
    public void Dispose() {
      if (_disposed) {
        return;
      }
      _disposed = true;
      // tells GC to not call the finalizer for this instance.
      GC.SupressFinalizer(this);

      ReleaseFileStream(FileStream);
    }
}

// use it
// for it to work, fs.Dispose() should not be called directly,
var fs = File.Open("path/to/file"); 
var autoRelease = new AutoReleaseFileHandle(fs);
$\endgroup$
3
$\begingroup$

This appears to be one of the reasons languages with garbage collectors implements finalizers. Finalizers are intended to allow a programmer to clean up an object's resources during garbage collection. The big problem with finalizers is that they aren't guaranteed to run.

There's a pretty good write-up on using finalizers here:

Object finalization and cleanup

In fact it specifically uses the file descriptor as an example. You should make sure to clean up such resource yourself, but there is a mechanism in place that MAY restore resources that weren't properly released.

$\endgroup$
4
  • $\begingroup$ I'm not sure if this answers my question. It is missing the part of my proposal where the system knows that it is about to run out of a resource. The only way to hammer that part in is to ensure that you manually run the gc before you allocate new file descriptors, but that is extremely inefficient, and I don't know if you can even cause the gc to run in java. $\endgroup$
    – mindreader
    Feb 6, 2016 at 19:55
  • $\begingroup$ OK, but file descriptors usually represent an Open File in the operating system which implies (depending upon the OS) use system level resources such as locks, buffer pools, structure pools, etc. . Frankly, I don't see the benefit of leaving these structures open for a later garbage collection and I see many detriments to leaving them allocated longer than necessary. The Finalize() methods are intended to allow a last ditch cleanup in the event that a programmer overlooked calls to clean up resources, but shouldn't be relied upon. $\endgroup$ Feb 7, 2016 at 13:25
  • $\begingroup$ My understanding is that the reason they shouldn't be relied upon is that if you were to allocate a ton of these resources, like maybe you are descending down a file hierarchy opening each file, you may open too many files before the gc happens to run, causing a blowup. The same thing would happen with memory, except that the runtime checks to make sure it will not run out of memory. I would like to know why a system cannot be implemented to reclaim arbitrary resources before the blowup, in nearly the exact same way as memory is done. $\endgroup$
    – mindreader
    Feb 8, 2016 at 14:05
  • $\begingroup$ A system COULD be written to GC resources other than memory, but you would have to track reference counts or have some other method of determining when a resource is no longer in use. You DON'T want to deallocate and reallocate resources that are still in use. All manor of mayhem can ensue if a thread has a file open for write, the OS "reclaims" the file handle and another thread opens a different file for write using the same handle. And I'd also still suggest that it's a waste of significant resources to leave them open until a GC like thread gets around to releasing them. $\endgroup$ Feb 8, 2016 at 14:31
3
$\begingroup$

There are many programming techniques to help manage these kinds of resources.

  • C++ programmers often use a pattern called Resource Acquisition is Initialization, or RAII for short. This pattern ensures that when an object that holds onto resources goes out of scope, it will close the resources it was holding on to. This is helpful when the object's lifetime corresponds to a particular scope in the program (e.g., when it matches the time when a particular stack frame is present on the stack), so it is helpful for objects that are pointed to by local variables (pointer variables stored on the stack), but not so helpful for objects that are pointed to by pointers stored on the heap.

  • Java, C#, and many other languages provide a way to specify a method that will be invoked when an object is no longer live and about to be collected by the garbage collector. See, e.g., finalizers, dispose(), and others. The idea is that the programmer can implement such a method so that it will explicitly close the resource before the object is freed by the garbage collector. However, these approaches do have some issues, which you can read about elsewhere; for instance, the garbage collector might not collect the object until far later than you'd like.

  • C# and other languages provide a using keyword that helps ensure that resources are closed after they're no longer needed (so you don't forget to close the file descriptor or other resource). This is often better than relying on the garbage collector to discover that the object is no longer live. See, e.g., https://stackoverflow.com/q/75401/781723. The general term here is a managed resource. This notion builds on RAII and finalizers, improving on them in some ways.

$\endgroup$
1
  • $\begingroup$ I am less interested in prompt resource deallocation, and more interested in the idea of just in time deallocation. RIAA is great, but not super applicable to very many garbage collection languages. Java is missing the ability to know when it is about to run out of a certain resource. Using and bracket type operations are useful and deal with errors, but I'm not interested in them. I simply want to allocate resources and then they will clean themselves up whenever it is convenient or necessary, and there's little way to screw it up. I guess no one has really looked into this. $\endgroup$
    – mindreader
    Feb 6, 2016 at 20:01
2
$\begingroup$

All memory is equal, if I ask for 1K, I don’t care where in the address space the 1K comes from.

When I ask for a file handle, I want a handle to the file I wish to open. Having a file handle open on a file, often blocks access to the file by other processes, or machine.

Therefore file handles must be closed as soon as they are not needed, otherwise they block other accesses to the file, but memory only needs to be reclaimed when you start to run out of it.

Running a GC pass is costly and is only done “when needed”, it is not possible to predict when anther process will need a file handle that your process may no longer be using, but still has open.

$\endgroup$
1
  • $\begingroup$ Your answer hits the real key: memory is fungible, and most systems have enough that it doesn't need to be reclaimed especially quickly. By contrast, if a program acquires exclusive access to a file, that will block any other programs everywhere in the universe that might need to use that file, no matter how many other files may exist. $\endgroup$
    – supercat
    Jan 15, 2017 at 3:17
0
$\begingroup$

I would guess the reason why this has not been approached much for other resources is exactly because most other resources are preferred to be released as soon as possible for anyone to reuse.

Note, of course, your example could be provided now using "weak" file descriptors with existing GC techniques.

$\endgroup$
0
$\begingroup$

To check if memory is not longer accessible (and thus guaranteed not to be used anymore) is rather easy. Most other types of resources can be handled by more or less the same techniques (i.e., resource acquisition is initialization, RAII, and its counterpart of freeing when the user is destroyed, which links it with memory administration). Doing some kind of "just in time" freeing is impossible in general (check the halting problem, you would have to find out that some resource was used for the last time). Yes, sometimes it can be done automatically, but it is a much messier case as memory. So it relies on user intervention for the most part.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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