Browse Source

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

Rika Ichinose 1 year ago
parent
commit
c2176d27ea
1 changed files with 45 additions and 5 deletions
  1. 45 5
      packages/rtl-objpas/src/inc/fpmonitor.pp

+ 45 - 5
packages/rtl-objpas/src/inc/fpmonitor.pp

@@ -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;