Still not sure why the RTL code is like this:
procedure TRemotable.SetDataContext(Value: TDataContext);
begin
if (RemotableDataContext <> nil) and (RemotableDataContext = Self.DataContext) then
begin
TDataContext(RemotableDataContext).RemoveObjectToDestroy(Self);
end;
FDataContext := Value;
end;
It means that if you ever have to change the DataContext property from the default global RemotableDataContext, it will be removed, but not added to the new DataContext.
When you assign it nil (so you can dump it to JSON, which often is easier to read than XML), and back to the old value, this results in a memory leak:
function TNotificationKeyPortTypeImplementation.prematchChanged(const prematchChangedRequest: prematchChangedRequest): prematchChangedResponse;
var
DataContext: TDataContext;
begin
// ...
DataContext := prematchChangedRequest.DataContext;
try
prematchChangedRequest.DataContext := nil; // otherwise the JSON serializer will stackoverflow because DataContext points back to the TRemotable instance.
Result := Format('prematchChanged: prematchChangedRequest=%s', [TJson.ObjectToJsonString(prematchChangedRequest)]);
finally
// `prematchChangedRequest.DataContext := nil` removed `prematchChangedRequest` from `DataContext.FObjsToDestroy`
DataContext.AddObjectToDestroy(prematchChangedRequest);
prematchChangedRequest.DataContext := DataContext; // does not add `prematchChangedRequest` to `DataContext.FObjsToDestroy`
end;
end;
or when you are outside an incoming SOAP call where DataContext might not be assigned at all:
function ToJSON(const Value: TRemotable): string;
var
DataContext: TDataContext;
RemotableDataContext: Pointer;
begin
if Assigned(Value) then
begin
DataContext := Value.DataContext;
try
Value.DataContext := nil; // otherwise the JSON serializer will stackoverflow because DataContext points back to the TRemotable instance.
Result := TJson.ObjectToJsonString(Value);
Result := TRegExSanitiser.ReplaceSecretInText(Result, [rttJSON]);
finally
// `Value.DataContext := nil` removed `Value` from `DataContext.FObjsToDestroy`
if Assigned(DataContext) then
begin
RemotableDataContext := GetRemotableDataContext();
if Assigned(RemotableDataContext) and (RemotableDataContext = DataContext) then
DataContext.AddObjectToDestroy(Value);
Value.DataContext := DataContext; // does not add `Value` to `DataContext.FObjsToDestroy`
end;
end;
end
else
Result := '';
end;
–jeroen