5

I have an AV when releasing a Form, it appears when I compress and send a FireDAC Dataset's data to a remote server.

This is the code I use to compress the TFDDataset's Data:

function CompressDataset(Dataset: TFDDataset): TMemoryStream;
var Data: TMemoryStream;
    Compress: TZCompressionStream;
begin
  Result := TMemoryStream.Create;   
  Data := TMemoryStream.Create;
  try
    Compress := TZCompressionStream.Create(Result);
    Dataset.SaveToStream(Data, TFDStorageFormat.sfBinary);
    Data.Position := 0;
    Compress.CopyFrom(Data, Data.Size);
  finally
    Data.Free;
    Compress.Free;
  end;
  Result.Position := 0;
end;

And this is the code to send that compressed data to the remote call (Datasnap).

procedure TfrmRentFacturacion_Facturar.btnSendDesgloseClick(Sender: TObject);
var Stream: TMemoryStream;
begin
  if qryFacturacion_Desglose.State = dsEdit then qryFacturacion_Desglose.Post;

  Stream := CompressDataset(qryFacturacion_Desglose);
  try
    spActualizaDesglose.ParamByName('AStream').AsStream := Stream;
    spActualizaDesglose.ExecProc;
  finally
    Stream.Free;
  end;
end;

This code leaves something unstable, most probably the TFDDataset qryFacturacion_Desglose, and raises an AV when releasing the form. But I don't get what could possible be wrong.

PS: Thanks to @J... suggestion to check the Call Stack I have found the source of the problem. This is the Call Stack :

:000000000040E735 TObject.Free + $15
:00000000007F1123 TParamObject.Destroy + $43
:000000000041A155 TInterfacedObject._Release + $55
:000007FEFF2211CE ; C:\Windows\system32\oleaut32.dll
:0000000000459DAB VarClearDeep + $1B
:0000000000459E6B @VarClear + $1B
:0000000000459E7D @VarClr + $D
:00000000004149F4 @VarClr + $14
:0000000000414ACC @FinalizeArray + $BC
:00000000004162F1 @DynArrayClear + $61
:0000000000414B58 @FinalizeArray + $148
:0000000000414985 @FinalizeRecord + $75
:000000000040E82E TObject.CleanupInstance + $4E
:000000000040E450 TObject.FreeInstance + $10
:000000000040F1C1 @ClassDestroy + $11
:000000000051ED43 TCollectionItem.Destroy + $43
:000000000040E738 TObject.Free + $18
:000000000051F40A TCollection.Clear + $5A
:000000000051F1CD TCollection.Destroy + $2D
:000000000084A858 TFDParams.Destroy + $88
:0000000000838FD8 FDFree + $18
:000000000084A8BB TFDParams.RemRef + $2B
:0000000000B8C907 TFDCustomCommand.Destroy + $57
:000000000040E738 TObject.Free + $18
:00000000005419F3 TComponent.DestroyComponents + $93
:000000000054117F TComponent.Destroy + $2F
:0000000000B92A66 TFDCustomTableAdapter.Destroy + $86
:0000000000B9BE02 TFDRdbmsDataSet.Destroy + $C2
:000000000040E738 TObject.Free + $18
:00000000005419F3 TComponent.DestroyComponents + $93
:000000000054117F TComponent.Destroy + $2F
:00000000006039C2 TControl.Destroy + $192
:000000000060AA91 TWinControl.Destroy + $1B1
:0000000000797273 TScrollingWinControl.Destroy + $73
:0000000000798EB7 TCustomForm.Destroy + $1E7
:000000000040E738 TObject.Free + $18
:00000000007A1389 TCustomForm.CMRelease + $9
:000000000040EE81 TObject.Dispatch + $41
:0000000000607D56 TControl.WndProc + $386
:000000000060EC07 TWinControl.WndProc + $8E7
:000000000079ADB0 TCustomForm.WndProc + $910
:000000000060DE4C TWinControl.MainWndProc + $2C
:0000000000545056 StdWndProc + $26
:00000000777D9BBD ; C:\Windows\system32\USER32.dll
:00000000777D98C2 ; C:\Windows\system32\USER32.dll
:00000000007A8E84 TApplication.ProcessMessage + $134
:00000000007A8EF8 TApplication.HandleMessage + $18
:00000000007A9364 TApplication.Run + $F4
Impuestos.Impuestos
:00000000776B59CD ; C:\Windows\system32\kernel32.dll
:00000000778EA561 ; ntdll.dll

The AV occurs when trying to free the Parameter AStream of the spActualizaDesglose TFDStoredProc that executes the remote call to the Datasnap Server.

I have changed the call, so it doesn't frees the original data Stream after executing the remote call.

 procedure TfrmRentFacturacion_Facturar.btnSendDesgloseClick(Sender: TObject);
    var Stream: TMemoryStream;
    begin
      if qryFacturacion_Desglose.State = dsEdit then qryFacturacion_Desglose.Post;

      Stream := CompressDataset(qryFacturacion_Desglose);
      spActualizaDesglose.ParamByName('AStream').AsStream := Stream;
      spActualizaDesglose.ExecProc;
    end;

Now the form is released without problems, but is this correct ?, won't I have a memory leak ?.

Thank you.

Marc Guillot
  • 5,367
  • 11
  • 30
  • Hi @Marc Guillot. I'll maybe see if I can reproduce this problem this evening. Before I do, what sort of size is your `Data` stream? I'm asking in case this is related to the ServerMethods stream-returning problem(s). – MartynA Mar 23 '17 at 15:32
  • Thanks a lot Martyn, this is not related to the problem on Datasnap with big streams. It happens always, even when there only is a single record (a few dozens of bytes). – Marc Guillot Mar 23 '17 at 15:36
  • Ok, I'll give it a whirl. CU later ... – MartynA Mar 23 '17 at 15:45
  • @J, the AV raises when I call Close, to release the Form (the FormClose event has an Action := caFree) – Marc Guillot Mar 23 '17 at 16:01
  • Thanks, I will take a look to the Stack. – Marc Guillot Mar 23 '17 at 16:42
  • Actually I think @J... may be right about an MCVE. I've been trying to set up a test project but I'm afraid it is not at all clear to me what you mean by "send that compressed data to the remote call (Datasnap)." – MartynA Mar 23 '17 at 16:50
  • @J... thanks for your suggestions, looking at the call stack, as you said, I have found the object that can't be freed. I have changed my code and now the Form is released without problems, but I'm not sure that I should really leave that Stream without freeing it ?. Won't I have a memory leak ?. I would appreciate if you can take a look at the PS that I have added to the Question. Thank you. – Marc Guillot Mar 23 '17 at 17:07
  • @MartynA thanks Martyn, with the suggestion to look at the Call Stack I have found the source of the problem (I have added a PS to the question). I'm just not sure that it's correct to not free that Stream, it looks like that it will generate memory leaks. – Marc Guillot Mar 23 '17 at 17:11
  • @MartynA, J... has just added an answer that the Parameter takes ownership of the TStream, so there won't be memory leaks. – Marc Guillot Mar 23 '17 at 17:14
  • 1
    @MarcGuillot If you're concerned about memory leaks (and even if you're not...) you can, and should, always use [ReportMemoryLeaksOnShutdown](http://docwiki.embarcadero.com/Libraries/en/System.ReportMemoryLeaksOnShutdown), at least in your debug builds. This is a windows-only feature, however. – J... Mar 23 '17 at 17:16
  • @J... I will, thank you. – Marc Guillot Mar 23 '17 at 17:17
  • 1
    @MarcGuillot: Yes I've seen. Funny that a similar point arises when a DS ServerMethod returns a stream - DS's code automatically frees it. – MartynA Mar 23 '17 at 18:09

1 Answers1

7

From the manual page :

Setting the AsStream property sets the DataType property to ftStream if it is not one of the character string / byte string / BLOB data types. The assigned TStream object will be owned by this TFDParam. To explicitly control the ownership, use SetStream method.

Emphasis mine. So yes, assigning the stream to the parameter gives the parameter ownership of that stream and it becomes responsible for freeing it when it is itself freed (which is done by the dataset when it is freed by the form which owns the dataset component).

When you free the stream here :

Stream := CompressDataset(qryFacturacion_Desglose);
try
  spActualizaDesglose.ParamByName('AStream').AsStream := Stream;
  spActualizaDesglose.ExecProc;
finally
  Stream.Free;
end;

you are destroying the object that the parameter is holding a reference to and it raises an AV when the parameter object tries to free it a second time.

J...
  • 28,957
  • 5
  • 61
  • 128