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;