我有一个简单的程序,它在单元的“初始化”中创建一个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更新1热修复4的主签出。
我们创建和破坏工人池的方式正确吗?如果是这样的话,是否有人熟悉OTL/EurekaLog中的问题(如果它们一起使用)?
发布于 2021-02-16 13:17:37
这似乎是一个缺陷/未在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和TOmniTask共享同一个TOmniSharedTaskInfo实例。如果从TOmniTaskControl的终结中删除了OtlTaskControl,那么仍在运行的线程池中的任何TOmniTask都将包含对已经删除的TOmniSharedTaskInfo的引用。
在没有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 at TOmniTask.InternalExecute。这发生在前面提到的chainTo := otSharedInfo_ref.ChainTo附近。
这只是“免费后使用”bug的又一次确认。这是因为通常在删除ostiMonitorLock: TOmniCriticalSection字段作为TOmniSharedTaskInfo销毁的一部分时,otSharedInfo_ref: TOmniSharedTaskInfo字段将被最后确定。
但是,如果存在“免费后使用”错误--那么TOmniTaskControl将被删除,而TOmniTask活动时仍保留对同一个TOmniSharedTaskInfo的引用。这意味着尝试otSharedInfo_ref.MonitorLock.Acquire将使用全新的关键部分重新初始化ostiMonitorLock: TOmniCriticalSection。而且,由于TOmniTaskControl和它的TOmniSharedTaskInfo已经消失了,所以不会有任何代码来清理这个新创建的关键部分。实际上,您刚刚创建了一个新的关键部分,作为已删除对象(TOmniSharedTaskInfo)中的字段!
https://stackoverflow.com/questions/66205938
复制相似问题