1

c00000005 ACCESS_VIOLATION caused when closing the Form (exiting application) while the following thread is running (this is just an EXAMPLE, to illustrate the problem):

type
  Twtf = class(TThread)
  protected
    procedure Execute; override;
  end;

procedure Twtf.Execute;
const
  hopefully_big_enough_to_trigger_problem = 100000000;
var
  A: array of Integer;
  I: Integer;
begin
  SetLength(A, hopefully_big_enough_to_trigger_problem);
  while not Terminated do begin
    for I := Low(A) to High(A) do
      A[I] := 0;
  end;
end;

var
  wtf: Twtf;

procedure TForm1.Button4Click(Sender: TObject);
begin
  wtf := Twtf.Create;
  wtf.FreeOnTerminate := True;
end;

What is happening according to the Local Variables debug window is that A is now (), namely Length(A) is 0. Obviously this is why accessing A[I] causes problems, but note that I did not manually do anything to A.

How do I stop this error from showing (does not necessarily have to be prevented from happening)? The application is closing, the thread should just die... QUIETLY. Which actually happens for smaller arrays, but with a sufficiently large one 1 or 2 out of every 3 attempts results in the problem (if run with debugging it is shown always; i.e., this always happens, but is sometimes simply hidden).

The following does not help:

destructor TForm1.Destroy;
begin
  wtf.terminate;
  inherited;
end;

Only doing TerminateThread(wtf.Handle, 0); in the destructor instead solves (hides) the problem. But surely there is a more graceful way?

hundreAd
  • 158
  • 2
  • 8
  • It's your task to [`wtf.WaitFor()`](https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Classes.TThread.WaitFor) after calling [`wtf.Terminate()`](https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Classes.TThread.Terminate). Have you read what the latter method does? – AmigoJack Feb 23 '23 at 03:11
  • AmigoJack: ```WaitFor``` when ```FreeOnTerminate := True```? NEVER a good idea. Regardless, you are suggesting to delay the application for what may well be a long amount of time -- you either close immediately OR display a message e.g. "do you want to quit? y/n" but NOT just delay unannounced. – hundreAd Feb 23 '23 at 06:05
  • Why and why? Just check for `.Terminated` more often - that's the whole point. – AmigoJack Feb 23 '23 at 08:41
  • @AmigoJack Checking for terminated more often will not solve concurrency issues during shutdown. – Dalija Prasnikar Feb 23 '23 at 08:47

2 Answers2

4

Once you start self-destroying thread, you seriously limit your ability to manage it and perform required cleanup and waiting. They are convenient as fire-and-forget threads that need to run rather small tasks where task can be safely interrupted (killed) at any moment.

If the task requires cleanup, then you should not use self-destroying threads.

Besides the AV that happens during application shutdown, there are other issues with your code.


Following code is not a proper way to initialize self-destroying thread:

wtf := Twtf.Create;
wtf.FreeOnTerminate := True;

In above code thread will start running immediately after construction, and it is possible (although not very likely) that thread will complete its job, before you can set FreeOnTerminate flag to true and that would create memory leak.

Proper way is to create thread in suspended mode, then set FreeOnTerminate and then start a thread:

wtf := Twtf.Create(True);
wtf.FreeOnTerminate := True;
wtf.Start;

Another way to do that is overriding TThread.Create and setting FreeOnTerminate flag there.


Next issue is that after you start self-destroying thread, you should never ever access its reference. So you can never call wtf.Terminate, nor call TerminateThread(wtf.Handle, 0); because at that point thread could already been destroyed and wtf will be dangling reference pointing to non-existing instance.

Because you cannot call wtf.Terminate the while not Terminated loop is also useless, unless you will be constructing other instances of that thread class which you will manually manage.


There are many ways to solve your problem, but which one you will choose depends on actual job the thread is doing and how many of such threads you need running and whether you can freely interrupt (terminate) them or you need to wait for them to finish.

If you can safely interrupt whatever thread is doing and showing AV is your only problem, then you can just wrap the code inside Execute method with try...except and eat up the exception.

The best option is to use manually managed thread, which you will create once and destroy when form is closing. In that case while not Terminated loop makes sense as it will allow thread to be interrupted on application shutdown. You can also call wtf.Terminate earlier in the application shutdown process (for instance in OnClose, or OnCloseQuery to give thread more time to check Terminated flag.

 procedure TForm1.Button4Click(Sender: TObject);
 begin
   wtf := Twtf.Create;
 end;

destructor TForm1.Destroy;
begin
  // calling Free will terminate thread and wait for it
  wtf.Free; 
  inherited;
end;

Of course, there are other options for managing threads regardless of whether they are self-destroying ones or manually managed, but it is impossible to list them all, especially when your particular use case is not clear. But all those require some additional mechanisms for signaling to thread that it needs to stop its work or application that there are threads still running.

Depending on the Delphi version you are using, you might also want to look at TTask as running and handling tasks will be easier than handling threads and threads used for running tasks are automatically managed by RTL.


If you really, really, really insist on using self-destroying threads and just want to kill everything as fast as possible once uses closes the application, then you can use TerminateProcess. Be aware, that any abrupt termination of either threads or process by calling TerminateThread, ExitProcess, TerminateProcess can cause issues if your application uses DLLs or shares memory. See: TerminateProcess function and Terminating a Process

To do that you can add following destructor in your main form:

destructor TForm1.Destroy;
begin
  inherited;
  TerminateProcess(GetCurrentProcess, 0);
end;

Additional reading:

How to wait that all anonymous thread are terminated before closing the app?

Can I call TerminateThread from a VCL thread?

Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • protecting Execute with ```try ... except``` does NOT work, alas (when the application is closing weird things happen...); using ```Free``` (when FreeOnTerminate is False) may well cause the thread to continue existing well after the application has long existed -- obviously undesirable. – hundreAd Feb 23 '23 at 12:39
  • the problem is not terminating the thread manually: the application is closing, whatever it is doing automatically to the thread is fine -- the only problem is the error message (caused since application is freeing array memory before it is freeing the thread WHILE the thread is working on the array) – hundreAd Feb 23 '23 at 12:47
  • Using `try...except` should work. Also OS will kill any thread that is still running when the parent process is killed, so there will be no left over threads. When you call Free, thread destructor will call `Terminate` and then `WaitFor`. This will block application shutdown until thread has finished running. In such scenario, the only problem is that you will have to wait until thread has finished its work. In such case checking whether thread is terminated more often will help it exit sooner. – Dalija Prasnikar Feb 23 '23 at 13:25
  • If you really want to kill thread immediately, then there is always option to call `TerminateThread` but you cannot call that on self-destroying thread, also if you are at that point where nothing matters better option is just to kill the process as depending on what the thread is doing can have other unwarted side-effects See: https://stackoverflow.com/a/7949378/4267244 But again, proper shutdown with proper cleanup is always better option. – Dalija Prasnikar Feb 23 '23 at 13:31
  • Calling ```TerminateThread``` happily works on self-terminating threads as far as I can tell (unless you know something I don't?); so long as the TThread variable is not nil, no problem. – hundreAd Feb 23 '23 at 15:32
  • You should never access reference to self-destroying thread after the thread has started as when thread finishes that reference will turn into dangling pointer. If the memory is accidentally still intact it may seem like you have working code while in reality you don't. This kind of code can randomly break. – Dalija Prasnikar Feb 23 '23 at 18:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252089/discussion-between-hundread-and-dalija-prasnikar). – hundreAd Feb 23 '23 at 19:29
-2

To reiterate:

This was an example crafted to illustrate what happens when a thread loop is accessing a dynamic array when the application is terminated (regardless of whatever graceful shutdown mechanisms, the thread may be within the loop at that point in time, and blocking the application while waiting a possibly long while for it to finish or get to a if Terminated check is not optimal for obvious reasons).

The problem is caused by the application freeing the array's memory before it frees the thread, WHILE the thread is working on the array (the array is a variable belonging to the thread); e.g., with a sufficiently large multidimensional A: array of array of Integer, it is actually possible to see A[500000] freed to () while A[400000] is still fine.

The solution is so simple as to be trivial:

Twtf = class(TThread)

...

var
  wtf: Twtf;

...

destructor TForm1.Destroy;
begin
  // Only Form in the Application, otherwise add Application.Terminated check?
  if wtf <> nil then
    with wtf do
      if not Finished then
        Suspend;
  inherited;
end;

This eliminates the race condition between the thread working on its array and the application freeing that array.

Note that the thread will NOT call its destructor, so resources (if necessary) should be freed manually.

All of the above is irrespective of FreeOnTerminate.

EDIT: Added Finished check

EDIT: Alternative to deprecated Suspend: SuspendThread(Handle);

hundreAd
  • 158
  • 2
  • 8
  • 2
    So, your solution is to suspend the active thread (regardless of what state it is actually is - what if it were inside a lock, for instance?) and just leave it suspended until the OS kills the process? That is a *terrible* solution. The thread in question depends on resources that are disappearing behind its back, so the *proper* solution is to make the thread more responsive to application exit, gracefully terminating itself as soon as possible when the application asks it to terminate itself during shutdown, *before* those other resources are freed. – Remy Lebeau Feb 23 '23 at 18:19
  • Besides what @RemyLebeau said... How are you setting wtf reference to nil? Again you are potentially accessing dangling pointer. If you use manually managed thread and call Free on thread reference, you will not have problem with disappearing array. You can even call Terminate on a thread at some earlier time during application shutdown process to give thread more time to check for Terminated flag. – Dalija Prasnikar Feb 23 '23 at 18:41
  • @DalijaPrasnikar "*If you use manually managed thread and call Free on thread reference, you will not have problem with disappearing array*" - because calling `Free` on a `TThread` object will invoke its destructor, which internally calls `WaitFor()` if the thread is still running (which in turn will crash if called outside of the thread and `FreeOnTerminate` is `True`). – Remy Lebeau Feb 23 '23 at 18:54
  • @RemyLebeau manually managed thread implies that `FreeOnTerminate` is `False`. So freeing thread will perform proper cleanup and avoid issues. – Dalija Prasnikar Feb 23 '23 at 18:59
  • @@DalijaPrasnikar: ```wtf.FreeOnTerminate := False;``` makes NO difference, same problem occurs; I am not setting ```wtf``` to nil, this is just to protect in case program flow never caused it to be created in the first place before the Form destructor runs – hundreAd Feb 23 '23 at 19:20
  • @RemyLebeau: as you can see, thread does check for ```Terminated```, however short of checking on each and every line... simply put, there CAN exist a case in which the application is terminated while the thread is busy doing something other than checking for its termination -- the example is naturally an edge case designed to illustrate this – hundreAd Feb 23 '23 at 19:23
  • @RemyLebeau: if by "active thread" you mean the "main" vcl thread than no, it's a different thread; I would have expected Delphi to either free to the effect that internal thread resources do not just disappear "behind its back", or at least to swallow the error (as it does in a lot of cases when the application is shutting down); the thread hold no resources other than its own internal variables (which are being freed as the problem shows...), so I see little difference in suspending it milliseconds before it would die anyhow along with the application – hundreAd Feb 23 '23 at 19:28
  • 1
    @hundreAd "*if by "active thread" you mean the "main" vcl thread*" - no, I meant the worker thread that is crashing when the array it is accessing is being ripped out of memory while the thread is still running. "*the thread hold no resources other than its own internal variables*" - that is not true. It has a local reference to a dynamic array which is stored elsewhere in memory. That dynamic memory is what is disappearing when the RTL's memory manager is cleaned up during shutdown, while the thread is still running – Remy Lebeau Feb 23 '23 at 20:01
  • Remy Lebeau: i stand corrected. nonetheless, ``suspend`` prevents "while the thread is still running", which is [as far as i can see] the effective equivalent of breaking out of the loop with respect to the thread is no longer accessing the array (i do realize pausing and exiting the loop are not exactly identical) – hundreAd Feb 23 '23 at 21:20
  • Remy Lebeau: regardless, the question is what can i do regarding all this, i cannot tested for ```terminated``` on each line, plus this will kill performance (this is a genetic algorithm, 500000 generations * 10000 solution pool * 290 solution length), if i am forced to choose between performance and an annoying error happening only when the application is exit while the thread is running (and even then Delphi part of the time manages to quietly swallow the error). for the function that is ultimately run see: stackoverflow.com/questions/75526077/multi-threading-slower-than-single-thread – hundreAd Feb 23 '23 at 21:31