RTC Forums
May 05, 2024, 08:35:12 AM *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
   Home   Help Login Register  
Pages: [1]
  Print  
Author Topic: More threading pain :-(  (Read 5485 times)
jonb2
Newbie
*
Posts: 36


« on: February 18, 2012, 04:51:09 PM »

Sorry D

I am still having a problem with threading sequences, this is with threading/winhttp enabled

====================================== in simple form

1) Button one - request a
2) Button two - request b

then

procedure TForm1.RtcDataRequest1BeginRequest(Sender: TRtcConnection);
begin
  TRtcDataClient(Sender).WriteHeader;
end;

procedure TForm1.RtcDataRequest1DataReceived(Sender: TRtcConnection);

begin
 if TRtcDataClient(Sender).Response.Started then
  begin
  form1.Memo1.Clear;
  end;

if TRtcDataClient(Sender).Response.Done then
  begin
  if APIno = 1 then
  SemRushGet(Utf8Decode(TRtcDataClient(Sender).Read));
  if APIno = 2 then
  AHRRushGet(Utf8Decode(TRtcDataClient(Sender).Read));
 /// form1.RtcDataRequest1.Client.Disconnect; <<< this made no difference
  end;

 ================================================ also, I took some timings with AQTime


Routine Name      Time         Time with Children   Shared Time      Hit Count
ReadNextBlock      9710.77202461051   9710.80637694532   99.9996462463212   6

Routine Name               Time         Time with Children   Shared Time      Hit Count
TRtcWinHttpClientProvider::SendHeaderOut   2447.71786905785   2447.75339137      99.9985487789631   2

Routine Name               Time         Time with Children   Shared Time      Hit Count
TRtcWinHttpClientProvider::OpenConnection   452.393824512649   549.51867778699   82.3254682324029   2

Routine Name      Time         Time with Children   Shared Time      Hit Count
Rtctimer::WaitForClose   100.875167653254   104.74119412485   96.308972316099   1

Routine Name      Time         Time with Children   Shared Time      Hit Count
Rtcthrpool::WaitForClose   99.8156601695565   105.612489919745   94.5112270768413   1

===================================================================

It seems that sending the header out takes quite a bit of time. Not sure why.

Without threading, it's fine. If threading is enabled, then the request does not reset (still active) and the data remains from the previous request.

It seems that I am missing something.

Any ideas?

Kind regards

Jon
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #1 on: February 18, 2012, 05:00:06 PM »

I'm sorry, but I don't understand what you mean. Can you please explain the problem you are having in simple english and using more words? Posting a complete source code example would also help understand what you are doing. If you can't post the complete source code example here, send it to me by E-Mail:


Best Regards,
Danijel Tkalcec
Logged
jonb2
Newbie
*
Posts: 36


« Reply #2 on: February 18, 2012, 05:07:22 PM »

Here's the source of the APIs getting section: The problem is that when I press Button 2 followed by button 1. The data from 2 is still in the buffer, as the threading is not done to call a new request.
========================

procedure SemRushGet(cap2: string);
var
strlist1: TStringList;
valu, trd, trd2: string;
i, i2, i3, adda, tcol: integer;  //rows, cols,
trdata, trdata1, trdata2, trdata3: single;
valucol: string;
CsvParse1 : TDICsvParser;
rows, cols : Cardinal;
CsvParse2: TDICsvParser;

begin

// Form1.Memo1.Lines.Add(cap2);
     strlist1:=TStringlist.Create;

     CsvParse2 := TDICsvParser.Create(nil);
     CsvParse1 := TDICsvParser.Create(nil);
     CsvParse1.ReadMethods := Read_UTF_8;

     CsvParse1.SetSourceBufferAsStrA(cap2,'');

     CsvParse1.ParseDimensions(Cols, Rows);
     CsvParse1.Separator:=';';

     CsvParse1.ResetDataLocation;
     CsvParse1.RewindSource;

    // while  do
    //for i  := 0 to rows-1 do
   //  for i2 := 0 to cols-1 do
      while CsvParse1.ReadNextData do
      begin

      strlist1.Add(CsvParse1.DataAsStr);

      //  FCsvParser.DataCol + i, FCsvParser.DataRow + i2
        //FCsvParser.pos
        { Filtering: If the first column (index = 0) matches a string,
          skip the rest of that data line. This can be used to increase
          pasing performance if certain data is not used by the application. }
//        case FCsvParser.DataCol of
//          0:
//            if Match and FCsvParser.DataIsStrIW(MatchText) then
//              begin
//                FCsvParser.SkipDataLine;
//              end;
//        end;
      end;


 
     
      strlist1.EndUpdate;
      Form1.Memo1.Lines.Add(strlist1.text);

      CsvParse1.Free;
      CsvParse2.Free;
     // kkk.Free;

      strlist1.Free;
     //  END SEMRUSH  //////////////

 end;

procedure TForm1.btnAhrGetClick(Sender: TObject);
//var
//urlget: string;
begin

    APIno:=2;

    //urlget:=urld2.Text;
    //form1.RtcHttpClient1.ServerPort:='80';
    form1.RtcHttpClient1.ServerAddr:=urld2.text;


    form1.RtcDataRequest1.Request.Host:=urld2.Text;
    //urltext2.SelectAll;
    form1.RtcDataRequest1.Request.Query.Text:=URLText2.Text;
    form1.RtcDataRequest1.Request.Method:='GET';
    Form1.RtcDataRequest1.Request.FileName:='/get_backlinks.php';

    form1.RtcDataRequest1.Post;

end;

procedure TForm1.btnSemGetClick(Sender: TObject);
var
urlget: string;
begin
    APIno:=1;

    form1.RtcHttpClient1.ServerAddr:=urld.text;


    form1.RtcDataRequest1.Request.Host:=urld.Text;

    form1.RtcDataRequest1.Request.Query.Text:=URLText.Text;
    form1.RtcDataRequest1.Request.Method:='GET';

    form1.RtcDataRequest1.Post;

end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//
    Action := caFree;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
//
end;

procedure TForm1.RtcDataRequest1BeginRequest(Sender: TRtcConnection);
begin
  // send request header out
  TRtcDataClient(Sender).WriteHeader;
end;

procedure TForm1.RtcDataRequest1DataReceived(Sender: TRtcConnection);

begin
 if TRtcDataClient(Sender).Response.Started then
  begin
  form1.Memo1.Clear;
  end;

if TRtcDataClient(Sender).Response.Done then
  begin
    { Executed only once per request,
      when we have just received it all. }
 
 /// Get response data
  if APIno = 1 then
  SemRushGet(Utf8Decode(TRtcDataClient(Sender).Read));

  if APIno = 2 then
  AHRefGet(Utf8Decode(TRtcDataClient(Sender).Read));


  //TRtcDataClient(Sender).Flush;
  form1.RtcDataRequest1.Client.Disconnect;
  end;

end;
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #3 on: February 18, 2012, 05:43:30 PM »

1) You are changing the Server address before you post a new request, without disconnecting from the Server first. You can't send requests to different Servers or different Ports at the same time by using a single connection component. If you need to work with multiple Servers, you should either use a separate set of components for each Server, or wait for all the pending requests to complete and disconnect from the Server before you start preparing the components to communicating with a different Server. For example, by using "RtcHttpClient1.DisconnecNow(False);"

2) You are using a global variable as an indicator which request was sent, instead of storing request-specific information inside the "Request.Info" property. This can only work in single-threaded blocking mode, but NOT in asynchronous event-driven, or multithreaded execution, because your 2nd call (pressing the 2nd button) changes your global variable (APIno), so the OnDataReceived event will be calling the wrong method.

3) Unless there is a very good reason for doing so (I can't think of any on the Client), you should NOT use "Disconnect" from a RTC event. By using Disconnect, you are not only closing the connection to the Server (which could normally be used for multiple requests, improving overall performance). You are also forcing the low-level connetion provider objects to be released from memory, which forces the component to create new OS handles, slows everything down and - if done often in a short time period - can result in all port numbers to get used up, after which you won't be able to open up a single new connection.

Best Regards,
Danijel Tkalcec
Logged
jonb2
Newbie
*
Posts: 36


« Reply #4 on: February 18, 2012, 06:08:04 PM »

Thank you D.

My thoughts:

1) I could have up to 10 to 20 API interfaces eventually, so having mulitple components will all their associated events could be a nightmare. I know I am bending your client to a purpose for which it was not designed, but it is very high performance, which is why i want to use it. There are not many options out there. How do I check that the request is complete in a threading situation and where then should I put the "RtcHttpClient1.DisconnecNow(False);" method ?

2) Yes, it's sloppy. But I envisage it each request being called from multiple places on the GUI, depending on the workflow position. It would be good if there was a 'request info' list repository component as an intermediary. An automatic weapon approach.

3) Point taken D. I was trying, in the depths of my ignorance, to find out what was going on. you have made this clearer to me.

Do you think with all the problems I might have for this instance of usage (non RTC), I would be better off looking for a more linear event answer and possibly forget threading altogether ?

Kind regards

Jon
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #5 on: February 18, 2012, 06:31:47 PM »

1.a) If you plan on having multiple different APIs, I think you should have each API entirely separated from the rest (use different data modules). IMO, adding all the APIs into a single unit/DataModule, or making all your APIs use the same components even though they are working with different Servers, then dinamically changing their properties, is something that will result in chaos.

1.b) As I've stated in my point 3, performance is not your only problem when connecting and disconnecting from Servers for every single request. If you do this, you will eventually end up with a non-working Client, because your app will use up all available port numbers. This is why strongly suggest that you do NOT use a single connection component to talk to multiple Servers. If your Client is really working with multiple Servers at the same time, you should create a separate set of components for each Server.

2) When preparing a request for posting, you are already using the "Request" property. Now you just need to use the "Request.Info" property to store any additional information required for request processing. The property is already there.

If you are just looking for a quick-and-dirty solution, then I suggest setting "MultiThreaded" to "FALSE" and calling "DisconnectNow(False);" after each call to "Post". Provided you don't create your own threads, with "MultiThreaded=FALSE" and using "DisconnectNow(False)", you can continue using global variables and change the Server/Port before each request. Naturally, with the side-effect of slower performance and potential risk of using up all the TCP/IP ports. I don't know how many requests your clients will be sending, so I can't tell you how likely it is that you will get into the "no more available port numbers" problem.

Threading would only make sense if your code was making use of the event-driven asynchronous nature of the RTC SDK. But if you just want to open a connection, send a request, get a response and close connection, then setting "MultiThreaded=FALSE" and "Blocking=TRUE" (to use blocking communication) is more appropriate. Both options are available when using the RTC SDK, but each requires a differnt approach by the developer.

Best Regards,
Danijel Tkalcec
Logged
jonb2
Newbie
*
Posts: 36


« Reply #6 on: February 18, 2012, 06:42:22 PM »

I think I will take your advice D. Thanks for the explanation and warning. One datamodule per API.

One more question to help me understand, as these are HTTP requests going to standard HTTP API servers. How can the client go beyond port 80 usage ? Does Windows allocate other ports if one is in use ?

J
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #7 on: February 18, 2012, 06:49:53 PM »

The OS will allocate a new port number for each new open socket/connection on the Client. These port numbers usually start with 1024 and go up to 65000. This might sound like a big number of available Ports, but (A) some Port numbers are reserved and (B) even after you close a connection, the port number will remain reserved for a few minutes, to ensure that data from the old connections won't end up in any new connection (where it doesn't belong). This is why you should reduce the number of connect/disconnect operations, if your client has to make a lot of fast/short requests.

Best Regards,
Danijel Tkalcec
Logged
Kevin Powick
RTC Expired
*
Posts: 87


« Reply #8 on: March 01, 2012, 01:52:18 AM »

A bit late, but just to add/clarify what Danijel has said about client ports.

The port 80 you (jonb2) refer to is the port which HTTP servers are listening for requests, not the port on which the client makes the request.  When your client makes a request to a server's port 80, it uses a dynamically allocated port number to do so.  As Danijel said, usually starting at 1024.

To see this in action, open a command prompt on your windows box and type in the command netstat -n.  You'll be presented with a list of currently active TCP connections.  Open a browser and navigate to a website, then type in netstat -n again.  In the Foreign Address column, you should see at least one IP address ending with :80.  That's your connection to the website you just established.  On that same line of the listing, look at the Local Address column and note your client's port number :nnnnn.

The point Danijel makes about running out of ports is that even after you disconnect from the remote server, Windows will keep that client-side port allocated for some time before returning it to the pool of available ports.  So, if you're constantly connecting and disconnecting from multiple servers, you could easily run out of available ports.

--
Kevin Powick

Logged

Linux is only free if your time is worthless
jonb2
Newbie
*
Posts: 36


« Reply #9 on: March 01, 2012, 01:53:37 PM »

Thanks Kevin ...

Kind of you to add to the clarification with an illustration. Comforting to know that this forum is populated by sages :-)




J.
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.03 seconds with 17 queries.