Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #669 (closed defect: fixed)

Opened 17 years ago

Last modified 16 years ago

Tango's GC does not release memory to the OS on termination

Reported by: CyberShadow Assigned to: sean
Priority: major Milestone: 0.99.3
Component: Core Functionality Version: 0.99.2 Don
Keywords: Cc: thecybershadow@gmail.com

Description

Tango's garbage collector does not seem to be releasing memory back to the OS when it terminates. This causes significant memory leaks with D DLLs that are loaded/unloaded during the application's lifetime.

Test code:

import tango.sys.win32.Process;
import tango.sys.win32.UserGdi;
import tango.stdc.stdio;

HINSTANCE g_hInst;

pragma(lib, "kernel32.lib");

extern (C)
{
	void gc_init();
	void gc_term();
	void _minit();
	void _moduleCtor();
	void _moduleDtor();

}

extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
    switch (ulReason)
    {
	case DLL_PROCESS_ATTACH:
	    gc_init();			// initialize GC
	    _minit();			// initialize module list
	    _moduleCtor();		// run module constructors
	    mainProc();
	    break;

	case DLL_PROCESS_DETACH:
	    _fcloseallp = null;
	    _moduleDtor();
	    gc_term();			// shut down GC
	    break;

	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	    // Multiple threads not supported yet
	    return false;
    }
    g_hInst=hInstance;
    return true;
}

void mainProc()
{
	ubyte[] data = new ubyte[10*1024*1024];
}

Phobos does not have this problem.

Change History

10/07/07 23:08:45 changed by sean

  • status changed from new to closed.
  • resolution set to wontfix.

The problem is that daemon threads are still running when gc_term() is called, so if the GC releases memory back to the OS these threads are liable to crash horribly. I'd actually had the code in to release the memory and then removed it for this reason. I'm going to mark this as 'wontfix' for now, but if a solution can be provided then I'll reconsider. I agree that the current situation is not ideal.

10/07/07 23:26:53 changed by CyberShadow

  • status changed from closed to reopened.
  • resolution deleted.

If there are any threads at all running in DLL code by the time the DLL is unloaded, the whole process will crash horribly anyway. Assuming the user doesn't call gc_term() when it's not appropriate to call it, there should be no ill consequences (unless some threads may be terminated after the gc_term() call and before the DLL is actually unloaded).

10/07/07 23:28:10 changed by CyberShadow

Also - if gc_term()'s behavior is not to be changed, add an user-accessible function to the GC to release the GC memory. This way, memory can be deallocated manually by DLL writers.

10/08/07 03:10:47 changed by kris

this sounds like either threads associated with the detaching DLL must be forcibly halted before gc_term() exits, or the DLL needs to be 'managed' as a resource by the GC, and not by the application ?

10/08/07 03:52:19 changed by CyberShadow

Other programming languages which I know of let the programmer handle it. Terminating threads forcibly when the DLL is being unloaded would save the entire application from crashing - but that scenario should never appear in the first place, since forceful termination of threads is very rarely a desired way to unload a DLL, and most likely means that, due to a bug or design flaw in the application, the DLL gets unloaded prematurely.

As for the 'managed by GC' idea - I don't think I fully understand your point. If you mean that the DLL should unload itself when all threads have terminated once the application tries to unload the DLL - I don't think it's possible unless DllMain?, and thus FreeLibrary? in the host application, blocks execution until the threads managed by the DLL have exited.

In my opinion, neither of these two (terminating all threads / waiting all threads to exit) should be enacted by the library, but rather they should be made easily available (e.g. Thread.killAllButCurrent and Thread.joinAll OSLT). For what it's worth, whatever threads Tango has in its registered threads list might be running code in other modules that will never return back to our DLL, and so unloading the DLL which started those threads would have no effect on them.

10/08/07 12:20:29 changed by larsivi

  • milestone changed from 0.99.2 RC5 to 0.99.3 RC6.

10/08/07 16:10:35 changed by sean

It would be easy enough to change the code to release memory once again and see if anyone complains about applications crashing on exit.

10/08/07 19:59:16 changed by sean

If gc_term() releases GC memory, the thread that's still running might crash. This wouldn't matter if this were a DLL. Since the process won't terminate until all threads have exited, waiting for all other threads to terminate before calling gc_term() would prevent crashes. The user will just have to register threads not created using Tango that use GC memory.

The shutdown process is the same for normal applications and for DLLs (via rt_term):

1. Wait for all non-daemon threads to complete
2. Call all static dtors
3. Call gc_term
4. Exit

The problem is that daemon threads are still running when C main returns, which is step 4. The only options I know of are:

A. Don't release memory in gc_term (the current solution)
B. Forcibly terminate all known daemon threads between steps 2 and 3 and then release memory in gc_term
C. Release the memory in gc_term and let daemon threads crash
D. Have different behavior for applications vs. DLLs -- perhaps have some way of specifying to the GC that memory should or shouldn't be released on gc_term, etc.

Please note that it is ultimately up to the garbage collector implementation to decide what should happen when gc_term is called--the most Tango can do is say what should typically happen and set an example by doing so in its default GC. But it's possible that some specialized garbage collectors (like the 'basic' gc that actually calls C malloc/free) may not even be able to release this memory. Though I suppose this is the point where it's up to the programmer to make sure his app or DLL can clean up after itself properly.

11/12/07 20:21:04 changed by sean

  • status changed from reopened to closed.
  • resolution set to fixed.

(In [2865]) I've re-enabled the call to gc.Dtor() in gc_term(). This could result in a crash on exit if daemon threads are running. However, it seems to be what people want so we'll see how it goes. closes #669