mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-12-12 10:30:38 +01:00
368 lines
9.1 KiB
ObjectPascal
368 lines
9.1 KiB
ObjectPascal
unit utcfpmonitor;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
{ $DEFINE DEBUG_MONITOR}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fpcunit, testregistry,
|
|
{$ifdef windows}
|
|
fpwinmonitor
|
|
{$else}
|
|
fpmonitor
|
|
{$endif};
|
|
|
|
const
|
|
WaitPeriod = 10;
|
|
WaitTimeout = 2000;
|
|
MaxObjCount = 2;
|
|
MaxThrdCount = 5;
|
|
|
|
Type
|
|
TThreadOperation = (toNone,toEnter,toTryEnter,toExit,toPulse,toWait,toPulseAll);
|
|
TOperationResult = Record
|
|
Op : TThreadOperation;
|
|
Tick : Int64;
|
|
Res : Boolean;
|
|
end;
|
|
|
|
TTestObject = Class(TObject)
|
|
// Operation/Timestamp when a thread performed a task
|
|
Res : Array[1..MaxThrdCount] of TOperationResult;
|
|
end;
|
|
|
|
{ TTestThread }
|
|
TTestThread = Class(TThread)
|
|
Private
|
|
FObj : TTestObject;
|
|
FOperation : TThreadOperation;
|
|
FTimeout : Integer;
|
|
FID : Integer;
|
|
Constructor Create(aObj : TTestObject; aOperation : TThreadOperation; aId,aTimeout : Integer; aOnFree : TNotifyEvent);
|
|
Public
|
|
Procedure Execute; override;
|
|
end;
|
|
{ TTestMonitorSupport }
|
|
|
|
TTestMonitorSupport = Class(TTestCase)
|
|
private
|
|
FThrdCount : Integer;
|
|
FObj : Array[1..MaxObjCount] of TTestObject;
|
|
FThrd : Array[1..MaxThrdCount] of TThread;
|
|
function DoCreateThread(aObj: TTestObject; aOperation: TThreadOperation; aId, aTimeout: Integer): TTestThread;
|
|
class procedure AssertEquals(Msg: String; aExpected, aActual: TThreadOperation); overload;
|
|
function GetObj(AIndex: Integer): TTestObject;
|
|
procedure ThreadDone(Sender : TObject);
|
|
procedure WaitForAllThreads(aTimeOut: Integer=0);
|
|
public
|
|
Procedure Setup; override;
|
|
Procedure TearDown; override;
|
|
Property Obj1 : TTestObject Index 1 Read GetObj;
|
|
Property Obj2 : TTestObject Index 2 Read GetObj;
|
|
Published
|
|
Procedure TestHookup;
|
|
Procedure TestLock;
|
|
Procedure TestLockMulti;
|
|
Procedure TestTryLock;
|
|
Procedure TestPulse;
|
|
Procedure TestPulseAll;
|
|
procedure TestWait;
|
|
end;
|
|
|
|
implementation
|
|
|
|
Uses TypInfo;
|
|
|
|
{ TTestThread }
|
|
|
|
constructor TTestThread.Create(aObj: TTestObject; aOperation: TThreadOperation; aId,aTimeout: Integer; aOnFree : TNotifyEvent);
|
|
begin
|
|
FObj:=aObj;
|
|
FOperation:=aOperation;
|
|
FTimeout:=aTimeout;
|
|
FID:=aID;
|
|
FreeOnTerminate:=True;
|
|
OnTerminate:=aOnfree;
|
|
Inherited Create(false);
|
|
end;
|
|
|
|
procedure TTestThread.Execute;
|
|
|
|
var
|
|
OpRes : TOperationResult;
|
|
|
|
begin
|
|
{$IFDEF DEBUG_MONITOR} Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' Begin executing operation ',FOperation);{$ENDIF}
|
|
try
|
|
OpRes.Res:=True;
|
|
Case FOperation of
|
|
toEnter : TMonitor.Enter(Fobj);
|
|
toTryEnter : OpRes.Res:=TMonitor.TryEnter(Fobj);
|
|
toExit : TMonitor.Exit(Fobj);
|
|
toPulse : begin
|
|
Sleep(WaitPeriod * 2);
|
|
TMonitor.Pulse(Fobj);
|
|
end;
|
|
toPulseAll :
|
|
begin
|
|
TMonitor.Enter(Fobj);
|
|
OpRes.Res:=TMonitor.Wait(FObj,FTimeout);
|
|
end;
|
|
toWait :
|
|
begin
|
|
TMonitor.Enter(Fobj);
|
|
OpRes.Res:=TMonitor.Wait(FObj,FTimeout);
|
|
end;
|
|
end;
|
|
OpRes.Tick:=GetTickCount64;
|
|
OpRes.Op:=FOperation;
|
|
FObj.Res[FID]:=OpRes;
|
|
// We need to clean up !
|
|
Case FOperation of
|
|
toEnter,
|
|
toWait,
|
|
toPulseAll,
|
|
toTryEnter:
|
|
begin
|
|
if OpRes.Res then
|
|
begin
|
|
TMonitor.Exit(Fobj);
|
|
{$IFDEF DEBUG_MONITOR} Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' Unlocking previously locked object ',FOperation);{$ENDIF}
|
|
end;
|
|
end;
|
|
else
|
|
//
|
|
end;
|
|
|
|
except
|
|
On E : Exception do
|
|
Writeln(StdErr,GetTickCount64,': Thread ',ptruint(GetCurrentThreadID),' exception ',E.ClassName,' during operation ',FOperation,' : ',E.Message);
|
|
end;
|
|
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' End executing operation ',FOperation);{$ENDIF}
|
|
end;
|
|
|
|
{ TTestMonitorSupport }
|
|
|
|
function TTestMonitorSupport.GetObj(AIndex: Integer): TTestObject;
|
|
begin
|
|
Result:=FObj[aIndex];
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.ThreadDone(Sender: TObject);
|
|
|
|
var
|
|
aCount,I : Integer;
|
|
|
|
begin
|
|
aCount:=0;
|
|
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' Begin done executing');{$ENDIF}
|
|
For I:=1 to MaxThrdCount do
|
|
begin
|
|
if FThrd[i]=Sender then
|
|
begin
|
|
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' Done executing: found thread at ',I){$ENDIF};
|
|
FThrd[i]:=Nil;
|
|
end
|
|
else if assigned(FThrd[I]) then
|
|
inc(aCount);
|
|
end;
|
|
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadID,' End done executing. Threads still active: ',aCount);{$ENDIF}
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.WaitForAllThreads(aTimeOut : Integer = 0);
|
|
|
|
var
|
|
I : Integer;
|
|
Last,Start : Int64;
|
|
TimeOut,OK : Boolean;
|
|
|
|
begin
|
|
If aTimeOut=0 then
|
|
aTimeout:=WaitTimeout;
|
|
Start:=GetTickCount64;
|
|
{$IFDEF DEBUG_MONITOR} Writeln(StdErr,Start,': Thread ',GetCurrentThreadID,' Waiting for ', FThrdCount,' threads to stop');{$ENDIF}
|
|
Timeout:=False;
|
|
Repeat
|
|
OK:=True;
|
|
CheckSynchronize(5);
|
|
For I:=1 to MaxThrdCount do
|
|
OK:=OK and (FThrd[i]=Nil);
|
|
if not Ok then
|
|
begin
|
|
sleep(10);
|
|
Last:=GetTickCount64;
|
|
TimeOut:=(Last-Start)>aTimeout;
|
|
end;
|
|
Until OK or TimeOut;
|
|
{$IFDEF DEBUG_MONITOR}
|
|
if not OK then
|
|
Writeln(StdErr,Last,': Thread ',GetCurrentThreadId,' Not all threads stopped');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
procedure TTestMonitorSupport.Setup;
|
|
|
|
var
|
|
I : Integer;
|
|
|
|
begin
|
|
inherited Setup;
|
|
FThrdCount:=0;
|
|
For I:=1 to MaxObjCount do
|
|
FObj[i]:=TTestObject.Create;
|
|
For I:=1 to MaxThrdCount do
|
|
FThrd[i]:=Nil;
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TearDown;
|
|
|
|
var
|
|
I : Integer;
|
|
|
|
begin
|
|
FThrdCount:=0;
|
|
For I:=1 to MaxObjCount do
|
|
FreeAndNil(FObj[i]);
|
|
For I:=1 to MaxThrdCount do
|
|
FThrd[i]:=Nil;
|
|
inherited TearDown;
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestHookup;
|
|
|
|
var
|
|
I : integer;
|
|
|
|
begin
|
|
For I:=1 to MaxObjCount do
|
|
AssertNotNull('Obj '+IntToStr(i),FObj[I]);
|
|
For I:=1 to MaxThrdCount do
|
|
AssertNull('Thrd '+IntToStr(i),FThrd[I]);
|
|
end;
|
|
|
|
function TTestMonitorSupport.DoCreateThread(aObj: TTestObject; aOperation: TThreadOperation; aId, aTimeout: Integer): TTestThread;
|
|
|
|
begin
|
|
Inc(FThrdCount);
|
|
FThrd[FThrdCount]:=TTestThread.Create(aObj,aOperation,Aid,aTimeout,@ThreadDone);
|
|
Result:=TTestThread(FThrd[FThrdCount]);
|
|
end;
|
|
|
|
class procedure TTestMonitorSupport.AssertEquals(Msg: String; aExpected, aActual: TThreadOperation);
|
|
begin
|
|
AssertEquals(Msg,GetEnumName(TypeInfo(TOperationResult),Ord(aExpected)),
|
|
GetEnumName(TypeInfo(TOperationResult),Ord(aActual)));
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestLock;
|
|
|
|
var
|
|
N : Int64;
|
|
|
|
begin
|
|
TMonitor.Enter(Obj1);
|
|
DoCreateThread(Obj1,toEnter,1,0);
|
|
Sleep(WaitPeriod);
|
|
N:=GetTickCount64;
|
|
TMonitor.Exit(Obj1);
|
|
WaitForAllThreads;
|
|
AssertTrue('Thread lock timestamp ',N<=FObj[1].Res[1].Tick);
|
|
AssertEquals('Thread did a lock ',toEnter,Obj1.Res[1].Op);
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestLockMulti;
|
|
|
|
var
|
|
N : Int64;
|
|
I : integer;
|
|
|
|
begin
|
|
TMonitor.Enter(Obj1);
|
|
For I:=1 to MaxThrdCount do
|
|
DoCreateThread(Obj1,toEnter,I,0);
|
|
Sleep(WaitPeriod);
|
|
N:=GetTickCount64;
|
|
TMonitor.Exit(Obj1);
|
|
WaitForAllThreads;
|
|
AssertTrue('Thread lock timestamp ',N<=FObj[1].Res[1].Tick);
|
|
AssertEquals('Thread did a lock ',toEnter,Obj1.Res[1].Op);
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestTryLock;
|
|
|
|
begin
|
|
TMonitor.Enter(Obj1);
|
|
DoCreateThread(Obj1,toTryEnter,1,0);
|
|
Sleep(WaitPeriod);
|
|
TMonitor.Exit(Obj1);
|
|
Writeln(GetTickCount64,': Thread ',ptruint(GetCurrentThreadID),' Released lock');
|
|
WaitForAllThreads;
|
|
AssertEquals('Thread tried a lock ',toTryEnter,Obj1.Res[1].Op);
|
|
AssertFalse('Thread lock failed ',Obj1.Res[1].Res);
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestPulse;
|
|
|
|
var
|
|
N : Int64;
|
|
|
|
begin
|
|
// Acquire the lock
|
|
TMonitor.Enter(Obj1);
|
|
DoCreateThread(Obj1,toPulse,1,INFINITE);
|
|
Sleep(WaitPeriod);
|
|
N:=GetTickCount64;
|
|
TMonitor.Wait(Obj1,500);
|
|
TMonitor.Exit(Obj1);
|
|
WaitForAllThreads;
|
|
AssertTrue('Thread pulse timestamp ',N<=FObj[1].Res[1].Tick);
|
|
AssertEquals('Thread did a pulse',toPulse,Obj1.Res[1].Op);
|
|
AssertTrue('Thread Wait was successful',Obj1.Res[1].Res);
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestPulseAll;
|
|
|
|
var
|
|
N : Int64;
|
|
i : integer;
|
|
|
|
begin
|
|
// Acquire the lock
|
|
For I:=1 to MaxThrdCount do
|
|
DoCreateThread(Obj1,toPulseAll,I,INFINITE);
|
|
Sleep(WaitPeriod*MaxThrdCount);
|
|
N:=GetTickCount64;
|
|
TMonitor.PulseAll(Obj1);
|
|
WaitForAllThreads(WaitTimeOut*MaxThrdCount);
|
|
For I:=0 to MaxThrdCount do
|
|
begin
|
|
AssertEquals('Thread '+IntToStr(i)+' did a Wait',toPulseAll,Obj1.Res[1].Op);
|
|
AssertTrue('Thread '+IntToStr(i)+' Wait was successful',Obj1.Res[1].Res);
|
|
AssertTrue('Thread '+IntToStr(i)+' pulse timestamp ',N<=FObj[1].Res[1].Tick);
|
|
end;
|
|
end;
|
|
|
|
procedure TTestMonitorSupport.TestWait;
|
|
|
|
var
|
|
N : Int64;
|
|
|
|
begin
|
|
// Acquire the lock
|
|
DoCreateThread(Obj1,toWait,1,INFINITE);
|
|
Sleep(WaitPeriod*4);
|
|
N:=GetTickCount64;
|
|
TMonitor.Pulse(Obj1);
|
|
WaitForAllThreads;
|
|
AssertEquals('Thread did a Wait',toWait,Obj1.Res[1].Op);
|
|
AssertTrue('Thread Wait was successful',Obj1.Res[1].Res);
|
|
AssertTrue('Thread pulse timestamp ',N<=FObj[1].Res[1].Tick);
|
|
end;
|
|
|
|
initialization
|
|
RegisterTest(TTestMonitorSupport);
|
|
end.
|
|
|