[Question] Nginx FastCGI and Multithreading lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- rb1147x · 2016-11-26 01:27 Hi all, I've been learning Free Pascal recently and I'm attempting to create a simple, multithreaded FastCGI program. I succeeded in initially creating a simple FastCGI app (not multithreaded), but realized that if I had multiple web requests going to my server, the second request would wait for the first request to finish. I'm using Nginx and after much Googling it seems like I need to create threads for the requests in my FastCGI program. Here is my code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1 ; {$mode objfpc}{$H+} interface uses  {$ifdef unix}cthreads,{$endif} SysUtils , Classes , httpdefs , fpHTTP , fpWeb, fphttpclient ; type   { TFPWebModule1 }   TFPWebModule1 = class( TFPWebModule )    procedure DataModuleRequest ( Sender : TObject ; ARequest : TRequest ;      AResponse : TResponse ; var Handled : Boolean );  private    { private declarations }  public    { public declarations }  end; type  TConnThread = class (TThread)    private      threadSender : TObject;      threadARequest : TRequest;      threadAResponse : TResponse;      threadHandled : Boolean;    protected      procedure Execute; override;    public      constructor Create(Sender : TObject ;      ARequest : TRequest ; AResponse : TResponse ; var Handled : Boolean);  end; var  FPWebModule1 : TFPWebModule1 ; implementation {$R *.lfm} { TFPWebModule1 } procedure TFPWebModule1 . DataModuleRequest ( Sender : TObject ;  ARequest : TRequest ; AResponse : TResponse ; var Handled : Boolean );var  //s : string;  //i : integer;  newThread : TConnThread;begin  writeln('We''re NOT in the thread!');  // start a new thread  newThread := TConnThread.Create(Sender, ARequest, AResponse, Handled);  newThread.Start;  //AResponse.Contents.Add('Test');  //for i := 0 to 20000 do  //    AResponse.Contents.Add(IntToStr(i));  Handled := true;end; constructor TConnThread.Create(Sender : TObject ;      ARequest : TRequest ; AResponse : TResponse ; var Handled : Boolean);begin  inherited Create(true);  FreeOnTerminate := true;  threadSender := Sender;  threadARequest := ARequest;  threadAResponse := AResponse;  threadHandled := Handled;end; procedure TConnThread.Execute;var  i : integer;begin     writeln('We''re in the thread!');     threadAResponse.ContentType := 'text/html;charset=utf-8';     threadAResponse.Contents.Add('Test');   for i := 0 to 20000 do         threadAResponse.Contents.Add(IntToStr(i));     threadHandled := true;end; initialization  RegisterHTTPModule ('TFPWebModule1', TFPWebModule1 );end.   I'm not entirely sure if this is the correct way to go about this, but in my mind it seems like that onRequest I should start a new thread. When the requests come in, nothing happens within the browser. In fact, using chrome dev tools it looks like the app completes/terminates, but I just see a blank screen. Meanwhile in my terminal, the console spits out both of my Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i writeln('We''re NOT in the thread!'); and Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i writeln('We''re in the thread!'); so at least I know SOMETHING is happening. My guess is that I may not be passing the Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i DataModuleRequest() arguments correctly? Any help is much appreciated, thanks! ---------------------------------------------------------------------- Leledumbo · 2016-11-26 06:13 Indeed the FastCGI backend currenty doesn't support multithreading. This is the reason why I use embedded HTTP server instead (it supports threading by a simple property value) and use nginx as reverse proxy only. Practically it's the same as if you use Nginx as the thread manager, only faster due to in-process threading. Doing it as you do now would be too late and is too localized. Would be better if you extend TCustomFCgiApplication overriding InitializeWebHandler to provide TFCgiHandler descendant overriding Run method of its TWebHandler parent. Currently, this is the same for all backends except embedded http server: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Procedure TWebHandler.Run;var ARequest : TRequest;    AResponse : TResponse;begin  while not FTerminated do    begin    if WaitForRequest(ARequest,AResponse) then      DoHandleRequest(ARequest,AResponse);    if assigned(OnIdle) then      OnIdle(Self);    end;end;You can change the call to DoHandleRequest to a separate thread. This way, each request will run in its own thread with minimum latency. ---------------------------------------------------------------------- rb1147x · 2016-11-26 15:26 Thanks for your response. I tried switching to using an HTTP server with the threading property set to True. However, I'm seeing the same issue when I try to make two requests. The 2nd waits until the 1st completes. Is there really nothing more that needs to be done besides setting the Thread property to True? Code below for context. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1 ; {$mode objfpc}{$H+} interface uses  SysUtils , Classes , httpdefs , fpHTTP , fpWeb ; type   { TFPWebModule1 }   TFPWebModule1 = class( TFPWebModule )    procedure DataModuleRequest ( Sender : TObject ; ARequest : TRequest ;      AResponse : TResponse ; var Handled : Boolean );  private    { private declarations }  public    { public declarations }  end; var  FPWebModule1 : TFPWebModule1 ; implementation {$R *.lfm} { TFPWebModule1 } procedure TFPWebModule1 . DataModuleRequest ( Sender : TObject ;  ARequest : TRequest ; AResponse : TResponse ; var Handled : Boolean );var  i : integer;  //s : string;begin     writeln('Starting loop...');     for i := 0 to 20000 do         AResponse.Contents.Add(IntToStr(i));     Handled := True;end; initialization  RegisterHTTPModule ('TFPWebModule1', TFPWebModule1 );end.   Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program httpproject1; {$mode objfpc}{$H+} uses  {$ifdef unix}cthreads,{$endif} fphttpapp , Unit1; begin  Application.Title:='httpproject1';  Application.Port:=6900;  Application.Threaded:=True;  Application.Initialize;  Application.Run;end. ---------------------------------------------------------------------- Leledumbo · 2016-11-26 19:29 Quote from: rb1147x on November 26, 2016, 04:26:09 pmHowever, I'm seeing the same issue when I try to make two requests. The 2nd waits until the 1st completes. Is there really nothing more that needs to be done besides setting the Thread property to True? Hmm... I just noticed right now that actually requests get served slower with threads, around 3.5 times. The funny thing is, the non- threaded version auto scales with my physical processor cores (I have Core i7-5500u), but the threaded one doesn't look so, counting the request per second. However, CPU usage shows that the threaded one really uses threads and all the CPU cores are indeed busy when benchmark is running. I guess we need Michael van Canneyt to analyze and answer. I use a much simpler, purely handcoded test program though: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode objfpc}{$H+} uses  {$ifdef unix}Cthreads,{$endif} Classes, HTTPDefs, fpHTTP, fpWeb, fpHTTPApp; type   { THelloWorldModule }   THelloWorldModule = class(TFPWebModule)    constructor CreateNew(AOwner: TComponent; Crea]"]>Blockedde: Integer); override;  procedure Request(Sender: TObject; ARequest: TRequest;      AResponse: TResponse; var Handled: Boolean);  end; constructor THelloWorldModule.CreateNew(AOwner: TComponent; Crea]"]>Blockedde: Integer);begin  inherited CreateNew(AOwner,Crea]"]>Blockedde);  OnRequest := @Request;end; procedure THelloWorldModule.Request(Sender: TObject; ARequest: TRequest;  AResponse: TResponse; var Handled: Boolean);begin  AResponse.Content := 'Hello, World!';  Handled := true;end; begin  THelloWorldModule.RegisterModule('',true);  Application.Port := 8080;  Application.Threaded := true;  Application.Initialize;  Application.Run;end. I use ab to benchmark: ab -n 100000 -c http://127.0.0.1:8080/ is tested with 1, 2 and 4 ---------------------------------------------------------------------- rb1147x · 2016-11-26 22:19 Thanks for the help with this. QuoteI guess we need Michael van Canneyt to analyze and answer. How do we get Michael's attention? ---------------------------------------------------------------------- howardpc · 2016-11-26 22:59 Start a thread in the fpc-Pascal mailing list. AFAIK Michael does not visit this forum. ---------------------------------------------------------------------- Phil · 2016-11-26 23:24 Quote from: rb1147x on November 26, 2016, 11:19:47 pmThanks for the help with this. Have you tried the FCGIApp unit from the old ExtPascal project? Source and example server apps here: https://macpgmr.github.io/MacXPlatform/WebAppOverview.html#Server https://macpgmr.github.io/MacXPlatform/WebAppOverview_2.html#PascalSer ver