Use exponential backoff in timeouted ‘TMonitor.Enter’ and explain why it’s still a bad solution.

This commit is contained in:
Rika Ichinose 2024-02-26 19:32:34 +03:00 committed by Michael Van Canneyt
parent d1432b7302
commit c2176d27ea

View File

@ -206,17 +206,57 @@ end;
function TMonitorData.Enter(aTimeout: Cardinal): Boolean;
type
StageEnum = (Spin, ThreadSwitch, SleepA, SleepB, SleepC, SleepD, SleepE, SleepF);
const
SleepTime: array[SleepA .. High(StageEnum)] of uint8 = (2, 4, 8, 16, 30, 50);
StageIterations: array[StageEnum] of uint8 = (40, 40, 8, 8, 8, 8, 8, 8);
var
Start : Int64;
TimeA,TimeB,Elapsed : Int64;
Stage : StageEnum;
StageIteration,TimeToSleep : uint32;
begin
// Should preferably use an event raised on Leave somehow.
// And this event should preferably not exist until someone actually uses timeouted Enter, ant not be raised until there are outstanding timeouted Enters.
// Sounds complex, so until then, spin-wait + exponentially wait.
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadId,' Begin Enter(',aTimeout,')');{$ENDIF}
Start:=GetTickCount64;
TimeA:=-1;
Stage:=Spin;
Int32(StageIteration):=-1;
Repeat
Result:=TryEnter;
if not Result then
Sleep(2);
until Result or ((GetTickCount64-Start)>aTimeout);
if Result or (aTimeout=0) then
break;
if TimeA=-1 then
TimeA:=GetTickCount64; // Avoid GetTickCount64 call if first TryEnter succeeds. -1 is a possible timestamp, but nothing particularly bad will happen.
Inc(Int32(StageIteration));
if StageIteration>=StageIterations[Stage] then
begin
if Stage<High(Stage) then
Inc(Stage);
StageIteration:=0;
end;
case Stage of
Spin: ;
ThreadSwitch: System.ThreadSwitch;
SleepA .. High(StageEnum):
begin
TimeToSleep:=SleepTime[Stage];
if aTimeout<TimeToSleep then
TimeToSleep:=aTimeout;
SysUtils.Sleep(TimeToSleep);
end;
end;
TimeB:=GetTickCount64;
Elapsed:=TimeB-TimeA;
TimeA:=TimeB; // Sum of Elapseds will always be exactly <current time> - <start time>.
if Elapsed>=aTimeout then
break;
aTimeout:=aTimeout-Elapsed;
until false;
{$IFDEF DEBUG_MONITOR}Writeln(StdErr,GetTickCount64,': Thread ',GetCurrentThreadId,' End Enter(',aTimeout,'), Result: ',Result);{$ENDIF}
end;