Programming considerations when using the "out-of-process" model

Multithreaded vs Singlethreaded

One of the main differences between running an ISAPI filter or extension as "out-of-process" is that it will run in a multithreaded environment, as oposed as running "in-process" where it will run in a singlethreaded application.

When writting an ISAPI module to be run "out-of-process", you must take care so that your ISAPI application is thread safe.

For example, let's take one of the examples in the ISAPI Introduction:



/* Import the ISAPI constants and type definitions */
#include "httpext.h"

BOOL WINAPI 
GetExtensionVersion( HSE_VERSION_INFO *pVer ) 
{
   pVer->dwExtensionVersion = HSE_VERSION_MAJOR;
   strncpy(pVer->lpszExtensionDesc, "A simple page counter", 
        HSE_MAX_EXT_DLL_NAME_LEN);
   return TRUE;
}

int hits=0;

DWORD WINAPI
HttpExtensionProc( LPEXTENSION_CONTROL_BLOCK ecb )
{
   char *header   =  "Content-Type: text/html";
   int headerlen  = strlen( header );
   char msg[256];
   int msglen;

   /* use a server support function to write out a header with our
      additional header information */
   ecb->ServerSupportFunction( ecb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, 0,  
                               &headerlen, (DWORD *)header );

   /* write out the number of accesses */
   sprintf( msg, "This page has been accessed %d times", ++hits );
   msglen = strlen( msg );
   ecb->WriteClient( ecb->ConnID, msg, &msglen,0 );

   /* return, indicating success */
   return 0;	
}

as you can see in the code, this extension counts the number of times it was invoked using a global variable called hits. The problem with this architecture is that if it is running in a multithreaded environment, the global variable hits isn't thread safe, because more than one thread can access it at the same time.

To make it thread safe, we need to wrap this variable with a mutex variable, so that no more than one thread can access it at the same time:



/* Import the ISAPI constants and type definitions */
#include "httpext.h"
#include <pthread.h> /* Assuming you are using POSIX threads. */

int hits=0;
pthread_mutex_t hits_mutex;

BOOL WINAPI 
GetExtensionVersion( HSE_VERSION_INFO *pVer ) 
{
   pVer->dwExtensionVersion = HSE_VERSION_MAJOR;
   strncpy(pVer->lpszExtensionDesc, "A simple page counter", 
        HSE_MAX_EXT_DLL_NAME_LEN);
   
   /* Initialize the Mutex */
   pthread_mutex_init( &hits_mutex, NULL );

   return TRUE;
}

DWORD WINAPI
HttpExtensionProc( LPEXTENSION_CONTROL_BLOCK ecb )
{
   char *header   =  "Content-Type: text/html";
   int headerlen  = strlen( header );
   char msg[256];
   int msglen;

   /* use a server support function to write out a header with our
      additional header information */
   ecb->ServerSupportFunction( ecb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, 0,  
                               &headerlen, (DWORD *)header );

   /* write out the number of accesses */

   /* try to lock the mutex */
   pthread_mutex_lock( &hits_mutex );
   sprintf( msg, "This page has been accessed %d times", ++hits );
   /* now unlock the mutex */
   pthread_mutex_unlock( &hits_mutex );

   msglen = strlen( msg );
   ecb->WriteClient( ecb->ConnID, msg, &msglen,0 );

   /* return, indicating success */
   return 0;	
}

We defined

sprintf( msg, "This page has been accessed %d times", ++hits );
as a critical section of the code, by wrapping it with a mutex lock/unlock.

You must also take care with what system library calls you use in your application, as some parts of your systems libraries might not be trhead safe.
Please read the systems programming documentation of your operating system if in doubt.

Unsupported ISAPI functionality in the "out-of-process" model

Because of the difference in architecture of the "out-of-process" model, not all ISAPI function calls are supported. The following is a list of unsupported ISAPI features when running "out-of-process":

Extended Notification of filters and extensions.

When running in "out-of-process", ISAPI filters and extensions have available to them an additional notification mechanism besides the Get*Version() and Http*Proc() functions. If in your application you define the following function:
void ZSLMain(int notification);
it will be called with the following values:

int notificationMeaning
ZSLLOADThe module was just loaded. Get*Version() will be called next.
ZSLUNLOADTerminate() was called, and the module is about to be unloaded.
ZSLTHREADA new thread was just created, or was just given this request to work in. Http*Proc() will be called next.
ZSLEXITA thread just finished to do the work for this request. The thread will next exit, or wait for a new request.

Tracing "out-of-process" Applications

You can use the program 'isapisnoop' (in $ZEUSHOME/web/bin) to trace the communication between the Zeus webserver and the external zeus.isapi process which loads and runs your isapi filters and extensions.

isapisnoop should only be used in a development environment.

Start isapisnoop, then restart your webserver: the zeus.isapi process will connect to the running isapisnoop and log the communication protocol. isapisnoop writes the log messages to stdout.

Use isapisnoop -d 2 to also log any ISAPI function calls made during the processing of a request.