Built-in per-Thread ISAPI Services


You ve already seen how a job that will be frequently executed from various request handlers (such as incrementing a hit counter) can be stored in a service in the ISAPI application. Also, we ve suggested a way of solving thread contention issues (by using NT synchronization mechanisms). Now, if the job to be thread-safe is atomic (such as incrementing a hit counter), the Interlocked APIs will solve most of the problems. If the job isn t atomic, but you can reasonably assume that it will take a small number of CPU cycles to be executed, you can use more powerful APIs. ATL Server provides a set of wrappers for objects such as mutexes and critical sections.

What if the job to be executed is supposed to take a long time? Assume that your Web application fetches some information from a Web service and then displays that information in a human-friendly form on a Web page. Each request to your Web application would generate one or more requests to the Web service. All these requests will be executed through a proxy class (generated, of course, with sproxy).

Using per-Thread Services

Now, you could create a proxy class every time you have to send a request. This is pretty expensive: A proxy class has to CoCreateInstance for ISAXXMLReader , which is a time-consuming operation. So you need to somehow reuse one existing proxy object (or a limited number thereof).

This cache of proxy objects fits very well in an ISAPI service: All the request handlers will use them and they have to not be destroyed for the lifetime of the Web application. But when multiple threads (request handlers) attempt to use the proxy objects, you ll need a way to ensure that contention is avoided.

The jobs to be synchronized are taking a long time to complete. You could have a small pool of objects, and then spend time waiting for one of them to be free, or you could have a large number of objects, and then waste memory and resources while they aren t used.

The optimal number of pooled proxy objects for this scenario is exactly as many proxy objects as threads. This way, each thread will have a proxy available whenever necessary, and no memory will be wasted with extra proxies, but still some time will be wasted in finding the free proxy object when necessary for a given thread (although you know now for sure that there s at least one).

In the next section, you ll learn how you can expose the proxy objects in the preceding case as per-thread instances of ISAPI services and use them without any need for synchronization.

How to Implement the Actual Service

We start with a brief note on the ATL Server threading model: Each request is executed by a request handler in a single thread. The thread is part of the ISAPI thread pool. Each thread in the thread pool has an instance of a worker object. The worker object class is passed as a template parameter to the ISAPI extension class. The worker instance exists for the lifetime of the respective thread; therefore, it will be shared in a thread-safe way by all the requests to be executed on that thread.

You ll wrap the access to the proxy object in an ISAPI service, and then you ll have an instance of the ISAPI service living inside each worker object. Eventually, you ll modify the ISAPI extension so that QueryService , if invoked with the service ID of the proxy object, will return the one in the current thread.

The remote Web service to be accessed by all the Web application requests is available as the Services\PerThreadSvc\RemoteWeatherService sample. It provides the following interface:

 [id(1)] HRESULT GetCityList([out, retval] int*        pnSize,                              [out, size_is(*pnSize)] BSTR **arCityList);  [id(2)] HRESULT GetCityForecast([in] BSTR        bstrCity,                          [out, retval] double *dForecastTemperature); 

You wrap it into an ISAPI service defined as follows :

 // IPerThreadSvc.  __interface __declspec(uuid("5BC416A1-CBA5-4769-9DEB-75E673E229FF"))          IPerThreadWeatherSvc : public IUnknown  {          // Gets the list of cities          HRESULT GetCityList(int* pCount, BSTR**        arCities);          HRESULT GetCityForecast(BSTR bstrCity, double* pdRet);          HRESULT CleanupCityList(BSTR* arCities, int nCount);  }; 

This way, a request handler (like the sample one, Services\PerThreadSvc\ PerThreadSvc) can use it as soon as you make it available in the ISAPI extension.

How to Make a Service per-Thread

In the ISAPI extension, Services\PerThreadSvc\PerThreadSvcIsapi, you ll start by creating a class ( CPerThreadWeatherSvc in PerThreadSvcIsapi.cpp) that implements the IPerThreadWeatherSvc service interface. The class will contain a proxy client object for the weather Web service discussed previously. That proxy client object is based on the class generated by sproxy and is defined as follows:

 RemoteWeatherService::CRemoteWeatherService        *m_pSvc; 

The service implementation class also defines two methods , Initialize and Uninitialize , that will create and destroy the proxy object, respectively.

So far, you have an ISAPI service implementation that exposes the functionality of the remote weather Web service and can be used by the request handlers. Now you ll create an instance of this service implementation object in each worker, thus in each ISAPI worker thread. You ll do this by creating a new class, CExtendedIsapiWorker , that extends the existing CIsapiWorker class. This new class will contain a CPerThreadWeatherSvc object that implements the ISAPI service:

 class CExtendedIsapiWorker : public CIsapiWorker  {  public:          CPerThreadWeatherSvc                m_WeatherSvc; 

The base class ( CIsapiWorker ) provides two virtual methods, Initialize and Terminate , that are invoked by the working thread when it starts (the worker object has just been created) and ends (the worker object is about to be destroyed). You ll override these virtuals to invoke the CPerThreadWeatherSvc object s Initialize and Uninitialize methods.

The next steps are pretty straightforward:

  1. Declare an ISAPI extension derivative that uses the CExtendedIsapiWorker class:

     class CIsapiEx : public CIsapiExtension<CThreadPool<CExtendedIsapiWorker> > 
  2. Override the new ISAPI class s QueryService method to support the PerThreadWeather service:

     virtual HRESULT STDMETHODCALLTYPE QueryService(                  REFGUID guidService,                  REFIID riid,                  void **ppvObject)  {          if (!ppvObject)                  return E_POINTER;          if (InlineIsEqualGUID(guidService,                         __uuidof(IPerThreadWeatherSvc)))     {              CExtendedIsapiWorker *p =              (CExtendedIsapiWorker*)GetThreadWorker();             ATLASSERT( p != NULL );          return p->m_WeatherSvc.QueryInterface(riid, ppvObject);      }          // otherwise look it up in the base class          return __super::QueryService(guidService, riid, ppvObject);  } 
  3. Instantiate an ISAPI extension object from the newly defined class instead of the original CIsapiExtension . Replace

     ExtensionType theExtension; 
  4. with

     CIsapiEx theExtension; 

Now the application is ready to compile and work. Each request handler can access the service via the ISAPI QueryService interface, and the returned service is the instance dedicated to the current thread, which you can use without worrying about possible contention and without waiting for it to become available.

Optimization

The original ISAPI worker object, CIsapiWorker , contains some objects that may prove useful in your proxy class service: an ISAXXMLReader object and a heap to be used in thread-specific allocations . You can reuse these objects in the proxy object as well. As you can see in the actual sample code, they re passed from the worker object to the ISAPI service implementation via the Initialize method and then used to initialize the SOAP proxy class and to set the memory manager to be used by that proxy class.

This way, the thread will use a dedicated heap (eliminating heap contention), and a single ISAXXMLReader object (saving the cost of CoCreateInstance -ing a new one for the proxy).

Per-Thread ISAPI Services Conclusion

The per-thread ISAPI services provide a powerful mechanism for avoiding the cost of synchronization primitives. Although this mechanism requires some more development effort than a regular ISAPI service, the results are worth the effort.

This mechanism is used by the ATL Server framework to provide the following functionality (some of the functionality is optional and selectable in the ATL Server Project Wizard in Visual Studio):

  • The ISAXXMLReader service provided by all the ISAPI extensions (if the user doesn t choose to explicitly disable it by defining the ATL_NO_SOAP constant)

  • The optional database connection service, which provides a thread-dedicated database connection

  • The optional browser capabilities service




ATL Server. High Performance C++ on. NET
Observing the User Experience: A Practitioners Guide to User Research
ISBN: B006Z372QQ
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net