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.
===
|