Ir al contenido


Foto

Las llamadas Datasnap me dejan el Cliente inestable


  • Por favor identifícate para responder
8 respuestas en este tema

#1 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 25 mayo 2016 - 03:09

Hola amigos,
 
Estoy programando una aplicación nueva en Datasnap : Delphi 10.1 Berlin, SQL Server, Conexión TCP (no son conexiones REST), el Cliente es también Delphi.
 
Tengo dos tipos de llamada, las que devuelven un Dataset, y las que hacen acciones y por tanto no devuelven información en forma de Dataset. Las primeras funcionan perfectamente, puedes llamar tantas como quieres sin que falle la aplicación, pero las segundas vuelven la aplicación inestable (la primera llamada siempre funciona, pero a partir de allí empiezas a tener AV).
 
Por ejemplo, en esta llamada a guardar las modificaciones, la primera ejecución funciona bien, pero la segunda ejecución hace bien la llamada al Datasnap y guarda las nuevas modificaciones que se hayan hecho entre las dos llamadas, pero salta un AV en el qryDatos.CancelUpdates. También empiezas a tener AV al cerrar formularios, la aplicación, ...

delphi
  1. function TfrmBaseGestion.iGuardar: boolean;
  2. var Stream: TMemoryStream;
  3. MemTable: TFDMemTable;
  4. begin
  5. Result := True;
  6. if qryDatos.ChangeCount > 0 then begin
  7. Result := False;
  8. Stream := TMemoryStream.Create;
  9. MemTable := TFDMemTable.Create(Self);
  10.  
  11. try
  12. MemTable.Data := qryDatos.Delta;
  13. MemTable.SaveToStream(Stream);
  14. Stream.Position := 0;
  15.  
  16. MethodsGestionesClient.PostDatos(Stream);
  17.  
  18. AfterModificar;
  19. Result := True;
  20. finally
  21. MemTable.Free;
  22. Stream.Free;
  23. end;
  24. qryDatos.CancelUpdates;
  25. iAbrir;
  26. end;
  27. end;

He probado tanto a hacer las llamadas mediante un procedimiento almacenado FireDAC como mediante una clase proxy (a través de una conexión dbExpress) y los problemas son muy similares.
 
¿ Habéis tenido algún problema similar de que las llamadas Datasnap os desestabilicen la aplicación ?. La verdad es que ya no se me ocurre como enfocar el problema.
 
Gracias.
  • 0

#2 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 25 mayo 2016 - 09:31

Hola, si puedes decirnos como realizas el llamado a las funciones de DataSnap, allí también puede ser el problema.   O El ciclo del módulo.

 

Lo otro que no puedo determinar es si el llamado MethodsGestionesClient.PostDatos(Stream), se comparte entre todos los clientes y requiere sincronización entre las tareas.


  • 0

#3 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 26 mayo 2016 - 05:39

Hola, gracias por responder  :)

 

He probado las dos formas posibles de llamar a métodos remotos Datasnap : mediante un procedimiento almancenado FireDAC y mediante una clase proxy.

 

Ya me funciona bien, ha quedado de la siguiente forma (al final he escogido el procedimiento almacenado, ya que me ahorro ir actualizando la clase proxy) :


php
  1. function TfrmBaseGestion.iGuardar: boolean;
  2. var Stream: TMemoryStream;
  3. MemTable: TFDMemTable;
  4. begin
  5. Result := True;
  6. if HayCambiosPendientes then begin
  7. Result := False;
  8. Stream := TMemoryStream.Create;
  9. MemTable := TFDMemTable.Create(Self);
  10.  
  11. try
  12. MemTable.Data := qryDatos.Delta;
  13. MemTable.SaveToStream(Stream);
  14. Stream.Position := 0;
  15. dmBaseData.spGestiones_Guardar.ParamByName('Query').Value := Query;
  16. dmBaseData.spGestiones_Guardar.ParamByName('Delta').AsStream := Stream;
  17. dmBaseData.spGestiones_Guardar.ExecProc;
  18. AfterModificar;
  19. Result := True;
  20. finally
  21. MemTable.Free;
  22. //Stream.Free;
  23. end;
  24. iAbrir;
  25. end;

La línea comentada, el Stream.Free, es la que provocaba el error.

 

Pensaba que los Streams se tenían que liberar, pero parece ser que no siempre es así. No se si esa memoria la debe estar liberando la misma llamada, una vez terminada.

 

Saludos.


  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 26 mayo 2016 - 11:56

Todo Stream, como incluso cualquier objeto que uno cree, se tiene que liberar Marc. Aún cuando la variable sea local a un procedimiento. La regla es: si YO lo creo, YO lo libero (a menos que específicamente el objeto a construir pertenezca a la rama TComponent, y le hayamos designado un padre que se encargue de su liberación).

Cuando el procedimiento finaliza se liberan los recursos y memoria que éste haya usado, pero esto aplica a variables de tipos simples y no a las variables de una clase. Asi que tanto a ese MemTable como al Stream los tienes que liberar.

 

Si dices que primero anda y luego hay CV es que estás intentando acceder a una memoria sucia. Y con más razón, si es que a ese método iGuardar se ejecuta varias veces, ¡Utiliza FreeAndNil!

 

Saludos,


  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 26 mayo 2016 - 12:20

Ummm.

Ahora que lo pienso, no debiera de haber problemas incluso al usar el Free en lugar del FreeAndNil.

 

Si tienes asegurado y detectado que el problema está en el Stream, quizá debieras analizar otra forma de trabajarlo. Yo ya me hice costumbre que al trabajar con Stream emplear un doble try, ya que hay Stream que en su creacíón puede arrojar una excepción. Para tu caso sería algo así:


delphi
  1. try
  2. Stream := TMemoryStream.Create;
  3. try
  4. // aquí todo lo que haya que hacer con el TStream
  5. finally
  6. Stream.Free;
  7. except
  8. // aquí capturamos las excepciones que nos interesa
  9. end;

Y no he tenido problemas incluso volviendo a invocar el método varias veces.

 

El asunto es que el AV ya de pleno dice que hay una memoria sucia por algún lado. Yo que tu no descartaría que tanto el Stream como el MemTable u otra variable sea la problemática. Noto que al crear el MemTable le asignas un self. ¿Quien es este Self? ¿El padre? Porque si es el padre y tu le estás liberando el hijo, es motivo de problemas... ¿No será que el error está en realidad en el MemTable? ;)

 

Pero tampoco no me deja tranquilo ese AfterModificar. ¿Que hace? Ha... por cierto... ¿De donde sale la variable Query?

 

Saludos,


  • 0

#6 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 27 mayo 2016 - 12:19

Hola Delphius, gracias por mirarlo.

 

Este formulario es una ficha base, a partir de la cual heredo visualmente las pantallas de mi aplicación (así no tengo que lidiar continuamente con el Datasnap, ya lo hace esta ficha base). Por tanto el self es el propio formulario (por lo tanto cada MemTable se liberaría al menos al cerrar su formulario). La llamada AfterModificar es un pseudo-evento que he creado para esa ficha base, solo existe para que los descendientes lo puedan sobre-escribir y hacer acciones adicionales cada vez que se guarda un registro. Pero en principio esa llamada esta vacía, habitualmente no hace nada.

 

Siempre había liberado los Stream (por eso me costó tanto aislar este problema, lo último que esperaba es que se provocase allí). La llamada al procedimiento almacenado dmBaseData.spGestiones_Guardar.ExecProc; no es una llamada usual, en realidad es una llamada al método remoto en el servidor de aplicaciones. Sospecho que esa llamada está liberando ya la memoría del Stream, y que por tanto mi Stream.Free en realidad estaba liberando memoría utilizada por otros componentes, corrompiéndolos, y por tanto provocando esas AV en los sitios más insospechados (como por ejemplo al hacer un qryDatos.CancelUpdates).

 

Después de todo en el ejemplo de referencia de Embarcadero (Samples\Object Pascal\Datasnap\FireDAC) no liberan el Stream :


delphi
  1. procedure TClientForm.PostTables;
  2. var
  3. LMemStream: TMemoryStream;
  4. I: integer;
  5. LDataSet: TDataSet;
  6. begin
  7. for I := 0 to DataModuleFDClient.FDSchemaAdapter.Count - 1 do
  8. begin
  9. LDataSet := DataModuleFDClient.FDSchemaAdapter.DataSets[I];
  10. if LDataSet <> nil then
  11. if LDataSet.State in dsEditModes then
  12. LDataSet.Post;
  13. end;
  14.  
  15. LMemStream := TMemoryStream.Create;
  16. try
  17. DataModuleFDClient.FDSchemaAdapter.ResourceOptions.StoreItems := [siDelta, siMeta];
  18. DataModuleFDClient.FDSchemaAdapter.SaveToStream(LMemStream, TFDStorageFormat.sfBinary);
  19. LMemStream.Position := 0;
  20. FDStoredProcPost.Params[0].asStream:= LMemStream;
  21. FDStoredProcPost.ExecProc;
  22. except
  23. On E: Exception do
  24. raise Exception.Create(E.Message);
  25. end;
  26. end;


  • 0

#7 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 27 mayo 2016 - 07:23

Y si en lugar de hacer una asignación del stream lo lees,   dmBaseData.spGestiones_Guardar.ParamByName('Delta').AsStream.LoadFromStream(Stream);

 

La verdad no tengo a mano ahora el Delphi para probarlo, pero es una opción.  

 

Saludos.


  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 27 mayo 2016 - 09:02

Yo también he llegado a pensar que en algún otro lado, algo te estuviera liberando el stream.

Es más es lo primero que pensé, pero descarté esa posibilidad ya que a mi no se me hace algo de esperarse. Que un tercero se meta en la liberación de otro que no viene al caso es algo no muy habitual. De hacerlo, debería estar perfectamente documentado eso.

Quizá eso ocurre en el ExecProc como dices; al menos es el lugar más obvio en donde se va a mirar primero.

 

Si efectivamente se soluciona con comentar el Steam.Free y como sugiere el ejemplo de la wiki de Embarcadero que no hay que hacer Free entonces quizá si hay alguien que se encarga del "trabajo sucio". E insisto, si es así: ¡Tiene que estar documentado! Sino va a ser motivos de problemas para muchos desarrolladores... motivo para armar un buen foro bardo :o

 

Respecto al self y que dices que es formulario, ten presente que cerrar el form no necesariamente implica liberarlo. ¡Es ocultarlo! A menos que especificamente establescas el valor caFree a la variable CloseAction del OnClose.

 

Como yo no uso Delphi no puedo replicar el problema. No te sabría decir más.

 

Saludos,


  • 0

#9 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 27 mayo 2016 - 12:04

Si no me equivoco cuando instancias la clase proxy, se indica como parametro un boolean "InstanceOwner", indicando con True que queres que todo el manejo de memoria es "automatico"

 

Siendo asi, tiene sentido lo que dice Gustavo, yo obtendria una copia del Stream recibido y me la guardaria en otro Stream local que yo quiera manejar


  • 0




IP.Board spam blocked by CleanTalk.