#1 2018-03-07 11:04:49

DDGG
Member
Registered: 2018-01-10
Posts: 18

Does THttpApiServer is single-threaded?

I'm fairly new to mORMot, so please bear with me if my question seems stupid.

I'm using THttpApiServer to build an API server, and I found out that if I make a response for a long time (e.g. Sleep(3000) in the OnProcess callback), the follow-up requests will blocking.
So does THttpApiServer is a single-threaded server? If it is, what should I do if I need to handle multiple requests in a moment?

I'm not good at English, sorry for that...
Thanks in advance!

Offline

#2 2018-03-07 11:59:23

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,655
Website

Re: Does THttpApiServer is single-threaded?

No, it uses a thread pool, of 32 threads by default.

I guess the blocking is on the client side.

Offline

#3 2018-03-08 02:51:22

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

Thanks for your reply!

I wrote a simple console application to test it.

program TestHttpApiServer;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, SynCommons, SynCrtSock, SynLog;

type
  TMyServer = class
    constructor Create;
    destructor Destroy; override;
  private
    fServer: THttpApiServer;
    function Process(Ctxt: THttpServerRequest): Cardinal;
  public
    procedure Start;
  end;

var
  ILog: ISynLog;
  Server: TMyServer;

{ TMyServer }

constructor TMyServer.Create;
begin
  fServer := THttpApiServer.Create(True);
  fServer.OnRequest := Process;
  fServer.AddUrl('root', '8080', False, '+', True);
end;

destructor TMyServer.Destroy;
begin
  fServer.Free;
  inherited;
end;

function TMyServer.Process(Ctxt: THttpServerRequest): Cardinal;
begin
  ILog.Log(sllDebug, '% : % %', [NowUTCToString, Ctxt.Method, Ctxt.URL]);

  Sleep(3000);

  ILog.Log(sllDebug, '% : % % => %', [NowUTCToString, Ctxt.Method, Ctxt.URL, 'OK']);
  Ctxt.OutContent := 'OK';
  Result := 200;
end;

procedure TMyServer.Start;
begin
  fServer.Start;
  ILog.Log(sllInfo, ': server start!');
end;

begin
  with TSynLog.Family do
  begin
    Level := LOG_VERBOSE;
    EchoToConsole := LOG_VERBOSE;
    PerThreadLog := ptOneFilePerThread;
  end;
  ILog := TSynLog.Add;

  Server := TMyServer.Create;
  Server.Start;

  Readln;
end.

Could you please give me some advice?

Offline

#4 2018-03-08 03:01:48

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

ab wrote:

No, it uses a thread pool, of 32 threads by default.

I guess the blocking is on the client side.


As the simple program above, I use a test-tool named ab (ApacheBench) to test it, the option -c5 means to use five requests to make at a time.

C:\Users\user>ab -n10 -c5 http://127.0.0.1:8080/root
This is ApacheBench, Version 2.3 <$Revision: 1663405 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        mORMot
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /root
Document Length:        2 bytes

Concurrency Level:      5
Time taken for tests:   30.011 seconds
Complete requests:      10
Failed requests:        0
Total transferred:      1840 bytes
HTML transferred:       20 bytes
Requests per second:    0.33 [#/sec] (mean)
Time per request:       15005.250 [ms] (mean)
Time per request:       3001.050 [ms] (mean, across all concurrent requests)
Transfer rate:          0.06 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    1   0.4      1       2
Processing:  3002 12000 4472.0  15001   15002
Waiting:     3002 12000 4472.0  15001   15002
Total:       3003 12002 4471.7  15002   15002

Percentage of the requests served within a certain time (ms)
  50%  15002
  66%  15002
  75%  15002
  80%  15002
  90%  15002
  95%  15002
  98%  15002
  99%  15002
 100%  15002 (longest request)

C:\Users\user>

And the program output as following:

20180308 02325142 info  : server start!
20180308 02325428 debug 2018-03-08 02:32:54 : GET /root
20180308 02325728 debug 2018-03-08 02:32:57 : GET /root => OK
20180308 02325728 debug 2018-03-08 02:32:57 : GET /root
20180308 02330028 debug 2018-03-08 02:33:00 : GET /root => OK
20180308 02330028 debug 2018-03-08 02:33:00 : GET /root
20180308 02330328 debug 2018-03-08 02:33:03 : GET /root => OK
20180308 02330328 debug 2018-03-08 02:33:03 : GET /root
20180308 02330628 debug 2018-03-08 02:33:06 : GET /root => OK
20180308 02330628 debug 2018-03-08 02:33:06 : GET /root
20180308 02330928 debug 2018-03-08 02:33:09 : GET /root => OK
20180308 02330928 debug 2018-03-08 02:33:09 : GET /root
20180308 02331228 debug 2018-03-08 02:33:12 : GET /root => OK
20180308 02331228 debug 2018-03-08 02:33:12 : GET /root
20180308 02331528 debug 2018-03-08 02:33:15 : GET /root => OK
20180308 02331528 debug 2018-03-08 02:33:15 : GET /root
20180308 02331828 debug 2018-03-08 02:33:18 : GET /root => OK
20180308 02331828 debug 2018-03-08 02:33:18 : GET /root
20180308 02332128 debug 2018-03-08 02:33:21 : GET /root => OK
20180308 02332128 debug 2018-03-08 02:33:21 : GET /root
20180308 02332428 debug 2018-03-08 02:33:24 : GET /root => OK

It's obvious that the requests were processed one-by-one.
What should I do? Thanks a lot!

Offline

#5 2018-03-08 14:02:05

mpv
Member
From: Ukraine
Registered: 2012-03-24
Posts: 1,571
Website

Re: Does THttpApiServer is single-threaded?

You miss fServer. Clone.  Please,  see sample 22 for code example

Offline

#6 2018-03-08 14:06:09

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,655
Website

Re: Does THttpApiServer is single-threaded?

You forgot to call Clone().

  fServer.AddUrl('root', '8080', False, '+', True);
  fServer.Clone(32);

I will fix sample 9 and 13 code.
Sample 22 was correct, in that matter.

But I would not use low-level THttpApiServer, anyway, but higher level TSQLHttpServer, which is cross-platform and has additional features.

Offline

#7 2018-03-08 14:08:16

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,655
Website

Re: Does THttpApiServer is single-threaded?

Also note that your use of TSynLog is plain wrong.
Each TSynLog instance should be used in a single thread, locally.

You should use it as such:

procedure TMyServer.Start;
begin
  fServer.Start;
  TSynLog.Add.Log(sllInfo, ': server start!');
end;

Or in a single method:

function TMyServer.Process(Ctxt: THttpServerRequest): Cardinal;
var
  log: TSynLog; // or ISynLog if you use Enter
begin
  log := TSynLog.Add; // or := TSynLog.Enter for ISynLog local variable
  log.Log(sllDebug, '% : % %', [NowUTCToString, Ctxt.Method, Ctxt.URL]);
  Sleep(3000);
  log.Log(sllDebug, '% : % % => %', [NowUTCToString, Ctxt.Method, Ctxt.URL, 'OK']);
  Ctxt.OutContent := 'OK';
  Result := 200;
end;

Offline

#8 2018-03-09 04:02:21

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

Thank you very much!  fServer.Clone() works great!

I have considered TSQLHttpServer, but it takes more learning.
According to my current understanding, it seems that TSQLHttpServer needs to work with a TSQLRestServer together,
and the TSQLRestServer needs to work with TSQLRecord which needs to associate database tables (and record columns with published properties) exactly.

But my project have plenty of legacy stored procedures. e.g. a GetUserLoginInfo procedure accepts a username and password then do validation and logs to an authorization log table internally, this procedure returns an multi-table joined query result (not only the user table stuffs). So maybe I could not declare a TSQLRecord based TUser class exactly maps to existed User table, since the result of GetUserLoginInfo is mixed with multi-tables fields. I don't know how to fetch an multi-table joined query result into inter-related TSQLRecord objects till now.

Please forgive my poor understanding and description...

Your documents is a great, it talks about not only the use of framework classes, but also advanced software architecture ideology.
So it deserves repeated readings, but it takes a lot of time for me, I wish there will be more practical examples.
Thanks to all who work for the great mORMot framework!

Last edited by DDGG (2018-03-09 04:04:43)

Offline

#9 2018-03-09 05:55:28

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

ab wrote:

Also note that your use of TSynLog is plain wrong.
Each TSynLog instance should be used in a single thread, locally.
...

I will use it in accordance with your instructions, thanks!

BTW: I saw the implementation of the TSynLog.Add, it seems not thread safe, is it?


class function TSynLog.Add: TSynLog;
{$ifdef HASINLINENOTX86}
var f: TSynLogFamily;
begin
  if Self<>nil then begin // inlined TSynLog.Family (Add is already inlined)
    f := PPointer(PtrInt(Self)+vmtAutoTable)^;
    if f=nil then
     result := FamilyCreate.SynLog else
     result := f.SynLog;
  end else
    result := nil;
end;
{$else}
asm
  call TSynLog.Family
  jmp TSynLogFamily.SynLog
end;

Thanks.

Offline

#10 2018-03-09 07:53:53

edwinsn
Member
Registered: 2010-07-02
Posts: 1,218

Re: Does THttpApiServer is single-threaded?

@DDGG, I believe your situation perfectly fits in the use case of TSQLRestServer, please search for 'method-based service' and 'interface-based serfice' in the mORMot document, that's the correct approach for implementing your GetUserLoginInfo API.

and you can use an in-memory db (use ':memory:' as the db file name) as the database, then just define a single dummy TSQLRecord-derived class in order for the framework to work.


Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.

Offline

#11 2018-03-09 08:26:01

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,655
Website

Re: Does THttpApiServer is single-threaded?

TSynLog.Add is thread-safe since to enable logging you need to access the TSynLog.Family at least once.
It is perfectly thread-safe to write in the main thread, and read in several threads.

Do not be too much dubious: the framework is used on production since years on heavily multi-threaded apps.

Offline

#12 2018-03-09 08:58:58

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

@edwinsn Although I don’t quite understand you said, I'm still grateful!

@ab I seem to understand: Because TSynLog.Family has been called in the main thread, so when TSynLog.Add called in TMyServer.Process with multi-threads it just simply execute result := f.SynLog; (it is a Singleton design pattern). is it?
Thanks.

I don't doubt the framework's multithreaded security design, just worry that I'm using it incorrectly...

Offline

#13 2018-03-09 09:58:34

edwinsn
Member
Registered: 2010-07-02
Posts: 1,218

Re: Does THttpApiServer is single-threaded?

DDGG wrote:

@edwinsn Although I don’t quite understand you said, I'm still grateful!

If you want to expose from the server-side to the client-side your stored procedure such as GetUserLoginInfo you mentioned, you have two high-level "mORMot approaches":
1 - use method-based service, or
2 - use interface-base service.


For approach #1, you can take a look at https://github.com/synopse/mORMot/blob/ … Server.dpr

Code like the following will be much much more easier and intuitive than doing everything in THttpApiServer.OnRequest.

type
  // TSQLRestServerFullMemory kind of server is light and enough for our purpose
  TServiceServer = class(TSQLRestServerFullMemory)
  published
    procedure Sum(Ctxt: TSQLRestServerURIContext);
  end;

See that? You don't even need the TSQLRecord class or the SQLITE engine.


Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.

Offline

#14 2018-03-09 11:05:11

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

edwinsn wrote:

For approach #1, you can take a look at https://github.com/synopse/mORMot/blob/ … Server.dpr

See that? You don't even need the TSQLRecord class or the SQLITE engine.

Thanks for your detailed instructions, I have learned the sample you mentioned.

Do you mean I need to re-implement a GetUserLoginInfo service by Delphi code? No, I just want to re-use those legacy stored procedure in database.

So I'm writing a service class and some methods simply call the underlying stored procedure (e.g. GetUserLoginInfo).
I'm going to convert the stored procedure's result from variant to object for temporary storage and processing, and convert it to JSON for a HTTP response. (I know there is a FetchAllAsJSON but I do sure need an object)

Offline

#15 2018-03-09 12:45:22

edwinsn
Member
Registered: 2010-07-02
Posts: 1,218

Re: Does THttpApiServer is single-threaded?

No, I didn't mean you need to re-implement/duplicate the code logic from the stored procedure, you just call it from within the service class. What I mean is that, if I understand you correctly you don't have to manually do the following:

"I'm going to convert the stored procedure's result from variant to object for temporary storage and processing, and convert it to JSON for a HTTP response. (I know there is a FetchAllAsJSON but I do sure need an object)"

if you use TSQLRestServer instead.

Last edited by edwinsn (2018-03-09 12:45:32)


Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.

Offline

#16 2018-03-13 02:17:12

DDGG
Member
Registered: 2018-01-10
Posts: 18

Re: Does THttpApiServer is single-threaded?

edwinsn wrote:

No, I didn't mean you need to re-implement/duplicate the code logic from the stored procedure, you just call it from within the service class. What I mean is that, if I understand you correctly you don't have to manually do the following:

...

if you use TSQLRestServer instead.

Okay, I will continue learning, thank you!

Offline

Board footer

Powered by FluxBB