首页 > 解决方案 > Delphi EurekaLog 和 OmniThreadLibrary 不兼容?

问题描述

我有一个简单的程序,它在一个单元的“初始化”中创建一个 OmniThread 工作池,并在该单元的“最终化”中销毁同一个池。只要我们不使用 EurekaLog,它就可以正常工作。如果我们包含 EurekaLog,则会在应用程序完成期间(关闭应用程序后)引发访问冲突。这种情况每 3 到 10 次程序结束才会发生一次;所以这似乎是某种时间问题。

如果我们在“正常”应用程序流程中创建工作池(而不是在单独单元的初始化和最终确定中) ,似乎一切正常。

本单元代码如下:

unit Unit1;

interface

uses
  OtlParallel;

var
  _Worker: IOmniBackgroundWorker;

implementation

initialization
  _Worker := Parallel.BackgroundWorker.NumTasks(10)
    .Execute(
      procedure(const AWorkItem: IOmniWorkItem)
      begin
        //
      end
    );

finalization
  _Worker.Terminate(INFINITE);
  _Worker := nil;

end.

主要应用也很简单:


uses
  {$IFDEF EurekaLog}
  EMemLeaks,
  EResLeaks,
  EDebugExports,
  EDebugJCL,
  EFixSafeCallException,
  EMapWin32,
  EAppVCL,
  EDialogWinAPIMSClassic,
  EDialogWinAPIEurekaLogDetailed,
  EDialogWinAPIStepsToReproduce,
  ExceptionLog7,
  {$ENDIF EurekaLog}
  Vcl.Forms,
  Unit1 in 'Unit1.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.Run;
end.

访问冲突的调用堆栈是:

:5653e4c4 
OtlTaskControl.TOmniTask.Execute
OtlThreadPool.TOTPWorkerThread.ExecuteWorkItem($393A160)
OtlThreadPool.TOTPWorkerThread.Execute
System.Classes.ThreadProc($3976800)
EThreadsManager.NakedBeginThreadWrapper(???)
:76ee6359 KERNEL32.BaseThreadInitThunk + 0x19
:77628944 ntdll.RtlGetAppContainerNamedObjectPath + 0xe4
:77628914 ntdll.RtlGetAppContainerNamedObjectPath + 0xb4

我使用 OTL 和 EurekaLog 版本 7.9.1.4 update 1 hot-fix 4 的最新 master checkout。

我们创建和销毁工作池的方式是否正确?如果是这样,如果它们一起使用,是否有人熟悉 OTL/EurekaLog 中的问题?

标签: delphiomnithreadlibraryeurekalog

解决方案


这似乎是一个缺陷/未在 OTL 中实现:

destructor TOmniTaskControl.Destroy;
begin
  // TODO 1 -oPrimoz Gabrijelcic : ! if we are being scheduled, the thread pool must be notified that we are dying !

看起来这段代码可以在线程池中调度一个任务,该任务仍在运行。

两者共享同一个TOmniTaskControl实例。如果从 的最终确定中删除,则仍在运行的线程池中的任何内容都将包含对已删除的引用。TOmniTaskTOmniSharedTaskInfoTOmniTaskControlOtlTaskControlTOmniTaskTOmniSharedTaskInfo

在没有 EurekaLog 的应用程序中这“不是问题”,因为已删除对象的内存将保持不变。因此,对已删除对象的访问将是成功的。但是,添加 EurekaLog 意味着使用调试模式擦除已处理的内存。因此,访问已删除的对象可能会失败。

具体来说,当TOmniTask尝试访问已经删除的代码时,代码会崩溃TOmniSharedTaskInfo

procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
// ...
begin
  otCleanupLock.EnterWriteLock;
// ...
      finally
        otCleanupLock.EnterWriteLock;
        if assigned(otSharedInfo_ref.ChainTo) and
           (otSharedInfo_ref.ChainIgnoreErrors or (otExecutor_ref.ExitCode = EXIT_OK))
        then
          chainTo := otSharedInfo_ref.ChainTo; // - fails here inside System.@IntfCopy
        otSharedInfo_ref.ChainTo := nil;
// ...

无法访问的:5653e4c4位置只是一个地址,System.@IntfCopy当威胁来自otSharedInfo_ref.

您可以通过禁用 EurekaLog 中的“捕获内存问题”选项(在处理时覆盖对象/接口 VMT)并将“释放内存时”选项设置为“什么都不做”来确认这是一个“释放后使用”问题。如果你这样做 - 提到的访问已删除otSharedInfo_ref/TOmniSharedTaskInfo内部的崩溃TOmniTask.InternalExecute将不再发生(事实上 - 因为现在删除的otSharedInfo_ref内容将保持不变,因此可以访问)。

此外,现在将发现一个新的内存泄漏:

OtlSync.pas TOmniCriticalSection.Create
OtlSync.pas CreateOmniCriticalSection
OtlSync.pas TOmniCS.Initialize
OtlTaskControl.pas TOmniCS.Acquire
OtlTaskControl.pas TOmniTask.InternalExecute
OtlTaskControl.pas TOmniTask.Execute
OtlThreadPool.pas TOTPWorkerThread.ExecuteWorkItem
OtlThreadPool.pas TOTPWorkerThread.Execute
System.Classes.pas ThreadProc

这种泄漏发生在otSharedInfo_ref.MonitorLock.Acquire内部TOmniTask.InternalExecute。这发生在前面提到的附近chainTo := otSharedInfo_ref.ChainTo

这只是对“免费后使用”错误的另一个确认。这是因为通常当字段作为' 销毁的一部分被删除时,ostiMonitorLock: TOmniCriticalSection字段 ofTOmniSharedTaskInfo将被最终确定。otSharedInfo_ref: TOmniSharedTaskInfoTOmniTaskControl

但是,如果存在“释放后使用”错误 - 那么当仍然存在对相同的引用时,将TOmniTaskControl被删除。这意味着尝试将使用全新的关键部分重新初始化。并且由于和它已经消失了 - 将没有代码来清理这个新创建的关键部分。实际上,您刚刚创建了一个新的临界区作为已删除对象 ( ) 中的一个字段!TOmniTaskTOmniSharedTaskInfootSharedInfo_ref.MonitorLock.AcquireostiMonitorLock: TOmniCriticalSectionTOmniTaskControlTOmniSharedTaskInfoTOmniSharedTaskInfo


推荐阅读