Mega Code Archive

 
Categories / Delphi / System
 

Adding request Queuing and Thread Pooling to your ISAPI .DLL

Title: Adding request Queuing and Thread Pooling to your ISAPI .DLL Question: 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. ===