Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any way to access the IIS kernel cache from ASP.NET?

This only clears items in the user cache:

    public static void ClearCache()
    {
        foreach (DictionaryEntry entry in HttpRuntime.Cache)
        {
            HttpRuntime.Cache.Remove(entry.Key.ToString());
        }
    }

Is there any way to access the kernel cache as well?

Clarification: I want to print the keys of all items in the kernel cache, and as a bonus I'd like to be able to clear the kernel cache from a C# method as well.

like image 335
Robert Claypool Avatar asked Aug 14 '09 05:08

Robert Claypool


People also ask

How do I cache in asp net?

To manually cache application data, you can use the MemoryCache class in ASP.NET. ASP.NET also supports output caching, which stores the generated output of pages, controls, and HTTP responses in memory. You can configure output caching declaratively in an ASP.NET Web page or by using settings in the Web. config file.

What is kernel mode caching?

Cache Kernel caches OS system objects such as threads, address spaces, and application kernels. The higher level application kernels provide the management functions for the application, managing thread priority and address spaces and reducing supervisor-level complexity.


1 Answers

Yep, it's possible to programmatically enumerate and remove items from IIS's kernel cache.

Caveats:

  • non-trivial text parsing requred for enumeration
  • lots of ugly P/Invoke required for removal
  • Also, you'll need at least Medium Trust (and probably Full Trust) to do the things below.
  • Removal won't work in IIS's integrated pipeline mode.
  • Enumeration probably won't work on IIS6

Enumeration:

The only documented way I know to enumerate the IIS kernel cache is a command-line app available in IIS7 and above (although you might be able to copy the NETSH helper DLL from V7 onto a V6 system-- haven't tried it).

netsh http show cachestate

See MSDN Documentation of the show cachestate command for more details. You could turn this into an "API" by executing the process and parsing the text results.

Big Caveat: I've never seen this command-line app actually return anything on my server, even for apps running in Classic mode. Not sure why-- but the app does work as I can see from other postings online. (e.g. http://chrison.net/ViewingTheKernelCache.aspx)

If you're horribly allergic to process creation and feeling ambitious, NETSH commands are implemented by DLL's with a documented Win32 interface, so you could write code which pretends it's NETSH.exe and calls into IIS's NETSH helper DLL directly. You can use the documentation on MSDN as a starting point for this approach. Warning: impersonating NETSH is non-trivially hard since the interface is 2-way: NETSH calls into the DLL and the DLL calls back into NETSH. And you'd still have to parse text output since the NETSH interface is text-based, not object-based like PowerShell or WMI. If it were me, I'd just spawn a NETSH process instead. ;-)

It's possible that the IIS7 PowerShell snapin may support this functionality in the future (meaning easier programmatic access than the hacks above), but AFAIK only NETSH supports this feature today.

Invalidation:

I've got good news and bad news for you.

The good news: Once you know the URL of the item you want to yank from IIS's kernel cache, there's a Win32 API available to remove it on IIS6 and above. This can be called from C# via P/Invoke (harder) or by putting the call in a managed C++ wrapper DLL. See HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK on MSDN for details.

I took a stab at the code required (attached below). Warning: it's ugly and untested-- it doesn't crash my IIS but (see above) I can't figure out how to get cache enumeration working so I can't actually call it with a valid URL to pull from the cache. If you can get enumeration working, then plugging in a valid URL (and hence testing this code) should be easy.

The bad news:

  • as you can guess from the code sample, it won't work on IIS7's integrated pipeline mode, only in Classic mode (or IIS6, of course) where ASP.NET runs as an ISAPI and has access to ISAPI functions
  • messing with private fields is a big hack and may break in a new version
  • P/Invoke is hard to deal with and requires (I believe) full trust

Here's some code:

using System;
using System.Web;
using System.Reflection;
using System.Runtime.InteropServices;

public partial class Test : System.Web.UI.Page
{
    /// Return Type: BOOL->int
    public delegate int GetServerVariable();

    /// Return Type: BOOL->int
    public delegate int WriteClient();

    /// Return Type: BOOL->int
    public delegate int ReadClient();

    /// Return Type: BOOL->int
    public delegate int ServerSupportFunction();

    /// Return Type: BOOL->int
    public delegate int EXTENSION_CONTROL_BLOCK_GetServerVariable();

    /// Return Type: BOOL->int
    public delegate int EXTENSION_CONTROL_BLOCK_WriteClient();

    /// Return Type: BOOL->int
    public delegate int EXTENSION_CONTROL_BLOCK_ReadClient();

    /// Return Type: BOOL->int
    public delegate int EXTENSION_CONTROL_BLOCK_ServerSupportFunction();

    public static readonly int HSE_LOG_BUFFER_LEN = 80;

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct EXTENSION_CONTROL_BLOCK
    {
        /// DWORD->unsigned int
        public uint cbSize;

        /// DWORD->unsigned int
        public uint dwVersion;

        /// DWORD->unsigned int
        public uint connID;

        /// DWORD->unsigned int
        public uint dwHttpStatusCode;

        /// CHAR[HSE_LOG_BUFFER_LEN]
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 80 /*HSE_LOG_BUFFER_LEN*/)]
        public string lpszLogData;

        /// LPSTR->CHAR*
        public System.IntPtr lpszMethod;

        /// LPSTR->CHAR*
        public System.IntPtr lpszQueryString;

        /// LPSTR->CHAR*
        public System.IntPtr lpszPathInfo;

        /// LPSTR->CHAR*
        public System.IntPtr lpszPathTranslated;

        /// DWORD->unsigned int
        public uint cbTotalBytes;

        /// DWORD->unsigned int
        public uint cbAvailable;

        /// LPBYTE->BYTE*
        public System.IntPtr lpbData;

        /// LPSTR->CHAR*
        public System.IntPtr lpszContentType;

        /// EXTENSION_CONTROL_BLOCK_GetServerVariable
        public EXTENSION_CONTROL_BLOCK_GetServerVariable GetServerVariable;

        /// EXTENSION_CONTROL_BLOCK_WriteClient
        public EXTENSION_CONTROL_BLOCK_WriteClient WriteClient;

        /// EXTENSION_CONTROL_BLOCK_ReadClient
        public EXTENSION_CONTROL_BLOCK_ReadClient ReadClient;

        /// EXTENSION_CONTROL_BLOCK_ServerSupportFunction
        // changed to specific signiature for invalidation callback
        public ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK ServerSupportFunction;
    }
    /// Return Type: BOOL->int
    ///ConnID: DWORD->unsigned int
    ///dwServerSupportFunction: DWORD->unsigned int
    ///lpvBuffer: LPVOID->void*
    ///lpdwSize: LPDWORD->DWORD*
    ///lpdwDataType: LPDWORD->DWORD*
    [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
    public delegate bool ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
        uint ConnID, 
        uint dwServerSupportFunction, // must be HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK
        out Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK lpvBuffer, 
        out uint lpdwSize, 
        out uint lpdwDataType);

    public readonly uint HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK = 1040;

    // typedef HRESULT (WINAPI * PFN_HSE_CACHE_INVALIDATION_CALLBACK)(WCHAR *pszUrl);
    [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
    public delegate bool Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
        [MarshalAs(UnmanagedType.LPWStr)]string url);

    object GetField (Type t, object o, string fieldName)
    {
        FieldInfo fld = t.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
        return fld == null ? null : fld.GetValue(o);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        // first, get the ECB from the ISAPIWorkerRequest
        var ctx = HttpContext.Current;
        HttpWorkerRequest wr = (HttpWorkerRequest) GetField(typeof(HttpContext), ctx, "_wr");
        IntPtr ecbPtr = IntPtr.Zero;
        for (var t = wr.GetType(); t != null && t != typeof(object); t = t.BaseType)
        {
            object o = GetField(t, wr, "_ecb");
            if (o != null)
            {
                ecbPtr = (IntPtr)o;
                break;
            }
        }

        // now call the ECB callback function to remove the item from cache
        if (ecbPtr != IntPtr.Zero)
        {
            EXTENSION_CONTROL_BLOCK ecb = (EXTENSION_CONTROL_BLOCK)Marshal.PtrToStructure(
                ecbPtr, typeof(EXTENSION_CONTROL_BLOCK));
            uint dummy1, dummy2;

            Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK invalidationCallback;
            ecb.ServerSupportFunction(ecb.connID,
                    HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK,
                    out invalidationCallback,
                    out dummy1,
                    out dummy2);

            bool success = invalidationCallback("/this/is/a/test");
        }
    }
}
like image 97
Justin Grant Avatar answered Nov 03 '22 12:11

Justin Grant