I have a simple program that creates an OmniThread workerpool in the 'initialization' of a unit and destroys the same pool in the 'finalization' of that unit. This works fine as long as we do not use EurekaLog. If we include EurekaLog an access violation is raised during finalization of the application (after closing the app). This happens only once every 3 to 10 times the program ends; so it seems some sort of timing issue.
It seems that it all works fine, if we create the workerpool in the "normal" application flow (not in the initialization and finalization of a seperate unit).
The code of this unit is as follows:
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.
The main application is also simple:
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.
The callstack on the access violation is:
: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
I use the latest master checkout of OTL and EurekaLog version 7.9.1.4 update 1 hot-fix 4.
Is the way we create and destroy the workerpool correct? And, if so, is anybody familiar with issues in OTL/EurekaLog if they are used together?
It appears to be a flaw / not implemented in OTL:
It looks like this code can have a task sheduled in the thread pool, which is still running.
Both
TOmniTaskControlandTOmniTaskshare the sameTOmniSharedTaskInfoinstance. IfTOmniTaskControlis deleted from finalization ofOtlTaskControl, then anyTOmniTaskin still running thread pool will contain reference to already deletedTOmniSharedTaskInfo.It is "not a problem" in app without EurekaLog, since memory of deleted object will be unchanged. Thus, access to already deleted object will be successful. However, adding EurekaLog means erasing disposed memory with a debug pattern. Thus, accessing already deleted object may fail.
Specifically, the code crashes when
TOmniTasktries to access already deletedTOmniSharedTaskInfo:The inaccessible
:5653e4c4location is just an address, whichSystem.@IntfCopytries to read when threating trashed fields fromotSharedInfo_ref.You can confirm that it is a "use after free" issue by disabling "Catch memory problems" option in EurekaLog (which overwrites object/interface VMT on disposal) and setting "When memory is released" option to "Do nothing". If you do that - the mentioned crash accessing already deleted
otSharedInfo_ref/TOmniSharedTaskInfoinsideTOmniTask.InternalExecutewill no longer occur (indeed - because now the deletedotSharedInfo_refwill be unchanged, thus will be accessible).Additionally, a new memory leak will now be found instead:
This leak occurs at
otSharedInfo_ref.MonitorLock.AcquireinsideTOmniTask.InternalExecute. This happens near previosly mentionedchainTo := otSharedInfo_ref.ChainTo.It is just another confirmation of "use after free" bug. That is because normally
ostiMonitorLock: TOmniCriticalSectionfield ofTOmniSharedTaskInfowould be finalized when theotSharedInfo_ref: TOmniSharedTaskInfofield is deleted as part ofTOmniTaskControl's destruction.However, if there is "use after free" bug - then the
TOmniTaskControlis being deleted while there isTOmniTaskalive still holding reference to the sameTOmniSharedTaskInfo. This means that attempt tootSharedInfo_ref.MonitorLock.Acquirewill re-initializeostiMonitorLock: TOmniCriticalSectionwith a brand new critical section. And since theTOmniTaskControland itsTOmniSharedTaskInfois already gone - there will be no code to cleanup this newly created critical section. Indeed, you just created a new critical section as a field inside already deleted object (TOmniSharedTaskInfo)!