text: /**********************************************************************/
  /**                       Microsoft Windows NT                       **/
  /**                Copyright(c) Microsoft Corp., 1998                **/
  /**********************************************************************/
  
  /*
      tlcach.cxx
  
      This module implements the private interface to the two-level cache.  This 
      cache is used to either cache the contents of a file or to copy the
      file and cache the handle to it
  
      FILE HISTORY:
          BAlam       10-31-1998      Created
  */
  
  #include "TsunamiP.Hxx"
  #pragma hdrstop
  
  // #define LOCAL_ALLOC 1
  #define PRIVATE_HEAP 1
  // #define VIRTUAL_ALLOC 1
  // #define LOOKASIDE 1
  
  #include "issched.hxx"
  #include "tlcach.h"
  
  //
  // Globals
  //
  
  // Memory Cache size statistics
  
  DWORDLONG                   g_cbMaxMemCacheSize;
  DWORDLONG                   g_cbMemCacheSize;
  DWORDLONG                   g_cbMaxMemCacheUsed;
  DWORD                       g_cMemCacheElements;
  DWORD                       g_cMaxMemCacheElementsUsed;
  
  // Cache utility
                              
  CRITICAL_SECTION            g_csMemCache;
  DWORD                       g_dwMemCacheSizeCookie;
  DWORD                       g_cmsecAdjustmentTime = DEFAULT_ADJUSTMENT_TIME;
  CHAR                        g_achTempPath[ MAX_PATH + 1 ];
  
  
  
  #if defined(LOCAL_ALLOC)
  
  const char                  g_szTsunamiAllocator[] = "LocalAlloc";
  
  #elif defined(PRIVATE_HEAP)
  
  const char                  g_szTsunamiAllocator[] = "PrivateHeap";
  
  HANDLE                      g_hMemCacheHeap = NULL;
  
  #elif defined(VIRTUAL_ALLOC)
  
  const char                  g_szTsunamiAllocator[] = "VirtualAlloc";
  
  #elif defined(LOOKASIDE)
  
  const char                  g_szTsunamiAllocator[] = "LookAside";
  
  enum {
      ENORMOUS = 300,
      MANY     = 200,
      LOTS     = 100,
      SOME     = 50,
      FEW      = 20,
      MINIMAL  = 4,
  
      KB = 1024,
  };
  
  ALLOC_CACHE_CONFIGURATION g_aacc[] = {
      { 1, SOME,          128},
      { 1, SOME,          256},
      { 1, SOME,          512},
      { 1, LOTS,          768},
      { 1, LOTS,       1 * KB},
      { 1, ENORMOUS,   2 * KB},
      { 1, ENORMOUS,   3 * KB},
      { 1, ENORMOUS,   4 * KB},
      { 1, MANY,       5 * KB},
      { 1, MANY,       6 * KB},
      { 1, MANY,       7 * KB},
      { 1, MANY,       8 * KB},
      { 1, LOTS,       9 * KB},
      { 1, LOTS,      10 * KB},
      { 1, LOTS,      11 * KB},
      { 1, LOTS,      12 * KB},
      { 1, LOTS,      14 * KB},
      { 1, SOME,      16 * KB},
      { 1, SOME,      20 * KB},
      { 1, SOME,      24 * KB},
      { 1, SOME,      28 * KB},
      { 1, SOME,      32 * KB},
      { 1, FEW,       36 * KB},
      { 1, FEW,       40 * KB},
      { 1, FEW,       44 * KB},
      { 1, FEW,       48 * KB},
      { 1, FEW,       56 * KB},
      { 1, MINIMAL,   64 * KB},
      { 1, MINIMAL,   80 * KB},
      { 1, MINIMAL,   96 * KB},
      { 1, MINIMAL,  128 * KB},
      { 1, MINIMAL,  160 * KB},
      { 1, MINIMAL,  192 * KB},
      { 1, MINIMAL,  256 * KB},
  };
  
  CLookAside* g_laMemCache = NULL;
  
  // paacc must be sorted in increasing sizes, it is sorted right now from
  // where it is called
  
  CLookAside::CLookAside(
      ALLOC_CACHE_CONFIGURATION* paacc,
      SIZE_T                     caacc)
      : m_dwSignature(SIGNATURE),
        m_apach(NULL),
        m_aacc(NULL),
        m_cach(0),
        m_nMinSize(0),
        m_nMaxSize(0)
  {
      ALLOC_CACHE_HANDLER** apach = new ALLOC_CACHE_HANDLER* [caacc];
      if (apach == NULL)
          return;
  
      ALLOC_CACHE_CONFIGURATION* paacc2 = new ALLOC_CACHE_CONFIGURATION [caacc];
      if (paacc2 == NULL)
          return;
      
      for (SIZE_T i = 0;  i < caacc;  ++i)
      {
          ALLOC_CACHE_CONFIGURATION acc = paacc[i];
          acc.cbSize -= HEAP_PREFIX + HEAP_SUFFIX + ACACHE_OVERHEAD;
          paacc2[i] = acc;
  
          if (i == 0)
              m_nMinSize = acc.cbSize;
          else if (i == caacc-1)
              m_nMaxSize = acc.cbSize;
  
          char szName[40];
          sprintf(szName, "TsLookAside-%d", acc.cbSize);
          
          apach[i] = new ALLOC_CACHE_HANDLER(szName, &acc);
          bool fInOrder = (i == 0  ||  paacc2[i].cbSize > paacc2[i-1].cbSize);
  
          if (!fInOrder)
          {
              DBGPRINTF((DBG_CONTEXT, "CLookAside: config array out of order\n"));
          }
  
          if (apach[i] == NULL  ||  !fInOrder)
          {
              for (SIZE_T j = i;  j-- > 0; )
              {
                  delete apach[j];
              }
              m_nMinSize = m_nMaxSize = 0;
              return;
          }
      }
  
      m_apach = apach;
      m_aacc  = paacc2;
      m_cach  = caacc;
  }
  
  
  
  CLookAside::~CLookAside()
  {
      for (SIZE_T j = m_cach;  j-- > 0; )
      {
          delete m_apach[j];
      }
  
      delete [] m_apach;
      delete [] m_aacc;
  
      m_dwSignature = SIGNATURE_X;
  }
  
  
  
  int
  CLookAside::_FindAllocator(
      IN DWORD cbSize)
  {
      if (cbSize > m_nMaxSize)
          return -1;  // too big to cache
      else if (cbSize <= m_nMinSize)
          return 0;
  
      int l = 0, h = m_cach-1;
  
      do
      {
          DBG_ASSERT(m_aacc[l].cbSize < cbSize  &&  cbSize <= m_aacc[h].cbSize);
  
          unsigned m = (unsigned) (l + h) >> 1;
          DBG_ASSERT(m > 0);
  
          if (m_aacc[m-1].cbSize < cbSize  &&  cbSize <= m_aacc[m].cbSize)
              return m;
          else if (m_aacc[m].cbSize < cbSize)
              l = m+1;
          else
              h = m-1;
      } while (l <= h);
  
      DBG_ASSERT(FALSE);
      return -1;
  }
  
  
  
  LPVOID
  CLookAside::Alloc(
      IN DWORD cbSize)
  {
      LPVOID pv = NULL;
      int iAllocator = _FindAllocator(cbSize);
  
      if (iAllocator < 0)
      {
          pv = VirtualAlloc(NULL, cbSize, MEM_COMMIT, PAGE_READWRITE);
      }
      else
      {
          DBG_ASSERT(iAllocator < m_cach && cbSize <= m_aacc[iAllocator].cbSize);
          pv = m_apach[iAllocator]->Alloc();
      }
  
      return pv;
  }
  
  
  
  BOOL
  CLookAside::Free(
      IN LPVOID pv,
      IN DWORD cbSize)
  {
      int iAllocator = _FindAllocator(cbSize);
  
      if (iAllocator < 0)
      {
          VirtualFree(pv, 0, MEM_RELEASE);
      }
      else
      {
          DBG_ASSERT(iAllocator < m_cach && cbSize <= m_aacc[iAllocator].cbSize);
          m_apach[iAllocator]->Free(pv);
      }
  
      return TRUE;
  }
  
  
  #endif // LOCAL_ALLOC
  
  //
  // Defines
  //
  
  #define MemCacheLock()      ( EnterCriticalSection( &g_csMemCache ) )
  #define MemCacheUnlock()    ( LeaveCriticalSection( &g_csMemCache ) )
  
  //
  // Private declarations
  //
  
  VOID
  WINAPI
  I_MemoryCacheSizeAdjustor(
      PVOID       pContext
  );
  
  //
  // Global functions
  //
  
  DWORD
  InitializeTwoLevelCache( 
      IN DWORDLONG            cbMemoryCacheSize
  )
  /*++
  Routine Description:
  
      Initialize memory cache
  
  Arguments:
  
      cbMemoryCacheSize - Size of memory cache (in bytes).  
      
  Return Value:
  
      ERROR_SUCCESS if successful, else Win32 Error
  
  --*/
  {
      DWORD                   dwError = ERROR_SUCCESS;
  
      if (DisableTsunamiCaching)
          return dwError;
      
      INITIALIZE_CRITICAL_SECTION( &g_csMemCache );
  
      if ( cbMemoryCacheSize == (DWORDLONG)-1 )
      {
          MEMORYSTATUSEX          MemoryStatus;
          MemoryStatus.dwLength = sizeof MemoryStatus;
      
          // 
          // Get our own estimate of size of cache
          //
          
          GlobalMemoryStatusEx( &MemoryStatus );
          
          g_cbMaxMemCacheSize = min( MemoryStatus.ullAvailPhys,
                                     MemoryStatus.ullTotalVirtual ) / 2;
          
          //
          // Schedule a max cache size adjustor
          //
          
          g_dwMemCacheSizeCookie = ScheduleWorkItem( I_MemoryCacheSizeAdjustor,
                                                     NULL,
                                                     g_cmsecAdjustmentTime,
                                                     TRUE );
          if ( !g_dwMemCacheSizeCookie )
          {
              dwError = GetLastError();
          }
      }
      else
      {
          g_cbMaxMemCacheSize = cbMemoryCacheSize;
      }
  
      if ( dwError == ERROR_SUCCESS )
      {
  #if defined(LOCAL_ALLOC)
          // no initialization needed
  #elif defined(PRIVATE_HEAP)
          g_hMemCacheHeap = HeapCreate( 0, 0, 0 );
          if (g_hMemCacheHeap == NULL)
              dwError = ERROR_NOT_ENOUGH_MEMORY;
  #elif defined(VIRTUAL_ALLOC)
          // no initialization needed
  #elif defined(LOOKASIDE)
          g_laMemCache = new CLookAside(g_aacc,
                                        sizeof(g_aacc)/sizeof(g_aacc[0]));
          if (g_laMemCache == NULL)
              dwError = ERROR_NOT_ENOUGH_MEMORY;
  #endif // LOCAL_ALLOC
      }
  
      if ( dwError != ERROR_SUCCESS )
      {
          TerminateTwoLevelCache();
      }
      return dwError;
  }
  
  DWORD
  ReadFileIntoMemoryCache( 
      IN HANDLE               hFile,
      IN DWORD                cbFile,
      OUT DWORD *             pcbRequired,
      OUT VOID **             ppvBuffer
  )
  /*++
  Routine Description:
  
      Read contents of file into a buffer
  
  Arguments:
  
      hFile - Handle to valid file
      cbFile - Size of file ( ==> size of buffer )
      pcbRequired - Filled in with number of bytes required to be removed from
                    cache to fit element
      ppvBuffer - Filled in with pointer to buffer with file contents.  Set
                  to NULL on failure
  
  Return Value:
  
      ERROR_SUCCESS if successful, else Win32 Error
  
  --*/
  {
      BOOL                    bRet;
      VOID *                  pvBuffer = NULL;
      DWORD                   cbRead;
      OVERLAPPED              Overlapped;
      DWORD                   dwError = ERROR_SUCCESS;
  
      DBG_ASSERT( hFile && ( hFile != INVALID_HANDLE_VALUE ) );
      DBG_ASSERT( pcbRequired != NULL );
      DBG_ASSERT( ppvBuffer != NULL );
  
      *pcbRequired = 0;
  
      //
      // First check whether there will be room in cache for the blob 
      //
  
      MemCacheLock();
      
      if ( ( g_cbMemCacheSize + cbFile ) > g_cbMaxMemCacheSize ) 
      {
          // 
          // Not enough room for cache
          //
          
          MemCacheUnlock();
          *pcbRequired = DIFF(( g_cbMemCacheSize + cbFile ) - g_cbMaxMemCacheSize);
          dwError = ERROR_NOT_ENOUGH_MEMORY;
          goto Finished;
      }
      
      g_cbMemCacheSize += cbFile;
      g_cbMaxMemCacheUsed = max( g_cbMaxMemCacheUsed, g_cbMemCacheSize );
      g_cMemCacheElements++;
      g_cMaxMemCacheElementsUsed = max( g_cMaxMemCacheElementsUsed, g_cMemCacheElements );
      
      MemCacheUnlock();
  
      *pcbRequired = 0;
  
      //
      // Allocate blob for file
      //
      
  #if defined(LOCAL_ALLOC)
      pvBuffer = LocalAlloc( LMEM_FIXED, cbFile );
  #elif defined(PRIVATE_HEAP)
      DBG_ASSERT(g_hMemCacheHeap != NULL);
      pvBuffer = HeapAlloc( g_hMemCacheHeap, 0, cbFile );
  #elif defined(VIRTUAL_ALLOC)
      pvBuffer = VirtualAlloc(NULL, cbFile, MEM_COMMIT, PAGE_READWRITE);
  #elif defined(LOOKASIDE)
      pvBuffer = g_laMemCache->Alloc(cbFile);
  #endif // LOCAL_ALLOC
  
      if ( pvBuffer == NULL )
      {
          MemCacheLock();
          
          g_cbMemCacheSize -= cbFile;
          
          MemCacheUnlock();
  
          dwError = ERROR_NOT_ENOUGH_MEMORY;
          goto Finished;
      }
      
      //
      // Read file into blob
      //
      
      Overlapped.Offset = 0;
      Overlapped.OffsetHigh = 0;
      Overlapped.hEvent = NULL;
      
      bRet = ReadFile( hFile,
                       pvBuffer,
                       cbFile,
                       &cbRead,
                       &Overlapped );
  
      if ( !bRet )
      {
          dwError = GetLastError();
          if ( dwError != ERROR_IO_PENDING )
          {
              // 
              // Something bad happened
              //
              
              goto Finished;
          }
          else
          {
              //
              // Reset the error lest we confuse ourselves later on cleanup
              //
              
              dwError = ERROR_SUCCESS;
          
              //
              // Wait for async read to complete
              //
                             
              bRet = GetOverlappedResult( hFile,
                                          &Overlapped,
                                          &cbRead,
                                          TRUE );
              if ( !bRet )
              {
                  //
                  // Something bad happened
                  //
                  
                  dwError = GetLastError();
                  
                  goto Finished;
              }
          }
      }
  
      //
      // Ensure that we read the number of bytes we expected to
      //
      
      if ( cbRead != cbFile )
      {
          dwError = ERROR_INVALID_DATA;
      }
      
  Finished:
      
      if ( dwError != ERROR_SUCCESS )
      {
          if ( pvBuffer != NULL )
          {
  #if defined(LOCAL_ALLOC)
              LocalFree( pvBuffer );
  #elif defined(PRIVATE_HEAP)
              HeapFree( g_hMemCacheHeap, 0, pvBuffer );
  #elif defined(VIRTUAL_ALLOC)
              VirtualFree( pvBuffer, 0, MEM_RELEASE );
  #elif defined(LOOKASIDE)
              g_laMemCache->Free(pvBuffer, cbFile);
  #endif // LOCAL_ALLOC
  
              pvBuffer = NULL;
          }
      }
      
      *ppvBuffer = pvBuffer;
  
      return dwError;
  }
  
  DWORD
  ReleaseFromMemoryCache(
      IN VOID *                  pvBuffer,
      IN DWORD                   cbBuffer
  )
  /*++
  Routine Description:
  
      Release file content blob from cache
  
  Arguments:
  
      pvBuffer - Buffer to release
      cbBuffer - Size of buffer
  
  Return Value:
  
      ERROR_SUCCESS if successful, else Win32 Error
  
  --*/
  {
      DBG_ASSERT( pvBuffer );
  #if defined(LOCAL_ALLOC)
      LocalFree( pvBuffer );
  #elif defined(PRIVATE_HEAP)
      DBG_ASSERT(g_hMemCacheHeap != NULL);
  
      HeapFree( g_hMemCacheHeap, 0, pvBuffer );
  #elif defined(VIRTUAL_ALLOC)
      VirtualFree( pvBuffer, 0, MEM_RELEASE );
  #elif defined(LOOKASIDE)
      g_laMemCache->Free(pvBuffer, cbBuffer);
  #endif // LOCAL_ALLOC
      
      MemCacheLock();
      
      g_cbMemCacheSize -= cbBuffer;
      g_cMemCacheElements--;
      
      MemCacheUnlock();
      
      return ERROR_SUCCESS;
  }
  
  DWORD
  TerminateTwoLevelCache(
      VOID
  )
  /*++
  Routine Description:
  
      Terminate the memory cache
  
  Arguments:
  
      None
  
  Return Value:
  
      ERROR_SUCCESS if successful
      ERROR_INVALID_DATA if still elements in cache 
  
  --*/
  {
      if (DisableTsunamiCaching)
          return ERROR_SUCCESS;
      
  #if defined(LOCAL_ALLOC)
      // no cleanup
  #elif defined(PRIVATE_HEAP)
      if (g_hMemCacheHeap != NULL)
      {
          DBG_REQUIRE( HeapDestroy( g_hMemCacheHeap ) );
          g_hMemCacheHeap = NULL;
      }
  #elif defined(VIRTUAL_ALLOC)
      // no cleanup
  #elif defined(LOOKASIDE)
      delete g_laMemCache;
      g_laMemCache = NULL;
  #endif // LOCAL_ALLOC
  
      if ( g_dwMemCacheSizeCookie != 0 )
      {
          RemoveWorkItem( g_dwMemCacheSizeCookie );
          g_dwMemCacheSizeCookie = 0;
      }
  
      DeleteCriticalSection( &g_csMemCache );
      
      return ( g_cbMemCacheSize ) ? 
              ERROR_INVALID_DATA : ERROR_SUCCESS;
  }
  
  VOID
  WINAPI
  I_MemoryCacheSizeAdjustor(
      IN PVOID                pContext
  )
  /*++
  
  Routine Description:
  
      Called to adjust the maximum size of the memory cache
  
  Arguments:
  
      pContext - Context (set to NULL)
  
  Return value:
  
      None    
  
  --*/
  {
      MEMORYSTATUSEX              MemoryStatus;
      MemoryStatus.dwLength = sizeof MemoryStatus;
  
      GlobalMemoryStatusEx( &MemoryStatus );
  
      MemCacheLock();
  
      g_cbMaxMemCacheSize = min( MemoryStatus.ullAvailPhys + g_cbMemCacheSize,
                                 MemoryStatus.ullTotalVirtual ) / 2;
  
      MemCacheUnlock();
  }
  
  DWORD
  DumpMemoryCacheToHtml(
      IN CHAR *                   pszBuffer,
      IN OUT DWORD *              pcbBuffer
  )
  /*++
  
  Routine Description:
  
      Dump memory cache stats to buffer
  
  Arguments:
  
      pszBuffer - buffer to fill
      pcbBuffer - size of buffer
  
  Return value:
  
      ERROR_SUCCESS if successful, else Win32 Error
  
  --*/
  {
      *pcbBuffer = wsprintf( pszBuffer,
          "<table>"
          "<tr><td>Current memory cache size</td><td align=right>%I64d</td></tr>"
          "<tr><td>Current memory cache limit</td><td align=right>%I64d</td></tr>"
          "<tr><td>Number of items in memory cache</td><td align=right>%d</td></tr>"
          "<tr><td>Peak memory cache size</td><td align=right>%I64d</td></tr>"
          "<tr><td>Peak memory cache element count</td><td align=right>%d</td></tr>"
          "</table>",
          g_cbMemCacheSize,
          g_cbMaxMemCacheSize,
          g_cMemCacheElements,
          g_cbMaxMemCacheUsed,
          g_cMaxMemCacheElementsUsed 
      );
  
      return TRUE;
      
  }
  
  VOID
  QueryMemoryCacheStatistics(
      IN  INETA_CACHE_STATISTICS * pCacheCtrs,
      IN  BOOL                     fClearAll
  )
  /*++
  
  Routine Description:
  
      Query memory cache perfmon counters
  
  Arguments:
  
      pCacheCtrs - Relevant members of stat structure are filled in
      fClearAll - Clear the counters
  
  Return value:
  
      ERROR_SUCCESS if successful, else Win32 Error
  
  --*/
  {
      DBG_ASSERT( pCacheCtrs );
      
      if ( fClearAll )
      {
          pCacheCtrs->CurrentFileCacheSize = 0;
          pCacheCtrs->MaximumFileCacheSize = 0;
      }
      else
      {
          pCacheCtrs->CurrentFileCacheSize = g_cbMemCacheSize;
          pCacheCtrs->MaximumFileCacheSize = g_cbMaxMemCacheUsed;
      }
  }
  