Visit our Sponsor   Visit our Sponsor
delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
484 Users Online NOW
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback





Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi



Loremo - the 1.5 liter car coming in 2009




Startblatt.de






Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (9)


Adding request Queuing and Thread Pooling to your ISAPI .DLLFormat this article printer-friendly!Bookmark function is only available for registered users!
Product:
Delphi 5.x (or higher)
Category:
WebSnap
Skill Level:
Scoring:
Last Update:
02/08/2006
Search Keys:
delphi delphi3000 article borland vcl code-snippet ISAPI WebBroker Thread Thread-Pool CreateIoCompletionPort PostQueuedCompletionStatus GetQueuedCompletionStatus ImpersonateLoggedOnUser OpenThreadToken
Times Scored:
37
Visits:
12292
Uploader: Quinton Bernhardt
Company:
Reference: N/A
 
Question/Problem/Abstract:
My ASP performs better (under load) than my ISAPI DLL written in Delphi 5 using ISAPIApp.  They both perform the same tasks i.e. execute stored procedures (using ADO 2.5) and serve the resulting data back to the client.

We used Microsoft's Web Stress Tool (http://webtool.rte.microsoft.com) to load the ASP and then the DLL with 100 simultaneous requests and Mr. DLL lost the race.
Answer:



We knew that ASP used thread pooling and, without really understanding why, we added thread pooling to the DLL.  And what do you know, Mr. ASP never won again.

SIDE AFFECT:
===========
A benefit actually.  Queuing of ISAPI request is implicitly added to your solution.  As you know (or will realize in a production environment) WebBroker/ISAPIApp does NOT queue requests.  What I mean is if you have set your Application.MaxConnections to x and you receive x+1 simultaneous hits WebBroker generates a "500 Internal Server Error" - a big time no-no in a production environment.

Anyway - attached is the unit we created which sits on top of ISAPIApp to auto-magically transforms your app into a thread pooling, request queuing - machine.

Before deciding to use this please use the Microsoft Web Stress Tool to test you app - once without pooling and again with pooling.  You may not require it.

====

unit uISAPIThreadPool;
(*
     SUMMARY
     =======
     This unit implements a thread pool for a Delphi 5 ISAPI Web Application.

     If all threads are busy in the thread pool the request are queued, unlike
     TISAPIApplication that just generates a "500 Internal Server Error"

     ENVIRONMENT
     ===========
     Tested on Win2k Professional/Server and IIS 5.

     USE:
     ====
     Put this unit in the uses clause (in the main web project source) after the ISAPIApp enty
     as this unit overrided the 3 exports (HttpExtensionProc, GetExtensionProc & TerminateExtension).

     Currently an instance of TThreadPool is created with a pool size of 25.  This seems to be
     optimum.  The Microsoft MFC examples use a pool of 10.  Play around with the pool size to
     achieve maximum performance.

     Author: Quinton Bernhardt
     email: quinton@frooky.co.za
*)
interface
uses ISAPI2,Windows;

function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall;
function HttpExtensionProc(var ECB: TEXTENSION_CONTROL_BLOCK): DWORD; stdcall;
function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;

implementation
uses ISAPIApp, SysUtils, Classes;

  type
        TThreadWorkData = Record
         ECB: PEXTENSION_CONTROL_BLOCK;
         SecurityToken: THandle;
        end;
        PThreadWorkData = ^TThreadWorkData;

        TThreadPool = class
         private
           FPool: Array of THandle;
           FCompletionPortID: THandle;
         public
           function newInstanceData(ECB: PEXTENSION_CONTROL_BLOCK; SecToken: THandle): PThreadWorkData;
           constructor Create(InitThreads: Integer);
           function postWorkItem(ECB: PEXTENSION_CONTROL_BLOCK): LongBool;
           destructor Destroy; override;
           property CompletionPortID: THandle read FCompletionPortID;
        end;

var ThreadPool: TThreadPool;

  function WorkerFunc(Context: TThreadPool): Integer;
    var SLen: Cardinal;
        SData: PThreadWorkData;
        OL: POverLapped;
        CompletionPortID: THandle;

    begin
      CompletionPortID:= Context.CompletionPortID;

      While GetQueuedCompletionStatus(CompletionPortID, SLen, Cardinal(SData), OL, INFINITE) do
       try
        try
          if (OL = Pointer($FFFFFFFF)) then break;
          ImpersonateLoggedOnUser(SData^.SecurityToken);
          ISAPIApp.HttpExtensionProc(SData^.ECB^);
          With SData^.ECB^ do
            ServerSupportFunction(ConnID, HSE_REQ_DONE_WITH_SESSION, nil , nil, nil);
        finally
          CloseHandle(SData^.SecurityToken);
          (* BUGFIX (1-dec-2000) only Dispose of SData if OL <> $ffffffff *)
          if SData <> Nil then Dispose(SData);
        end;
       except // Unhandled Exception, do not exit the worker thread.
         (*
            SHOULD NEVER HAPPEN.
            ISAPIApp.HttpExtensionProc already catches all exceptions AND
            the Win32 functions used here return error codes i.e. they
            do not raise exceptions.

         *)
       end;
       Result:= 0;
    end;

  function TThreadPool.newInstanceData(ECB: PEXTENSION_CONTROL_BLOCK; SecToken: THandle): PThreadWorkData;
   begin
     New(Result);
     Result^.ECB:= ECB;
     Result^.SecurityToken:= SecToken;
   end;

  function TThreadPool.postWorkItem(ECB: PEXTENSION_CONTROL_BLOCK): LongBool;
    var SecToken: THandle;    
   begin
     // Open Security token of calling thread this function
     OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, False, SecToken);
     Result:= PostQueuedCompletionStatus(FCompletionPortID, 0,
                                     Cardinal(newInstanceData(ECB, SecToken)),
                                     Nil);
   end;

  constructor TThreadPool.Create(InitThreads: Integer);
   var i: integer; FThreadID: LongWord;
    begin
      FCompletionPortID:= CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
      SetLength(FPool, InitThreads);
      for i:= 0 to InitThreads - 1 do
        FPool[i]:= BeginThread(nil, 0, @WorkerFunc, Self, 0, FThreadID);
    end;

  destructor TThreadPool.Destroy;
   var i: integer;
    begin
      for i:= 0 to Length(FPool)-1 do
       PostQueuedCompletionStatus(FCompletionPortID, 0, 0, Pointer($FFFFFFFF));

      WaitForMultipleObjects(Length(FPool), @FPool, TRUE, 120000);

      for i:= 0 to Length(FPool)-1 do
       CloseHandle(FPool[i]);

      CloseHandle(FCompletionPortID);

      FPool:= Nil;
    end;

  function HttpExtensionProc(var ECB: TEXTENSION_CONTROL_BLOCK): DWORD;
  var IsPosted: BOOL;
  begin
   try
    // Post Work Item to completions port and store the thread's security context
    IsPosted:= ThreadPool.postWorkItem(@ECB);
    if not IsPosted then
       Result:= HSE_STATUS_ERROR
    else
       Result:= HSE_STATUS_PENDING;
   except
     Result:= HSE_STATUS_ERROR;
   end;
  end;

function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL;
  begin
    ThreadPool:= TThreadPool.Create(25);
    Result:= ISAPIApp.GetExtensionVersion(Ver);
  end;

function TerminateExtension(dwFlags: DWORD): BOOL;
   begin
     ThreadPool.Free;
     Result:= ISAPIApp.TerminateExtension(dwFlags);
   end;
end.
===





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
It's work with Delphi 6???
    Enrique Ortuño (May 12 2002 3:00AM)

Hi Do you suggest use this code and not Borland's code???

There are some incompatibility?

Thanks
Respond

RE: It's work with Delphi 6???
Quinton Bernhardt (May 15 2002 10:06AM)

Hi

Yes, this will work with Delphi 6 but this unit has become redundant as Borland has released a ISAPI threading unit, similar to this, for Delphi 6 in Update Pack 2.  Alternatively you can download the Borland unit from Steve Trefethen's site at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=17616

Remember, this unit only works on IIS servers

cheers
Respond

Number of Threads and MaxConnections
    Mairon Benevides (Aug 9 2001 2:30PM)


I saw that the number of threads of the pool shoud be the same in ISAPIApp.MaxConnections.

In the article, is used 25 threads. The pool uses the maximum of 25 WebModules.

If you put less pool thread that MaxConections allow, some threads will receive a error about of the maximum connections exceeded.  
Respond

RE: Number of Threads and MaxConnections
Quinton Bernhardt (Aug 10 2001 9:58AM)

Yip, that is something i never added to the documentation.  Just to correct you though -- if your MaxConnection value is lower than your thread pool depth then delphi returns Error 500 if if max connections is exceeded.

What i suggest is that you set you MaxConnections to 9999.  The thread pooling will only allow 25 of those connections to be active at once anyway.

Another thing....
This is very simple thread pooling.  This source could be changed to include detection of how many processors the box has and automatically set the number of threads etc.  The value of 25 is just what i used during testing.  The actual number to use will vary on the strength (processors, resources, memory) of the box that your dll runs on.

Delphi 6 now comes with its own ISAPI Thread Pool Manager.  Check it out.
Respond

RE: RE: Number of Threads and MaxConnections
Chuck Gadd (Dec 19 2001 9:18PM)

according to several people (borland employees), the thread pooling included in D6 is very buggy and should not be used.

Respond

RE: Number of Threads and MaxConnections
Quinton Bernhardt (Jan 2 2002 7:30AM)

yeah, i've looked at the D6 pooling code.  The code maintains it's own array/pool of threads and does all the thread management itself.  
The code in this article lets the WIN32 API do all the thread pool management which means 1. i'm lazy and 2.  less chance of a bug creeping in...
Respond

Where do I place my "user" code
    Malcolm Rhoda (Mar 6 2001 1:01AM)

Where do I place my code to say service a client request for access to a database ? Typically the function (user code) will open a recordset and navigate through the records to produce an HTML page and then send it to the browser).


Respond

RE: Where do I place my
Quinton Bernhardt (Mar 7 2001 1:13AM)

Hi Malcolm

Program Logic (i.e. DB access etc.) belongs in your WebBroker App.  See the WebBroker help on the structure of a WebBroker Application.

Delphi 5 has an webbroker example at \demos\webserv
Respond

ISPI thread Pool
    Lukasz Zielinski (Feb 17 2001 2:18PM)

I've tested it on WinNT4 and IIS4 works PERFECT!!!!
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


  Visit our Sponsor

 

  Community Ad of
Hans Gulö
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)