This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


September 1998

Microsoft Systems Journal Homepage

Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp.

Developers are increasingly using the Active Server Pages (ASP) runtime environment for COM components. Since its inception, ASP has had a reputation for being quirky with respect to threading. One reason for this behavior is that these pages run within an ISAPI extension DLL, which has its own threading problems. Another reason is that some performance tips currently in circulation are totally bogus. While each version of ASP has its own idiosyncrasies, I will narrow my discussion to the version of ASP that ships with Microsoft® Internet Information Server (IIS) 4.0.
      ASP generally follows the rules of COM. If you write an in-process component that is accessed by an ASP script, ASP won't do nasty things to your object. However, if you make it hard for ASP to use your object, overall application performance will suffer.
      An ASP application is composed of one or more ASP files under a single virtual directory. ASP files contain embedded server-side scripts that are parsed by IIS before it sends the response back to the client. At runtime, each ASP application has one or more active sessions that correspond to a particular Web user.
      A single global.asa file allows the ASP developer to define application and session-wide event handlers and objects. Consider the following global.asa file:

 <script RunAt=Server language=vbscript>
 Sub Application_OnStart()
     Rem Happens once at beginning of the 1st session
 End Sub
 Sub Session_OnStart()
     Rem Happens at beginning of each session
 End Sub
 Sub Session_OnEnd()
     Rem Happens at end of each session
 End Sub
 Sub Application_OnEnd()
     Rem Happens at end of the last active session
 End Sub
 </script>
This script defines empty handlers for each of the four intrinsic events that are fired by the ASP runtime. A global.asa file can also contain <object> tags that allow objects to be created with application, session, or page scope. This global.asa fragment

 <object RunAt=Server Scope=Application 
     id=g_morris
     progid="Animals.Cat">
 </object>
 <object RunAt=Server Scope=Session
     id=s_fido 
     progid="Animals.Dog">
 </object>
creates one instance of the COM class Animals.Cat that is associated with all sessions of the application. It also associates a unique instance of the COM class Animals.Dog with every session of the application.
      ASP postpones creating these objects until their programmatic names are referred to in script text. To access the application-wide Cat, you can simply write the following line of script in an ASP page:

 g_morris.ForceSneezingInHumans
To access the Dog that is created specifically for this session, you can write the following line of ASP script:

 s_fido.WoofWoof
Note that each Web client will have its own private server-side Dog object, while all Web clients will share access to the server-side Cat.
      Most of the interesting bits of an ASP application do not happen in a global.asa, but in individual ASP files that clients browse to within their Web browsers. ASP pages are standard HTML-based Web pages that also contain server-side scripts and objects. To embed a server-side script into an ASP page, you can either use the RunAt attribute

 <script RunAt=Server>
     g_morris.MateWith s_fido
 </script>
or the <% and %> delimiters:

 <%
     g_morris.MateWith s_fido
 %>
Objects that are specific to an ASP page can be created using <object> tags

 <object RunAt=Server
     id=polly 
     progid="Animals.Parrot">
 </object>
or by dynamically using the intrinsic ASP Server object from an ASP script:

 <%
     Dim porky
     Set porky = Server.CreateObject("Animals.Pig")
 %>
Technically, you can create objects using the VBScript intrinsic CreateObject, but that's considered poor style.
      It is also possible (at least in theory) to associate objects dynamically with the current session or application.

 <%
     Dim y
     Set y = Server.CreateObject("Animals.Cow")
     Set session("bessie") = y
     Set y = Server.CreateObject("Animals.Horse")
     Set application("ed") = y
 %>
Once associated with the session or application objects, these references are visible across multiple invocations of the ASP pages in an application:

 <%
 rem use the session-wide cow
     session("bessie").Moo 
 rem use the application-wide horse
     application("ed").Neigh
 %>
ASP requires the client to consent to using HTTP cookies in order to associate the session object with the appropriate client request. Web applications can disable session management either on an application or page-wide basis.

ASP Script Elements and COM Threading
      How does all of this relate to COM threading? First, ASP is just another COM client. It does not play threading tricks on your object, but rather plays by the rules of COM (for the most part). As shown in Figure 1, HTTP requests arrive at the INETINFO.EXE process where they are dispatched to the ASP ISAPI extension. Inside this ISAPI extension, the requests are dispatched to a pool of ASP-managed threads that each execute inside private COM single-threaded apartments (STA). Technically, these threads/apartments are managed by the Microsoft Transaction Server (MTS) runtime, as your ASP scripts are processed inside an MTS method invocation.

Figure 1 ASP and Threads
Figure 1 ASP and Threads

      If session state is disabled for the application (by using the IIS administration tools) or the particular ASP page (by using the @EnableSessionState directive), IIS makes no attempt to serialize requests from a single Web client. Each sessionless request is simply queued on a random thread in the pool where each of the embedded scripts will be processed sequentially as the resulting HTML is shipped back to the client. It is illegal to access the session object (or any global.asa objects with session scope) from a sessionless ASP page. If a single client has several open browser windows to a sessionless ASP page, it is possible that multiple refreshes from the client will execute concurrently.
      If session state is enabled (the default setting), IIS serializes concurrent requests within a single ASP session. For this reason, the intrinsic session object does not have Lock and Unlock methods (as does the intrinsic application object). Suppose a single client has several browser windows open to a session-enabled ASP application. ASP ensures that any requests that are issued concurrently are processed sequentially. However, it is possible that multiple sessions (which implies multiple clients) can execute concurrently within an ASP application. This is why the intrinsic application object (which is visible to all sessions) has Lock and Unlock methods that allow ASP scripts to serialize updates to the shared state. Of course, holding the application object locked for any period of time can cause massive performance problems due to contention.
Figure 2 No Thread Affinity
Figure 2 No Thread Affinity

      By default, the pages within an ASP session do not have thread/apartment affinity. Hence, ASP requests from a particular client may be dispatched to different threads over the lifetime of the session. This affords the ASP plumbing flexibility in terms of dispatching requests to available or idle threads. To avoid having a single client consuming multiple server-side threads, the ASP runtimes keep track of which requests have been dispatched to each of the pooled threads. In particular, the ASP runtimes ensure that requests from a particular session never span more than one thread simultaneously. Thus, the situation shown in Figure 2 cannot happen. Instead, after dispatching a request from a given session, ASP detects subsequent requests from the same session and ensures that they are dispatched to the same thread queue, as shown in Figure 3.
      While IIS tries to keep a particular session on one thread while requests for the session are pending on the Web server, IIS does not normally try to keep a session on a particular thread/apartment across session idle times. If a particular client is constantly submitting requests in parallel within the same session, the requests will stick to the same thread. However, when the client stops to read a Web page, a different thread may process the next request. Therefore, the thread that was used to process the previous request may be busy processing a request from another user (in another session), but ASP may have other threads that are currently idle and more than happy to service the request.
      The Application_OnStart and Session_OnStart event handlers in a global.asa file are run on the same thread that processes the first page of the application/session. In this respect, they are logically prepended to the first ASP page in a particular session.
Figure 3 Thread Affinity
Figure 3 Thread Affinity

      IIS 4.0 allows ASP applications to be configured to run as in-process applications inside the Web server process (INETINFO.EXE) or as out-of-process applications in a separate MTS-managed process (MTX.EXE). The former offers somewhat better performance. The latter offers better fault isolation and debugging support. The INETINFO.EXE process runs as a Windows NT service under the Local System account. ASP scripts that execute in in-process ASP applications run as IUSR_MACHINENAME if authentication is not enabled. IIS-controlled MTX.EXE processes run as IWAM_MACHINENAME. ASP scripts in out-of-process ASP applications still run as the IUSR_MACHINENAME account if authentication is disabled. If authentication is enabled, ASP scripts always run with the Web client's credentials whether the ASP application is in-process or out-of-process.

Adding Server-side Objects
      So far my discussion of threading has only focused on processing ASP script elements. Adding server-side objects is a bit more complex. The easiest case to understand is objects that have page scope (for example, the Pig and Parrot objects from the earlier ASP fragments). Because ASP scripts always run on STA threads, in-process classes that are marked ThreadingModel=Both or ThreadingModel=Apartment will allow the object to be created directly on the thread that processes the ASP script. Since the object's methods are executed using the ASP thread, the method will execute using the credentials of the ASP script as described previously.
      If the in-process class is marked ThreadingModel=Free or has no ThreadingModel attribute, the object will be created on a COM-managed thread and the ASP script will get a proxy. This is not ASP magic—it's just how COM works. Because the object's methods will execute on a COM-managed thread within the process, the object's methods will execute using the credentials of the process (either the SYSTEM account for in-process ASP applications or the IWAM_MACHINENAME account for out-of-process ASP applications). Also, since a thread switch is needed for each method call, method invocation will be considerably slower.
      By default, ASP prohibits out-of-process activation via Server.CreateObject or <object> tags. You can change this by altering the AspAllowOutOfProcComponents property in the IIS 4.0 metabase. The following VBScript will perform this update on the local computer:


 Dim iis
 Set iis = GetObject("IIS://LocalHost/W3svc")
 iis.Put "AspAllowOutOfProcComponents", True
 iis.SetInfo
You can either put this into your Application_OnStart event handler or put it into a .VBS file and run it using Windows® Scripting Host.
      With this metabase setting in place, you can now create out-of-process objects using either Server.CreateObject or the <object> tag. Since the object will live in a distinct process, the ASP client always gets back a proxy, and the process will run with the credentials indicated in the registry for the particular server. If the server is not configured to run as a particular user (that is, if it has no RunAs entry), it will start using the process credentials of the ASP application (either LocalSystem or IWAM_MACHINENAME). Note that there is a bug in Windows NT 4.0 Service Pack 3 or earlier that sometimes causes the wrong credentials to be used. You can install a post-SP3 hot fix to ensure that IIS 4.0 security works properly.
      So, implementing objects that have page scope is easy. Simply create ThreadingModel=Apartment objects and everything is fine. This is so simple that it can even be done in Visual Basic. But what if you want to write a component that will be used at session scope? This is a bit more complex. As mentioned earlier, the thread used to process ASP pages submitted by a particular session may change when there are no pending requests. This lack of thread affinity allows IIS to map requests to idle threads as efficiently as possible.
      If, however, you were to instantiate an STA-based object using <object> tags with session scope, that object's methods would always need to be serviced on the thread that processed the <object> tag. To avoid consuming two or more threads per page, ASP notices that you have bound an STA-based object to a session and ensures that all subsequent requests are serviced on the original thread. This forces the session to exhibit thread affinity (which is also true of all STA-based objects). A similar problem occurs when explicitly storing a reference to an STA-based object as a session object property.
      To avoid pinning a session to a particular thread, session-scope objects should be implemented as apartment-neutral (the COM term) or agile (the ASP term). Apartment-neutral objects can be accessed freely from any apartment in the process. Under Windows NT 4.0, objects indicate their apartment neutrality by aggregating the Freethreaded Marshaler (FTM). When ASP tries to store an object reference as a session property, it interrogates the object to verify that it is apartment-neutral as follows:

 BOOL IsApartmentNeutral(IUnknown *pUnk) {
     extern CLSID CLSID_FreeThreadedMarshaler;
     BOOL bResult = FALSE;
     IMarshal *pMsh = 0;
     HRESULT hr = pUnk->QueryInterface(IID_IMarshal, 
                                       (void**)&pMsh);
     if (SUCCEEDED(hr)) {
         CLSID clsid;
         hr = pMsh->GetUnmarshalClass(IID_IUnknown,
                                      pUnk,
                                      MSHCTX_INPROC, 0, 
                                      MSHLFLAGS_NORMAL,
                                      &clsid);
         bResult = clsid == CLSID_FreeThreadedMarshaler;
         pMsh->Release();
     }
     return bResult;
 }
      This code fragment assumes that the variable CLSID_ FreeThreadedMarshaler has been initialized by calling GetUnmarshalClass on the FTM returned by CoCreateFreeThreadedMarshaler, shown in Figure 4. It would be poor style to simply hard code the FTM's CLSID, as it may change in future versions of the COM library. Also, simply aggregating the FTM will probably break your object. FTM-based objects must ensure that they are callable from any thread in the process. They shouldn't hold any apartment-relative object references and instead must use the Global Interface Table. (See my September 1997 column for more on apartment neutrality.)

Application-scoped Objects
      From the discussion so far, you may have concluded that plain vanilla ThreadingModel=Apartment classes can be used to create session-scoped objects, but overall performance will be greater if only apartment-neutral objects are used at session scope. What about application-scoped objects?
      Application-scoped objects are a different animal. ASP uses a dedicated thread to process the application-scoped <object> tags in a global.asa file. This thread is never used to run ASP pages to ensure that any STA-based application-scoped objects won't be pinned to a thread that is needed to process ASP pages. On my installation, this thread runs with the same credentials as the first ASP request, but a post-SP3 hot fix that addresses some of the security issues in IIS 4.0 may change this. Consult the MSDN Knowledge Base for more information.
      The previous paragraph implies that, by definition, ASP scripts will execute in a different apartment than that used to initialize the application-scoped <object> tags. If the object isn't apartment-neutral (that is, it doesn't use the FTM), ASP scripts will gain access to it via a proxy. Method invocation performance will be affected because a thread switch is needed to get from the proxy to the object. In addition, unlike page and session-scoped objects, the security credentials used to execute the script will not be used by the application-scoped object. To avoid these problems, application-scoped objects created with the <object> tag should be apartment-neutral. When this happens, ASP scripts will be given direct access to the object. This allows concurrent access to the object and allows the ASP script thread to directly invoke the object's methods without losing the script's security credentials.
      While it is possible (but not recommended) to create application-scoped objects that are apartment-relative using the <object> tag, you cannot store references to apartment-relative objects as properties of the ASP application object. When you try to do this, ASP tests the object for apartment neutrality; if it is not apartment-neutral, the assignment fails. If it were possible to store references to apartment-relative objects in the application object, the ASP Script thread that did the assignment would be needed to service all subsequent method invocations, potentially tampering with the ASP thread/page allocation.
      The exception to this limitation is that ASP will allow you to store proxies (which technically are apartment-relative) as application properties. In this case, ASP will use the Global Interface Table to ensure that scripts get properly marshaled object references.
      Given all of these somewhat confusing options, you should keep a few guidelines in mind when you are writing in-process components:
Page Scope ThreadingModel=Apartment is all that is needed to ensure you run on the thread of the ASP script. Don't bother with the FTM.
Session Scope Consider using the FTM to avoid pinning the session to a particular thread.
Application Scope Consider using the FTM to avoid forcing a thread switch at each method invocation.
Application or Session Scope Reconsider this type of implementation. Designs based on singletons are bad news in both MTS and IIS and should be revamped to use some other technique to achieve shared state—that is, transient per-page COM objects that store their shared state as flat properties in either application or session objects or somewhere more exotic, like SQL Server.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box: dbox@develop.com or http://www.develop.com/dbox/default.asp.

From the September 1998 issue of Microsoft Systems Journal.