RTC Forums
May 14, 2024, 05:04:09 PM *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
   Home   Help Login Register  
Pages: [1]
  Print  
Author Topic: Stack overflow: RtcMessageClient + RtcDataProvider + huge file download  (Read 5285 times)
Max Terentiev
RTC License
***
Posts: 49


« on: January 24, 2017, 09:01:39 PM »

Hi Danijel,

I have RtcMessageServer + RtcDataProvider on server side and RtcMessageClient + RtcDataRequest on client side.

On server side I send file to client this way:

Code:
procedure TEngineDM.FileDownloadProviderDataReceived(Sender: TRtcConnection);
var
  FileName:string;
  Len:Cardinal;
begin
with TRtcDataServer(Sender) do
  begin
  if Request.Complete then
    begin
    FileName:=Request.Info['DownloadFile'];
    if not FileExists(FileName) then
      begin
      Disconnect;
      exit;
      end;
    if Response.ContentLength>Response.ContentOut then
      begin
      Len:=Response.ContentLength-Response.ContentOut;
      if Len>65535 then
        Len:=65535;
      Write(Read_File(FileName,Response.ContentOut,Len));
      end;
    end;
  end;
end;

On Client side I receive data this way (client side is C++Builder so, it's C++):

Code:
void __fastcall TDefProgressFrm::DownloadFileRequestDataReceived(TRtcConnection *Sender)
{
Rtcinfo::RtcString FileData;
TRtcDataClient *DataClient;
DataClient=(TRtcDataClient *)Sender;
if(DataClient->Response->StatusCode!=200)
{
DataClient->Response->Reject();
return;
}
if(DataClient->Response->Started)
{
if(FileExists(SaveFileName))
DeleteFile(SaveFileName);
}
FileData=DataClient->Read();
Write_File(SaveFileName,FileData,DataClient->Response->ContentIn-FileData.Length());
if(DataClient->Response->Done)
{
ProgressBar->Position=100;
        CancelBtn->Caption="OK";
}
else
ProgressBar->Position=int((DataClient->Response->ContentIn/DataClient->Response->ContentLength)*100);
ProgressBar->Properties->Text=ClientDM->MultiLang->Translate("Downloading file from server")+" ("+IntToStr((int)ProgressBar->Position) +" %)";
Update();
}

It's works on small files but fail on large files with Stack owerflow error.

After debugging I notice that FileDownloadProviderDataReceived called many times until entire file is loaded to memory ! After it DownloadFileRequestDataReceived called many times until file saved to disk !

So it's should works this way FileDownloadProviderDataReceived -> DownloadFileRequestDataReceived and repeat
But works this way DownloadFileRequestDataReceived (many times) -> DownloadFileRequestDataReceived (many times)
I file size is large than 500 mb I got Stack overflow. And also entire file is loaded to memory !

Maybe RtcMessageClient/Server not supports partial file transfer ? Or I miss something ?

p.s. RtcMessageClient.MultiThreaded=true, RtcMessageServer.MultiThreaded=true, RtcDataRequest.AutoSyncEvents=true

Thanks for help !
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #1 on: January 24, 2017, 09:59:12 PM »

RtcMessageClient and RtcMessageServer components are designed for message-based protocols.

RtcMessageClient uses a MemoryStream internally to buffer the entire request content, which is then passed on to the Server component for processing (using a single method call), where the entire response content is prepared in a separate MemoryStream and returned back to the Client for response processing.

The main reason these components exist, is to simplify using 3rd-party transports for making remote function calls by utilizing the same communication channels as the 3rd-party product, regardless of the protocol used by the 3rd party - as long as it was message-based and allowed a simple request/response scheme.

In a nutshell, RtcMessageClient and RtcMessageServer components were not designed for sending large files in a single request or response. If you have to use these components, you should send your files in several request/response cycles to avoid high memory usage or stack overflows.

Best Regards,
Danijel Tkalcec
Logged
Max Terentiev
RTC License
***
Posts: 49


« Reply #2 on: January 25, 2017, 02:03:48 PM »

Ok, understand, I changed file download implementation to HTTP RANGEs and stack owerflow problem gone.

But I notice another strange problem: if I want to abort client downloading (in case of file problems) I got exception:
"Error! A complete response has to be sent from ProcessMessage!". I abort connection this way:

Code:
procedure TEngineDM.FileDownloadProviderDataReceived(Sender: TRtcConnection);
var
  FileName:string;
begin
with TRtcDataServer(Sender) do
  begin
  if Request.Complete then
    begin
    FileName:=Request.Info['DownloadFile'];
    if not FileExists(FileName) then // abort downloading if file has been deleted
        begin
        Disconnect; // Error! A complete response has to be sent from ProcessMessage!
        Exit;
        end;
    end;
  end;
end;

Right after Disconnect I got exception and RtcMessageServer hangs and stop process any requests.
Maybe Disconnect is not supported by RtcMessageServer ? If yes - how to gracefully disconnect/abort client (or at least prevent RtcMessageServer from hang) ?
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #3 on: January 25, 2017, 02:55:36 PM »

If you look at the "IRTCMessageReceiver" interface declaration, which the TRtcMessageServer component implements and TRtcMessageClient component requires to work, you will see it ONLY has one method declared:

    { ProcessMessage need to be called *only* once per request, with a complete request data
      waiting in the "aRequest" stream and an empty "aReply" stream to receive the response.
      Request processing is done in a "blocking mode", which means that the caller will
      have the complete response filled in the "aReply" stream, before the call returns. }
    procedure ProcessMessage(aRequest, aReply: TStream);

When you are working with RtcMessageClient and RtcMessageServer components, the call to Connect does absolutely nothing, while a call to Disconnect breaks any future event calls for the request/response. To make possible bugs in user code visible, an exeption will be raised by the TRtcMessageServer component in case response preparation code is interrupted (for example - if you call Disconnect). If you do NOT want this exception to be raised by the TRtcMessageServer component, you can manually set ...

  TRtcDataServer(Sender).Response.Sent:=TRUE;

... before calling "Disconnect", because this is the property being used to check if the complete response has been prepared in the "ProcessMessage" method. But make sure you don't make any calls to Write or WriteEx after that, because that would raise exceptions in a few other places, which exist to ensure you don't try to send out more data than expected.

Best Regards,
Danijel Tkalcec
Logged
Max Terentiev
RTC License
***
Posts: 49


« Reply #4 on: January 25, 2017, 03:30:30 PM »

Using TRtcDataServer(Sender).Response.Sent:=TRUE; I can now drop client connection on server with no exception.

But it's not eleminate some problems on client-side:

1. No events triggered like OnConnectError, OnConnectFail, OnConnectLost, OnResponseAbort, etc. So, how to detect connection drop on client side ?

2. If I call RtcDataRequest.CancelRequsets manually or using timer - RtcClientModule (linked to same RtcDataClient) no longer works, all future calls to Execute failed with 'Connection timeout' exception Sad
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #5 on: January 25, 2017, 05:03:03 PM »

TRtcMessageClient and TRtcMessageServer components do NOT have a concept of a "connection". These components work by using a permanent link between a TRtcMessageClient and any instance implementing the IRtcMessageReceiver interface (TRtcMessageServer - in your case), which - in a normal case - does NOT break. And that is why you were getting the exception after calling "Disconnect" on the Server. Since there is no concept of a "connection", there is no information exchanged between the Client and the Server when you call "Disconnect" on one side. If you want a "connecition" to be established between your Client and your Server, so you can use "disconnect" to break communication and get notified about it on the other side, use TRtcHttpClient and TRtcHttpServer components instead or TRtcMessageClient and TRtcMessageServer.

Best Regards,
Danijel Tkalcec
Logged
Max Terentiev
RTC License
***
Posts: 49


« Reply #6 on: January 25, 2017, 07:11:27 PM »

Thank you very much for your help !

Unfortuneately I can't switch to RtcHttp* components because my app must works in two editions: standalone and appserver. So, I use RtcMessage* components for standalone version and RtcHttp* for appserver. And try to maintain same code base (as same as possible).

Finally I implement this soulition for huge file download:

1. RtcHttp* version just download huge file like in demos shipped with RTC.

2. RtcMessage* version uses HTTP RANGE header and download file using many requests (2 mb per request). In case of server errors (like deleted downloading file between download requests) I just send back to client string filled with EOT 0x04 characters. On client I check received string for 0x04 characters and if found - cancel saving to file and call ((TRtcDataClient *)Sender)-Response->Reject(); - this allow me to gracefully cancel downloading, fire TRtcDataRequest.OnResponseReject event and show error like "problems on server" to user. Don't know is it most effective method but at least it's works for me.

Again, thank you very much for your fantastic support !  Smiley
Logged
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.21 | SMF © 2015, Simple Machines Valid XHTML 1.0! Valid CSS!
Page created in 0.026 seconds with 17 queries.