Omnithread unobserved nested thread not released when expected

163 views Asked by At

Background

My Flagship App seems to leak memory. Well, not really but just during runtime. Investigating this showed the memory 'leak' is resolved when the app closes, but not in between.

I am using a background thread , that iself starts another thread. Both threads (parent and child) are 'Unobserved' which should result in the TOmniTaskControl object to be released and freed when the tread is finished.

Experimenting code

procedure TfrmMain.MyTestProc(const aTask: IOmniTask);
begin
  sleep(100);
  SGOutputDebugStringFmt('%s.MyTestProc for %s',[ClassName,aTask.Name]);
  sleep(100);
end;

procedure TfrmMain.MyNestedTestProc(const aTask: IOmniTask);
var lTask:IOmniTaskControl;
begin
  sleep(100);
  SGOutputDebugStringFmt('%s.MyTestProc for %s',[ClassName,aTask.Name]);
  lTask:=CreateTask(MyTestProc,'NestedTask');
  lTask.Unobserved.Run;
  sleep(100);
end;

procedure TfrmMain.btSimpleThreadClick(Sender: TObject);
var lTask:IOmniTaskControl;
begin
  lTask:=CreateTask(MyTestProc,'SimpleThread');
  lTask.Unobserved.Run;
end;

procedure TfrmMain.btNestedThreadClick(Sender: TObject);
var lTask:IOmniTaskControl;
begin
  lTask:=CreateTask(MyNestedTestProc,'NestedThread');
  lTask.Unobserved.Run;
end;

When debugging, and setting breakpoint on TOmniTaskControl.Destroy, and having a watch TOmniTaskControl.Name on I see the following:

  • btSimpleThreadCLick:
    1. TOmniTaskControl for 'SimpleThread' gets to be created
    2. TOmniTaskControl for 'SimpleThread' gets to be destroyed
  • btNestedThreadCLick:
    1. TOmniTaskControl for 'NestedThread' is Created
    2. TOmniTaskControl for 'NestedTask' is Created
    3. TOmniTaskControl for 'NestedThread' is Destroyed

Problem: TOmniTaskControl for 'NestedTask' is NOT destroyed. Another issue is that the OnTerminate isn't called either. Then, when closing the app, the TOmniTaskCOntrol for 'SimpleThread' is destroyed. (And also, the OnTherminate is fired)

Workaround

I came up with this solution which seems to do the trick. The thing is however, I do usually NOT run my subthreads from a form where a TOmniEventMonitor is at hand. So I'd have to create a global TOmniEventMonitor object for this. But isn't this the whole point of the UnObserved method?

procedure TfrmMain.MyNestedTestProc(const aTask: IOmniTask);
var lTask:IOmniTaskControl;
begin
  sleep(100);
  SGOutputDebugStringFmt('%s.MyTestProc for %s',[ClassName,aTask.Name]);
  lTask:=CreateTask(MyTestProc,'NestedTask');
  lTask.MonitorWith(OmniEventMonitor).Run; // OmniEventMonitor is a component on my form
  sleep(100);
end;

Well, secondary workarounbd... kind-of. It does not allow for my thread to be freed unattended. if I change the NestedTestProc to the code below, then the NestedTaskgets to be destroyed at the expected moment. Unfortunately, this solution is clearly not 'Unobserved'

procedure TfrmMain.MyNestedTestProc(const aTask: IOmniTask);
var lTask:IOmniTaskControl;
begin
  sleep(100);
  SGOutputDebugStringFmt('%s.MyTestProc for %s',[ClassName,aTask.Name]);
  lTask:=CreateTask(MyTestProc,'NestedTask');
  try
    lTask.Run;
    lTask.WaitFor(2000);
  finally
    lTask:=nil;
  end;
  sleep(100);
end;

Update 20201029 >>

An invalid handle (1400) error typically occurs when the master task was completed and its associated Monitor was already destroyed. So - The 'Master' task thread should not die in case there is an "owned" monitor that is monitoring other threads.

So to check this, I changed the timing (using sleep()) to ensure the child task was completed before the master task is completed. The Invalid handle error is gone now, and the COmniTaskMsg_Terminated message gets to be posted successfully. But still the ComniTaskMsg_Terminated from the child task is not processed. (I expected the thread of the MasterTask to handle this.) IMO there are 2 problems:

  1. life time management of the Unobserved monitor
  2. shutdown management of the Unobserved monitor, which should keep the "owning" thread alive and keep processing messages until all monitored threads/tasks are gone.

Also I wonder whether these shutdownmessages hould be handled/processed by the Application main thread (It seems this is the case now) or otherwise through a separate thread that checks all the monitors in GTaskControlEventMonitorPool . AIA, pretty complicated stuff :s...

Giving this some thought, monitors that were created by the application main thread (thus Monitor.ThreadID=MainThreadID) should be handle their messages in the main thread message loop, and all others probably need to be handled by a separate thread... Its just too confusing! I will see if I write a unit test for this, just to demonstrate what I expect to happen.

<< Update 20201029

The question

With OmniThreadLibrary, How can I use unobserved threads inside threads, and avoid the described memory leak?

0

There are 0 answers