TROCustomSessionManager is not threadsafe

Currently, version 9.3.105.1531 of the TROCustomSessionManager is not threadsafe in the way sessions are deleted. It took me some while to create a minimal example to show the problem, and yes it is weird code, but the problem is similar to one that we found in large-scale products.

With this code you can determine that the problem exists.

procedure TTestForm.Button2Click(Sender: TObject);
var
  Session: TROSession;
  Sessions: TList<TROSession>;
  SessionManager: TROCustomSessionManager;
  i: Integer;
begin
  SessionManager := TROInMemorySessionManager.Create(nil);
  Sessions := TList<TROSession>.Create;
  for i := 1 to 1000 do
  begin
    Session := SessionManager.CreateSession(TGuid.NewGuid);
    Sessions.Add(Session);
    SessionManager.ReleaseSession(Session, True);
  end;
  try
    TTask.Run(
      procedure
      begin
        SessionManager.ClearSessions(False);
      end);
    TTask.Run(
      procedure
      var
        Session: TROSession;
      begin
        for Session in Sessions do
          SessionManager.DeleteSession(Session.SessionID, False);
      end);
    Sleep(10000);
  finally
    SessionManager.Free;
    Sessions.Free;
  end;
end;

The solution for this problem is:

procedure TROCustomSessionManager.DeleteSession(const aSessionID: TGUID; IsExpired : boolean);
var
  lRetry: Boolean;
begin
  CreateTimerByRequest;
  fInDeleteSession:=True;
  try
    if Assigned(FOnBeforeDeleteSession) then FOnBeforeDeleteSession(aSessionID, IsExpired);
**//    if not Clearing then fCritical.Enter; //Always use fCritical**
    fCritical.Enter;
    try
      lRetry := True;
      while lRetry do begin
        lRetry := False;
        try
          DoDeleteSession(aSessionID, IsExpired);
        except
          on e: Exception do begin
            if assigned(fOnException) then fOnException(aSessionID, e, lRetry);
            if not lRetry then raise;
          end;
        end;
      end;

      DoNotifySessionsChangesListener(aSessionID, saDelete, nil);
      if Assigned(fOnSessionDeleted) then fOnSessionDeleted(aSessionID, IsExpired);
    finally
**//      if not Clearing then fCritical.Leave; //And thus, always leave fCritical**
      fCritical.Leave;
    end;
  finally
    fInDeleteSession:=False;
  end;
end;

Thanks, logged as bugs://79285

bugs://79285 got closed with status fixed.