13

Years ago, I decided never to rely solely on setting a thread's FreeOnTerminate property to true to be sure of its destruction, because I discovered and reasoned two things at application's termination:

  1. it produces a memory leak, and
  2. after program's termination, the thread is still running somewhere below the keyboard of my notebook.

I familiarized myself with a workaround, and it did not bother me all this time. Until tonight, when again someone (@MartinJames in this case) commented on my answer in which I refer to some code that does not use FreeOnTerminate in combination with premature termination of the thread. I dove back in the RTL code and realized I may have made the wrong assumptions. But I am not quite sure about that either, hence this question.

First, to reproduce the above mentioned statements, this illustrative code is used:

unit Unit3;

interface

uses
  Classes, Windows, Messages, Forms;

type
  TMyThread = class(TThread)
    FForm: TForm;
    procedure Progress;
    procedure Execute; override;
  end;

  TMainForm = class(TForm)
    procedure FormClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FThread: TMyThread;
  end;

implementation

{$R *.dfm}

{ TMyThread }

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    Synchronize(Progress);
    Sleep(2000);
  end;
end;

procedure TMyThread.Progress;
begin
  FForm.Caption := FForm.Caption + '.';
end;

{ TMainForm }

procedure TMainForm.FormClick(Sender: TObject);
begin
  FThread := TMyThread.Create(True);
  FThread.FForm := Self;
  FThread.FreeOnTerminate := True;
  FThread.Resume;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FThread.Terminate;
end;

end.

Now (situation A), if you start the thread with a click on the form, and close the form right after the caption changed, there is a memory leak of 68 bytes. I assume this is because the thread is not freed. Secondly, the program terminates immediately, and the IDE is at that same moment back again in normal state. That in contrast to (situation B): when not making use of FreeOnTerminate and the last line of the above code is changed into FThread.Free, it takes (max.) 2 seconds from the disappearance of the program to the normal IDE state.

The delay in situation B is explained by the fact that FThread.Free calls FThread.WaitFor, both which are executed in the context of the main thread. Further investigation of Classes.pas learned that the destruction of the thread due to FreeOnTerminate is done in the context of the worker thread. This lead to the following questions on situation A:

  • Is there indeed a memory leak? And if so: is it important, could it be ignored? Because when an application terminates, doesn't Windows give back all its reserved resources?
  • What happens with the thread? Does it indeed run further somewhere in memory until its work is done, or not? And: is it freed, despite the evidence of the memory leak?

Disclaimer: For memory leak detection, I use this very simple unit as first in the project file.

NGLN
  • 41,230
  • 8
  • 102
  • 186
  • I don't know if you already read, but I do find very useful the posts from [Raymond Chen](http://blogs.msdn.com/b/oldnewthing) about these kind of subjects. Take this [one as an example](http://blogs.msdn.com/b/oldnewthing/archive/2012/01/05/10253268.aspx) or [two](http://blogs.msdn.com/b/oldnewthing/archive/2007/05/02/2365433.aspx#2375204). Look at comments and linked posts too. – EMBarbosa Jan 27 '12 at 16:47

1 Answers1

13

Indeed, the OS reclaims all a process's memory when it terminates, so even if those 68 bytes refer to the non-freed thread object, the OS is going to take those bytes back anyway. It doesn't really matter whether you've freed the object at that point.

When your main program finishes, it eventually reaches a place where it calls ExitProcess. (You should be able to turn on debug DCUs in your project's linker options and step through to that point with the debugger.) That API call does several things, including terminating all other threads. The threads are not notified that they're terminating, so the cleanup code provided by TThread never runs. The OS thread simply ceases to exist.

Rob Kennedy
  • 156,531
  • 20
  • 258
  • 446
  • +1 The OS stops all the process threads before it deallocates the process memory - this is why you get no AV's and it's quite safe, memory-wise, to just let the OS clean up. The OS can stop 'immediately' any thread in any state, even if it's running on another core than the thread calling ExitProcess(), and so your app shuts down without delay. Naturally, there's those cases where you do need to try to explicitly stop the threads - maybe a transaction needs committing, or a file flushed. Otherwise, the leak warning is spurious - don't worry about it if it's always there and never grows. – Martin James Jan 27 '12 at 11:11
  • One last point - you can get AV/s or 216/217 exception boxes on shutdown, but you have to try. One way to do it is to have threads reading/writing directly to fields of forms - the RTL closes forms and frees the form memory before ExitProcess() is reached, so such a thread may attempt to access freed memory during a shutdown. I just don't do such things & so I get no problems. – Martin James Jan 27 '12 at 12:12