The ISAPI Execution Environment

0
58

This document bases on information and testing done with IIS 1.0. We have not re-tried it with later versions. However, we feel very comfortable with the information contained herein and think that it still is correct.

There is just one exception: if you create an application in MMC under IIS 4.0, your ISAPI extension is run in a separate process space (as far as we know under MTS control). In this case, you obviously do not run in the process space of IIS (that’s why it is called process isolation). As far as your extension is concerned, that should make no big difference.

When we first started developing isapi apps, things were first very clear to us. However, as soon as we hit the field of “real” IIS application with multiple concurrent requests and multiple extensions being loaded, things became quite more complicated. All of a sudden, some things looked really confusing.

We found that the key to order this things at get a productive application out of our coding is understanding what IIS really does and how an extension is executed. This is what we call the ISAPI Execution Environmen and this is what this document is all about.

We hope it will save you some valuable time when first starting isapi development.

In order to allow easy offline storage and printout, we have created just this single, relativly large document. People with slow links and/or not-so-current browsers, please forgive us. We think it is the best compromise available.

If you find anything to add, correct or otherwise change in this document, please do not hesitate to mail us. Any comments are welcome.

Overview

If you start developing server extensions, you need to know something about their execution environment. ISAPI programs – extensions as well as filters – are a very special kind of animal. Being DLLs, they are no processes by themself – rather they share a common process space (IIS’ process). Thus they possibly interact with both the IIS as well as other ISAPI apps. Furthermore, IIS can handle multiple requests concurrently. Thus your isapi app can be called by more than one thread concurrently.

ISAPI is a very, very performant interface for highly sophisticated interactive web applications. However, such a highly performant interface has its cost. This – in our turn – is the additional care to be taken developing your applications. Please don’t feel scared away from isapi: it can be easily used if you know about its special nature and take care for its special needs. And this is all this document is about.

Life of an ISAPI App

An isapi app has a very special life cycle. It is dependent on its host, the IIS. First, lets look what IIS does during its life and how this interferes with our app:

  • First of all, its not the web publishing service that is loaded by the os’ service control manager. Rather it is a program called “inetinfo.exe” which in turn starts the “real services” (implemented as DLLs). We in turn will focus on the Web Publishing Service, which is named “w3svc”.
  • When w3svc starts, it does its initialization work. During its initialization, it checks the registry for installed isapi filters and initializes them (note: the life of an isapi filter won’t be discussed any further). W3svc also loads and initializes some other WOSA services, like Windows sockets and ODBC (for logging purposes).
  • During its initialization phase IIS creates a number of threads that later on will be used to serve clients. If you look with the process viewer application at an just started-up IIS, there are usually between 12 and 14 threads in its process space, a lot of them without any processing so far. We think these threads build a pool of “worker threads”. However, we found no documentation on that so far.
  • Being started up, IIS listens for HTTP-requests. As soon as one comes in, IIS assigns a worker thread (usually the same that’s the shortest one finished) to the request and carries on processing (we think – but have not verified – that the number of worker threads is extensible as more concurrent requests come in).
  • The worker thread then analyses the type of request. It uses the configuration settings supplied by Internet Service Manager to decide on what to do next. Choices are “web file services” (just deliver the need file more or less unprocessed) or call an extension. In the later case there can be numerous extensions to call – basically they are all the same: call an isapi extension. This is even true with IDC, in which case the IDCODBC.DLL isapi extension is called.
  • Now we get into business. Let’s think the worker thread has requested that our app is to call.
    • If not already loaded, IIS then first loads our DLL. As usual, this triggers the execution of our DllMain entry point. Here is the right place to do any global initialization of our extension. There is nothing IIS specific at this stage of execution!
    • Soon after loading the DLL, IIS requests our extension to register with IIS. This is done by calling our routine GetExtensionVersion. Here we have to supply a little information about ourselvs: like our name and the isapi version we are developed for. Please note that GetExtensionVersion is only called once. It won’t get called further on, not because we are executed by another worker thread or for whatever other reason. The only case in which an isapi extension’s GetExtensionVersion will be called again is after unloading and reloading the DLL (as the whole process starts from scratch). For now we can remind: GetExtensionVersion will only once be called in the lifecycle of our app.
    • Now IIS is in business with us. It knows how to handle us (depending on isapi version) and now starts activating us. For any further http request, IIS starts at this very point, skipping all extension initialization work.
    • Now our main entry point – HttpExtensionProc- is called by the worker thread. IIS supplies us with the input data stream and allows us to write to the client. Please note the multiple concurrent http requests for our application result in multiple worker threads calling our app concurrently. So at any given time, our code might be executed by numerous worker threads simultaneously. As soon as our processing is done (HttpExtensionProc returns), IIS begins to finish processing. Our DLL remains loaded.
  • The worker thread now does everything need to complete the request. After that, it is available for usage by another request and put on hold until such a requests reactivates it. We do not know if worker threads will be reduced when IIS’ workload is reduced. However, we do expect it (another nice thing worth tracing).
  • More important than that is what happened to our DLL: This one is still sitting in IIS’ process space waiting for any other requests hitting it. It has not been unloaded, resulting in possible very low overhead for each additional call (this is one of the performance strength of isapi).
  • However, the server might decide to remove our DLL at any given time. Again, we do not know the details. But it might happen. In this case, IIS calls UnloadLibrary and thus our DllMain procedure is hit again. Now is the right time to clean up any resources globally allocated (free any handles, heap memory structures etc.). After that, our extension is in an uninitialized (unregistered to IIS) state. If we get another request, the whole initialization process starts again (thus IIS tries to avoid unloading extensions).
  • One thing missing in IIS lifecycle: if the user decides to shut down IIS (or the NT OS at large), IIS unloads all still-loaded DLLs (as might be our extension), closes down all worker threads and finally shuts down itself.
  • This is what IIS does during its life. At least this is what we think it does. This is a good place to remind you that we are neither Microsoft nor have written IIS nor have access to its source. All the above is a) extracted from documentation, b) seen in the debugger and system tools, c) experienced and – very important – d) put together based on general software engineering principles. IIS’ developers might have done things quite different and we can be in error.

    However, there is also good news: at least the understanding of the above written has helped us pretty well when developing our extensions. So it might not be correct, it still should be helpful.

    Well, that should be enough of warnings. Back to software again. If you look at the above scenario, there are a number of important points in it:

    1. If you have to deal with global structures inside your extension, DllMain is a perfect place to deal with them.

    2. Do not lock any resources between requests. You never know when you will be called next: whether in the next microsecond or in five weeks time. Thus, no ressources should be occupied by your “ever active” app.

    3. Be sure to handle multithreading correctly (also see section below on this topic)

    4. Never expect to be called by the same worker thread again. It might happen, it might not.

    5. Clean up after each request! Other extensions might be called by the thread that just executed your coding.

    If you follow this guidelines, you can be pretty sure not to harm anything in IIS.

    Things to watch for

    Memory Protection

    Being part of IIS process space, isapi apps have full access to all of this space. Thus it is not only possible to read data at any location of the process space, your app can also write at any writable memory location. This includes locations outside of your isapi app, including some that are vital for IIS’ health. An isapi app can thus easily crash IIS and stop it from running. Keep this in mind when debugging you applications. They deserve very special attention, as your application’s error can cause some other applications – possibly some highly important ones – to crash instantly (well, looks a bit like being back in good old Win16 days…).

    Multithreading

    Isapi apps get called by multiple IIS threads concurrently (given concurrent HTTP requests). Thus your coding needs to be threadsafe. You have to apply special threadsafe coding technologies in order to get things running well.

    In contrast to some of your other multithreaded applications, however, you do not have control over the “main” thread of your program. First let’s explain what I mean with main thread. This is the thread that initially gets control, initializes global data structures and the “multithreading”. With multithreading initialization I mean the creation of synchronization objects and subordinate threads (as well as, of course, all data that is used by your application to control useful multithreading work).

    One example of this might be the allocation of an ODBC environment handle. According to ODBC SDK documentation such a handle should exist only once in a process space. The resulting handle itself is threadsafe and can be used by multiple threads concurrently. Thus, in your regular application, you might create the environment handle inside your main thread at startup, than go to your regular work (which involves the multithreading) and – at process termination time – close the handle. You’ll never run into trouble, as your application has total control over the usage of its handle and who’s using it.

    With IIS, however, things are quite different. Here the main thread is IIS. IIS unfortunately doesn’t know about your need of an ODBC environment handle. Thus its main thread doesn’t allocate one (at least we don’t know about this) and in turn can not pass it down to you. So your only option is to allocate the handle at DLL startup (using DllMain) and free it at unload time. This works pretty well as long as there is only one isapi app doing ODBC operations. But what about a second one concurrently being loaded? Doesn’t this one also need to get and free a handle? Sure it does. And – voila – here we have two handles inside one process space. As far as I know, you have absolutely no chance to get around this (as you do not control the main thread).

    Giving this example, there is one question: why do real-life IIS installations not crash? To be honest – I don’t know. Most isapi apps do exactly what I have described. Without ever crashing. I have run quite a lot of tests with a large number of concurrently allocated ODBC environment handles – and it never crashed. To me, it looks as if only documentation is in error and this operations seems to be pretty OK.

    The above example is an extreme side of IIS’ multithreading topic: it’s not really all that bad, but be warned about what you are doing and what implications it has. Please note that there may be other scenarios like the “ODBC environment handle scenario” described above. Also keep in mind that your perfectly well coded isapi app might run into trouble if it meets a not-so-well coded other app in the same server.

    And if you have a look at just your application: be sure to use only threadsafe technologies and coding. Be very skeptic about third party software (MFC included). Dig into the topic to be sure that it is really threadsafe. Check it with multiple concurrent executions at the beginning of your development project.

    Inside your own coding, be although sure to code threadsafe. You won’t need special coding technologies just to get your isapi app up and running. If there is no need to access “global” objects inside your app, you probably will never need any special coding. However, if you use e.g. global data structures you should protect them from improper usage by your threads. If you need to do this, use synchronization structures like critical sections. Make sure that no more than one thread can access such a critical object at a given time. By the way: this is they way to get around with non-threadsafe third party coding: if you *really* have to use it, put it in a critical section and be happy (although this has some performance implications). This is what Microsoft did in its OLEISAPI sample coding (for NT w/o DCOM).

    If you have special interest in IIS’ multithreading issues, you might want to download our IIS Multithreading Demo (isapithrd.dll) program. Isapithrd displays which worker thread is currently executing it and how many instances are concurrently executed.

    Stack Size

    Stack is nearly unlimited in Win32, isn’t it? You’d probably ask. Well, it is. Each of your threads might address up to 1 MB stack space. Not unlimited but pretty much. So there shouldn’t be any problem, should it?

    Well, there is one little tiny thing to take into consideration. Whenever a thread (the main thread for example) creates a new thread, it specifies the new threads maximum stack size. Fine if it is the 1 MB maximum. Less nice, if it is below. As far as we know, IIS does limit an isapi apps stack size. We guess that is in expectation of many, many apps being loaded concurrently and sharing the 2 GB process address space (but even then there would be plenty of virtual space for their stacks….).

    We do not know the exact reason for this limit. However, due to many reports on stack overflow, we do know there is a limit! We have not yet tried to get the exact number. But as a general guideline, we recommend allocating as few stack space as possible. If you have larger size data structures, it’s preferably to use the heap (using the “new” operator). However, if you use the heap, keep in mind that you should free any allocated memory!

    MFC classes

    We (and others as well) had some bad experiences with MFC classes (version 4.x!). Before you use any class, be sure to check if it can successfully be used inside an isapi app. We know that at least the following is dangerous:

  • using CRecordSet
  • using CHttpServer in general (does not clean up neatly)
  • using winsock classes (has been reported to us)
  • With the release of VC++ 5.0 this should be history. Be sure to upgrade to this version to avoid any MFC problems inside your ISAPI app.

    Disclaimer

    The information contained in this document is to the best of our knowledge. However, we do not guarantee its accuracy. Use the information contained herein at your sole risk.

    Rainer Gerhards works for Adiscon, who offers software for server monitoring. Visit http://www.monitorware.com for more information and free downloads.

    LEAVE A REPLY

    Please enter your comment!
    Please enter your name here