When to Use Caching


You can use caching whenever you have the need to store long-lived, static (or relatively static) data. An application can also cache data that would be too expensive to retrieve or compute every time there s a need for it. By caching long-lived or expensive-to-compute data, an application can greatly enhance its performance.

The ATL Server framework makes use of various types of caching in its Web application request and Web service request processing. ATL Server makes use of the stencil cache to store preprocessed stencil files, thus avoiding the cost of parsing them every time they re needed. The framework also makes use of the DLL cache to manage the loading and unloading of request handler DLLs, which eliminates the cost of loading and unloading these DLLs every time they re needed. The file cache is also used to manage caching pages generated by Web requests . Page caching can be used whenever a page contains dynamic content that changes infrequently.

Caching Support Example

In this section you ll look at a basic example of using the ATL Server caching support by creating a Web application that uses the BLOB cache.

From the ATL Server Project Wizard, go to the Server Options tab and check the Blob cache option under the Additional support options section. This generates an ATL Server ISAPI DLL with a BLOB cache built in as an ISAPI service exposed through the IServiceProvider interface. It also generates commented-out skeleton code to access the BLOB cache from within the request handler. Listing 12-1 shows the program for the ISAPI extension.

Listing 12.1: BLOB Cache Extension
start example
 // CBlobCacheExtension - the ISAPI extension class  template <class ThreadPoolClass=CThreadPool<CIsapiWorker>,      class CStatClass=CNoRequestStats,      class HttpUserErrorTextProvider=CDefaultErrorProvider,      class WorkerThreadTraits=DefaultThreadTraits >  class CBlobCacheExtension :      public CIsapiExtension<ThreadPoolClass,          CStatClass,          HttpUserErrorTextProvider,          WorkerThreadTraits>  {  protected:      typedef CIsapiExtension<ThreadPoolClass,          CStatClass, HttpUserErrorTextProvider,          WorkerThreadTraits> baseISAPI;      typedef CWorkerThread<WorkerThreadTraits> WorkerThreadClass;      // blob cache support      CBlobCache<WorkerThreadClass, CStdStatClass > m_BlobCache;  public:      BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer)      {          if (!baseISAPI::GetExtensionVersion(pVer))          {              return FALSE;          }          if (GetCriticalIsapiError() != 0)          {              return TRUE;          }          if (S_OK != m_BlobCache.Initialize(              static_cast<IServiceProvider*>(this), &m_WorkerThread))          {              ATLTRACE("Blob cache service failed to initialize\n");              TerminateExtension(0);              return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_BLOBCACHEFAILED);          }          return TRUE;      }      BOOL TerminateExtension(DWORD dwFlags)      {          m_BlobCache.Uninitialize();          BOOL bRet = baseISAPI::TerminateExtension(dwFlags);          return bRet;      }      HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService,              REFIID riid, void** ppvObject)      {          if (InlineIsEqualGUID(guidService, IID_IMemoryCache))              return m_BlobCache.QueryInterface(riid, ppvObject);          return baseISAPI::QueryService(guidService, riid, ppvObject);      }  }; // class CblobCacheExtension 
end example
 

You ll modify the code for the request handler to retrieve weather information from the cache (and cache it if it doesn t exist yet). Listing 12-2 presents the code for the memory cache client, which handles the freeing of data.

Listing 12.2: Weather Data Memory Cache Client
start example
 class CMemoryCacheClient :      public IMemoryCacheClient  {  public:      // IMemoryCacheClient      STDMETHOD(QueryInterface)(REFIID riid, void **ppv)      {          if (!ppv)              return E_POINTER;          if (InlineIsEqualGUID(riid, __uuidof(IUnknown))               InlineIsEqualGUID(riid, __uuidof(IMemoryCacheClient)))          {              *ppv = static_cast<IMemoryCacheClient*>(this);              return S_OK;          }          return E_NOINTERFACE;      }      STDMETHOD_(ULONG, AddRef)()      {          return 1;      }      STDMETHOD_(ULONG, Release)()      {          return 1;      }      STDMETHOD(Free)(const void *pData)      {          if (!pData)              return E_POINTER;          free(*((void **) pData));          return S_OK;      }  };  CMemoryCacheClient g_MemoryCacheClient; 
end example
 

Listing 12-3 shows the modified request handler code.

Listing 12.3: Modified Request Handler
start example
 [ request_handler("Default") ]  class CBlobCacheHandler  {  private:      // Put private members here      // uncomment the service declaration(s) if you want to use      // a service that was generated with your ISAPI extension      // Blob cache support      CComPtr<IMemoryCache> m_spBlobCache;      const char * m_szWeather;      DWORD m_dwSize;  protected:      // Put protected members here  public:      // Put public members here      HTTP_CODE ValidateAndExchange()      {          // TODO: Put all initialization and validation code here          // Set the content-type          m_HttpResponse.SetContentType("text/html");          // uncomment the service initialization(s) if you want to use          // a service that was generated with your ISAPI extension          // Get the IMemoryCache service from the ISAPI extension          if (FAILED(m_spServiceProvider->QueryService(__uuidof(IMemoryCache),                          &m_spBlobCache)))              return HTTP_FAIL;          HCACHEITEM hEntry = NULL;          // attempt to look up the entry          HRESULT hr = m_spBlobCache->LookupEntry("Weather_Today", &hEntry);          if (FAILED(hr)  !hEntry)          {              // attempt to add the entry (just make it static data)              static const char * const s_szWeather =                  "Sunny, 78 degrees F, No rain, Light wind";              size_t nLen = strlen(s_szWeather);              // allocate the data even though it's static, just to show how it would              // be done if it were being retrieved dynamically              char * szData = (char *)malloc(nLen+1);              if (!szData)                  return HTTP_ERROR(500, ISE_SUBERR_OUTOFMEM);              strcpy(szData, s_szWeather);              // create the expires time (just make it 24 hours from now)              CFileTime cftExpires = CFileTime::GetCurrentTime() + CFileTime::Day;              hr = m_spBlobCache->Add(                  "Weather_Today",        // the key for the data                  szData,                 // the data                  (DWORD)nLen+1,            // the size of the data                  &cftExpires,            // the expiration time of the data  // the DLL of the handler where the data lives (so the DLL will not be unloaded)                  m_hInstHandler,  // the out parameter that points to the added data                  &hEntry,  // the IMemoryCacheClient that will be used to free the data                  &g_MemoryCacheClient);              if (FAILED(hr)  !hEntry)                  return HTTP_FAIL;          }          hr = m_spBlobCache->GetData(hEntry, (void **)&m_szWeather, &m_dwSize);          if (FAILED(hr)  !m_szWeather  m_dwSize == 0)              return HTTP_FAIL;          return HTTP_SUCCESS;      }  protected:      // Here's an example of how to use a replacement tag with the stencil processor      [ tag_name(name="Hello") ]      HTTP_CODE OnHello(void)      {          m_HttpResponse << "Hello World! <br>"                            "Today's weather is : ";          m_HttpResponse.WriteLen(m_szWeather, m_dwSize-1);          return HTTP_SUCCESS;      }  }; // class CBlobCacheHandler 
end example
 

You retrieve (or add and then retrieve) the weather information in the ValidateAndExchange function and then display it to the user as part of the OnHello tag method.

The CMemoryCacheClient class implements the IMemoryCacheClient interface, which frees data from the cache. In this instance you re just using malloc and free , but it s possible to do more complicated allocation schemes (as is used with CStencilCache , where the stencil frees itself upon removal from the cache). In this example, you just used static data, but of course you could retrieve the weather information from a database or through a Web service call.

Key Types and Data Types

In the preceding example, you used the BLOB cache, which always takes and stores a void* type as its entry data and a LPCSTR type as its entry key. But by using the lower-level caches, it s possible to customize the key types and data types to achieve better type-safety and ease of use by avoiding casting.

Let s modify the previous example by using the CMemoryCache directly to store the data in a more useful fashion. Again, you ll use the ATL Server Project Wizard with the Blob cache option checked on the Server Options tab. However, you ll modify the ISAPI extension to use a custom cache derived from CMemoryCache instead of using CBlobCache directly. You ll call the new cache CBlobCacheEx , and in it you ll store a CWeatherData struct that contains information about the weather, such as the temperature, rain conditions, and wind conditions. You ll also need to define a new IMemoryCacheEx interface because you ve changed the types of the key and the data. The key is now an integer that refers to the day of the week, and the data is now the CWeatherData struct. Listing 12-4 shows the program for the modified ISAPI extension.

Listing 12.4: Definition of CBlobCacheEx
start example
 enum WIND_CONDITION { wcNone = 0, wcLight, wcBreezy, wcHurricane };  enum RAIN_CONDITION { rcNone = 0, rcLight, rcShowers, rcTorrential };  struct CWeatherData  {      int nTemp;      WIND_CONDITION eWind;      RAIN_CONDITION eRain;  };  __interface ATL_NO_VTABLE __declspec(uuid("a69dda6f-59da-4ba3-84f8-38b8db65201c"))      IMemoryCacheEx : public IUnknown  {      // IMemoryCache Methods      STDMETHOD(Add)(int Key, CWeatherData* Data, DWORD dwSize,                  FILETIME *pftExpireTime,                  HINSTANCE hInstClient, HCACHEITEM *phEntry,                  IMemoryCacheClient *pClient);      STDMETHOD(LookupEntry)(int szKey, HCACHEITEM * phEntry);      STDMETHOD(GetData)(const HCACHEITEM hEntry, CWeatherData **ppData,          DWORD *pdwSize) const;      STDMETHOD(ReleaseEntry)(const HCACHEITEM hEntry);      STDMETHOD(RemoveEntry)(const HCACHEITEM hEntry);      STDMETHOD(RemoveEntryByKey)(int Key);      STDMETHOD(Flush)();  };  template <          class MonitorClass,          class StatClass=CStdStatClass,          class SyncObj=CComCriticalSection,          class FlushClass=COldFlusher,          class CullClass=CExpireCuller >  class CBlobCacheEx : public CMemoryCache<CWeatherData*,      StatClass, FlushClass, int,      CElementTraits<int>, SyncObj, CullClass>,      public IMemoryCacheEx,      public IMemoryCacheControl,      public IMemoryCacheStats,      public IWorkerThreadClient  {      typedef CMemoryCache<CWeatherData*, StatClass, FlushClass, int,          CElementTraits<int>, SyncObj, CullClass> cacheBase;      MonitorClass m_Monitor;  protected:      HANDLE m_hTimer;  public:      CBlobCacheEx() : m_hTimer(NULL)      {      }      HRESULT Initialize(IServiceProvider *pProv)      {          HRESULT hr = cacheBase::Initialize(pProv);          if (FAILED(hr))              return hr;          hr = m_Monitor.Initialize();          if (FAILED(hr))              return hr;          return m_Monitor.AddTimer(ATL_BLOB_CACHE_TIMEOUT,              static_cast<IWorkerThreadClient*>(this),              (DWORD_PTR) this, &m_hTimer);      }      template <class ThreadTraits>      HRESULT Initialize(IServiceProvider *pProv,          CWorkerThread<ThreadTraits> *pWorkerThread)      {          ATLASSERT(pWorkerThread);          HRESULT hr = cacheBase::Initialize(pProv);          if (FAILED(hr))              return hr;          hr = m_Monitor.Initialize(pWorkerThread);          if (FAILED(hr))              return hr;          return m_Monitor.AddTimer(ATL_BLOB_CACHE_TIMEOUT,              static_cast<IWorkerThreadClient*>(this),              (DWORD_PTR) this, &m_hTimer);      }      HRESULT Execute(DWORD_PTR dwParam, HANDLE /*hObject*/)      {          CBlobCacheEx* pCache = (CBlobCacheEx*)dwParam;          if (pCache)              pCache->Flush();          return S_OK;      }      HRESULT CloseHandle(HANDLE hObject)      {          ATLASSERT(m_hTimer == hObject);          m_hTimer = NULL;          ::CloseHandle(hObject);          return S_OK;      }      ~CBlobCacheEx()      {          if (m_hTimer)              m_Monitor.RemoveHandle(m_hTimer);      }      HRESULT Uninitialize()      {          if (m_hTimer)          {              m_Monitor.RemoveHandle(m_hTimer);              m_hTimer = NULL;          }          m_Monitor.Shutdown();          return cacheBase::Uninitialize();      }      // IUnknown methods      HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)      {          HRESULT hr = E_NOINTERFACE;          if (!ppv)              hr = E_POINTER;          else          {              if (InlineIsEqualGUID(riid, __uuidof(IUnknown))                   InlineIsEqualGUID(riid, __uuidof(IMemoryCacheEx)))              {                  *ppv = (IUnknown *) (IMemoryCacheEx *) this;                  AddRef();                  hr = S_OK;              }              if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheStats)))              {                  *ppv = (IUnknown *) (IMemoryCacheStats*)this;                  AddRef();                  hr = S_OK;              }              if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheControl)))              {                  *ppv = (IUnknown *) (IMemoryCacheControl*)this;                  AddRef();                  hr = S_OK;              }          }          return hr;      }      ULONG STDMETHODCALLTYPE AddRef()      {          return 1;      }      ULONG STDMETHODCALLTYPE Release()      {          return 1;      }      // IMemoryCache Methods      HRESULT STDMETHODCALLTYPE Add(int Key, CWeatherData *Data, DWORD dwSize,          FILETIME *pftExpireTime,          HINSTANCE hInstClient,          HCACHEITEM *phEntry,          IMemoryCacheClient *pClient)      {          HRESULT hr = E_FAIL;          //if it's a multithreaded cache monitor we'll let the monitor          //take care of cleaning up the cache so we don't overflow          // our configuration settings. if it's not a threaded cache monitor,          // we need to make sure we don't overflow the configuration settings          // by adding a new element          if (m_Monitor.GetThreadHandle()==NULL)          {              if (!cacheBase::CanAddEntry(dwSize))              {                  //flush the entries and check again to see if we                  // can add                  cacheBase::FlushEntries();                  if (!cacheBase::CanAddEntry(dwSize))                      return E_OUTOFMEMORY;              }          }          _ATLTRY          {              hr = cacheBase::AddEntry(Key, Data, dwSize,                  pftExpireTime, hInstClient, pClient, phEntry);              return hr;          }          _ATLCATCHALL()          {              return E_FAIL;          }      }      // omitted .- all other methods delegate to CacheBase or m_statObj  }; // CBlobCacheEx 
end example
 

Listing 12-5 shows the modified ISAPI extension that uses the newly defined cache.

Listing 12.5: Modified ISAPI Extension
start example
 // CMemoryCacheExtension - the ISAPI extension class  template <class ThreadPoolClass=CThreadPool<CIsapiWorker>,      class CStatClass=CNoRequestStats,      class HttpUserErrorTextProvider=CDefaultErrorProvider,      class WorkerThreadTraits=DefaultThreadTraits >  class CMemoryCacheExtension :      public CIsapiExtension<ThreadPoolClass,          CStatClass,          HttpUserErrorTextProvider,          WorkerThreadTraits>  {  protected:      typedef CIsapiExtension<ThreadPoolClass, CStatClass,          HttpUserErrorTextProvider,          WorkerThreadTraits> baseISAPI;      typedef CWorkerThread<WorkerThreadTraits> WorkerThreadClass;      // blob cache support      CBlobCacheEx<WorkerThreadClass, CStdStatClass > m_BlobCache;  public:      BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer)      {          if (!baseISAPI::GetExtensionVersion(pVer))          {              return FALSE;          }          if (GetCriticalIsapiError() != 0)          {              return TRUE;          }          if (S_OK != m_BlobCache.Initialize(              static_cast<IServiceProvider*>(this), &m_WorkerThread))          {              ATLTRACE("Blob cache service failed to initialize\n");              TerminateExtension(0);              return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_BLOBCACHEFAILED);          }          return TRUE;      }      BOOL TerminateExtension(DWORD dwFlags)      {          m_BlobCache.Uninitialize();          BOOL bRet = baseISAPI::TerminateExtension(dwFlags);          return bRet;      }      HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService,              REFIID riid, void** ppvObject)      {          if (InlineIsEqualGUID(guidService, __uuidof(IMemoryCacheEx)))              return m_BlobCache.QueryInterface(riid, ppvObject);          return baseISAPI::QueryService(guidService, riid, ppvObject);      }  }; // class CMemoryCacheExtension 
end example
 

Listing 12-5 defines the CMemoryCacheClient class, the CWeatherData struct, and the new CBlobCacheEx cache class. It also defines the IMemoryCacheEx interface and makes the appropriate modifications to the CIsapiExtension -derived class so that it uses CBlobCacheEx .

Now you ll modify the request handler to use this new cache. You ll need to QueryService for the IMemoryCacheEx interface instead of the IMemoryCache interface, and you ll need to retrieve and add the data using the CWeatherData struct and the integer that represents the day of the week. And, of course, you ll need to modify the way you display the data in the tag method. Listing 12-6 is the program for the modified request handler.

Listing 12.6: Modified Request Handler Using the New Cache
start example
 // MemoryCache.h : Defines the ATL Server request handler class  //  #pragma once  #include "..\memorycacheisapi\memorycacheextension.h"  [ request_handler("Default") ]  class CMemoryCacheHandler  {  private:      // Put private members here      // uncomment the service declaration(s) if you want to use      // a service that was generated with your ISAPI extension      // Blob cache support      CComPtr<IMemoryCacheEx> m_spBlobCache;      CWeatherData *m_pData;      DWORD m_dwSize;  protected:      // Put protected members here  public:      // Put public members here      HTTP_CODE ValidateAndExchange()      {          // TODO: Put all initialization and validation code here          // Set the content-type          m_HttpResponse.SetContentType("text/html");          // uncomment the service initialization(s) if you want to use          // a service that was generated with your ISAPI extension          // Get the IMemoryCacheEx service from the ISAPI extension          if (FAILED(m_spServiceProvider->QueryService(__uuidof(IMemoryCacheEx),                          &m_spBlobCache)))              return HTTP_FAIL;          SYSTEMTIME st;          GetSystemTime(&st);          HCACHEITEM hEntry = NULL;          // attempt to look up the entry          HRESULT hr = m_spBlobCache->LookupEntry(st.wDayOfWeek, &hEntry);          if (FAILED(hr)  !hEntry)          {              // attempt to add the entry (just make it static data)              // array of day-of-week to weather struct mappings              static const CWeatherData s_arrWeather[] =              {                  { 78, wcNone, rcShowers },                  { 81, wcLight, rcNone },                  { 65, wcBreezy, rcLight },                  { 85, wcHurricane, rcTorrential },                  { 72, wcLight, rcLight},                  { 60, wcNone, rcNone},                  { 100, wcBreezy, rcShowers }              };              // allocate the data even though it's static,              // just to show how it would              // be done if it were being retrieved dynamically              CWeatherData *pData = (CWeatherData *)malloc(sizeof(CWeatherData));              if (!pData)                  return HTTP_ERROR(500, ISE_SUBERR_OUTOFMEM);              *pData = s_arrWeather[st.wDayOfWeek];              // create the expires time (just make it 24 hours from now)              CFileTime cftExpires = CFileTime::GetCurrentTime() + CFileTime::Day;              hr = m_spBlobCache->Add(                  st.wDayOfWeek,                  pData,                  (DWORD)sizeof(CWeatherData),                  &cftExpires,                  m_hInstHandler,                  &hEntry,                  &g_MemoryCacheClient);              if (FAILED(hr)  !hEntry)                  return HTTP_FAIL;          }          hr = m_spBlobCache->GetData(hEntry, &m_pData, &m_dwSize);          if (FAILED(hr)  !m_pData m_dwSize == 0)              return HTTP_FAIL;          return HTTP_SUCCESS;      }  protected:      // Here is an example of how to use a replacement      // tag with the stencil processor      [ tag_name(name="Hello") ]      HTTP_CODE OnHello(void)      {          static const char * const s_szWind[] =              {"None", "Light", "Breezy", "Hurricane" };          static const char * const s_szRain[] =              {"None", "Light", "Showers", "Torrential" };          m_HttpResponse << "Hello World! <br>"                            "Today's weather is :<br>"                            "Wind: " << s_szWind[m_pData->eWind] << "<br>"                            "Rain: " << s_szRain[m_pData->eRain] << "<br>"                            "Temp: " << m_pData->nTemp;          return HTTP_SUCCESS;      }  }; // class CmemoryCacheHandler 
end example
 

Timeouts

There are various timeouts used for the different caches. These timeouts are generally handled using preprocessor macros that can be overridden by the user. You use the timeouts to determine when automatic flushing and culling of the cache will occur. Here are the preprocessor macros you use for each of the caches:

  • ATL_BLOB_CACHE_TIMEOUT : Timeout used by the BLOB cache for flushing and culling.

  • ATL_DLL_CACHE_TIMEOUT : Timeout used by the DLL cache for flushing.

  • ATL_FILE_CACHE_TIMEOUT : Timeout used by the file cache for flushing and culling.

  • ATL_STENCIL_CACHE_TIMEOUT : Timeout used by the stencil cache for flushing and culling.

  • ATL_STENCIL_CHECK_TIMEOUT : Timeout used by the stencil cache to check against the base SRF file from which it was parsed.

  • ATL_STENCIL_LIFESPAN : The lifespan of a stencil in the stencil cache, which is independent of other flushing and culling parameters. That is, when a stencil s lifespan has expired it will be removed, regardless of other factors.

Cache Parameters

The caches can be tuned in various ways. The BLOB cache has the following functions that allow tuning how large the cache can grow in terms of memory allocation and total entries (these functions are supported underneath by the CMemoryCacheBase class). Any CMemoryCacheBase -derived class can use these functions provided the correct parameters are passed in when entries are added to the cache for the size of the data.

  • SetMaxAllowedSize : This function sets the maximum allowable size of the data that s stored in the cache. If a cache reaches its limit while adding an entry, an attempt will be made to flush the cache before failing to ensure there are no stale entries taking up space.

  • GetMaxAllowedSize : This function retrieves the maximum allowable size of the data stored in the cache.

  • SetMaxAllowedEntries : This function sets the maximum number of entries in the cache (independent of the size of the entries). If a cache reaches its limit while adding an entry, an attempt will be made to flush the cache before failing to ensure there are no stale entries taking up space.

  • GetMaxAllowedEntries : This function retrieves the maximum allowable entry count for a cache.

Cache Statistics

The caches (with the exceptions of the DLL cache and the data source cache) support retrieving cache statistics such as cache hits and misses, current allocation size and entry count, and the maximum allocation size and entry count. These cache statistics can be integrated with Perfmon (ATL Server has built-in Perfmon support for the caches). You can turn these statistics on and off by using the StatClass template parameter for the caches that support them.

By default, all the caches use CStdStatClass , which maintains cache statistics for each of the caches but doesn t integrate with Perfmon. For Perfmon integration, the template parameter should be changed to CPerfStatClass . If no cache statistics are desired, you can use CNoStatClass to no-op the statistics operations in the caches. There s one important caveat: Due to a limitation/bug in the VC7 version of ATL Server, there can be only one cache statistics instance per module. That is, if you have a BLOB cache and a file cache, each using CPerfStatClass , you can t get independent cache statistics for these caches, because they ll both go through the same instance of CPerfStatObject . You ll need to define your own CPerfStatClass to get independent cache statistics. This is simply a matter of cutting and pasting, and then renaming CPerfStatClass for each independent cache.




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