delphiparallel-processingomnithreadlibraryrad-studio

"Emergency" termination of omnithread IOmniParallelTask


Background

I have a unit test in which I check if my handler code code performs well during multi-thread stress:

procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
begin
  var bt:=Parallel.ParallelTask.NumTasks(1).NoWait.Execute(
    procedure
    begin
      SetWin32ThreadName('TestLoopedAppProgressRelease_SubThread');
      RunLoopedAppProgressRelease;
    end
  );
  lSuccess:=bt.WaitFor(cRunLoopTimerMilliSecs*2);
  if not lSuccess then
    bt.Terminate; // emergency termination, unit test failed <<< How do I do this?
  Check(lSuccess,'Failed to finish within expected time');
end;

in case the parallel thread fails to complete within the expected time, something is wrong and my check fails. Unfortunately if the parallel task hangs, it never gets to an end and my unit test freezes because of the release of the bt interface at the end of my routine that waits for the never ending parallel task to complete.

So I need to shutdown my parallel task the evil way.

NOTE 1: It's a unit test, I don't really care about the thread cleanup: something needs to be fixed anyway if the unit test fails. I just don't want my entire unit test suite to hang/freeze, but simply report the failure and to continue with the next test in my suite.

NOTE 2: The Numthreads(1) could be omitted or an arbitrary number of threads.

Here's the Q:How can I terminate an IOmniParallel task forcibly?


Solution

  • Okay, I should have done better research. Here's the solution for a single background thread, just use the IOmniTaskControl interface:

    procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
    begin
      var lTask:=CreateTask(
        procedure (const aTask:IOmniTask)
        begin
          RunLoopedAppProgressRelease;
        end,
        'TestLoopedAppProgressRelease_SubThread'
      );
      lTask.Run;
      var lSuccess:=lTask.WaitFor(cRunLoopTimerMilliSecs*2);
      if not lSuccess then
        lTask.Terminate; // emergency termination, unit test failed
      Check(lSuccess,'Failed to finish within expected time');
    end;
    

    And in case you want to run it in multiple subthreads, you need to wrap it up in an IOmniTaskGroup:

    procedure TestAppProgress.TestLoopedAppProgressRelease_MultiSubThread;
    const cThreads=4;
    begin
      var lTaskGroup:=CreateTaskGroup;
      for var i:=1 to cThreads do
      begin
        var lTask:=CreateTask(
          procedure (const aTask:IOmniTask)
          begin
            RunLoopedAppProgressRelease;
          end,
          'TestLoopedAppProgressRelease_SubThread '+i.ToString
        );
        lTaskGroup.Add(lTask);
      end;
      lTaskGroup.RunAll;
      var lSuccess:=lTaskGroup.WaitForAll(cRunLoopTimerMilliSecs*2);
      if not lSuccess then
        lTaskGroup.TerminateAll; // emergency termination, unit test failed
      Check(lSuccess,'Failed to finish within expected time');
    end;
    

    Using delays/breakpoints in my debugger I verfied the unit test (error) handling now works as expected. This is including expected memory leaks due to the killed threads.

    I still feel IOmniParallelTask should have an Terminate method similar to the ones in IOmniTask and IOmniTaskGroup