July 2005 - Posts

Preparing a little demo on SQL Server 2005 CLR showing the benifits of the CLR compared too the dangers of using extended procedures to extend the database's functionality. I decided to start Visual C++ 6.0 (:o), create a new project, choose the Extended Stored Procedure Wizard and call the project xpdemo. In the first step (which is the last step too) of the wizard, I'm giving the procedure the name xp_crash. Next, add two lines of code in the xp_crash method, as shown below:

#include <stdafx.h>

#define XP_NOERROR              0
#define XP_ERROR                1
#define MAXCOLNAME    25
#define MAXNAME     25
#define MAXTEXT     255

#ifdef __cplusplus
extern "C" {
#endif

RETCODE __declspec(dllexport) xp_crash(SRV_PROC *srvproc);

#ifdef __cplusplus
}
#endif

RETCODE __declspec(dllexport) xp_crash(SRV_PROC *srvproc)
{
 //crash me
 int *p = NULL;
 *p = 0;

 return XP_NOERROR ;
}

Ready to build. Next, the xpcopy.dll file is copied to the Binn folder of the SQL Server. Finally, go to Query Analyzer to register the extended procedure:

exec sp_addextendedproc 'xp_crash','xpdemo.dll'

Now when running the xp_crash extended procedure, SQL Server goes down, showing the danger of extending the database functionality with unmanaged code:

ODBC: Msg 0, Level 19, State 1
FCallRpcDll: Process 51 generated fatal exception c0000005 EXCEPTION_ACCESS_VIOLATION. SQL Server is terminating this process.
ODBC: Msg 0, Level 20, State 1
Stored function 'xp_crash' in the library 'xpdemo.dll' generated an access violation. SQL Server is terminating process 51.

Connection Broken

Now, when running SQL Server not as a service but as a process (just by starting sqlservr.exe in the binn folder when the service is not started), you'll get detailed information in the process window of SQL Server:

2005-07-24 01:55:31.64 spid51    Using 'xpdemo.dll' version 'UNKNOWN' to execute
 extended stored procedure 'xp_crash'.
2005-07-24 01:55:31.78 spid51    Using 'dbghelp.dll' version '4.0.5'
*Stack Dump being sent to C:\Program Files\Microsoft SQL Server\MSSQL\log\SQLDum
p0001.txt
2005-07-24 01:55:31.82 spid51    Error: 0, Severity: 19, State: 0
2005-07-24 01:55:31.82 spid51    FCallRpcDll: Process 51 generated fatal excepti
on c0000005 EXCEPTION_ACCESS_VIOLATION. SQL Server is terminating this process..

* ******************************************************************************
*
*
* BEGIN STACK DUMP:
*   07/24/05 01:55:31 spid 51
*
*   Exception Address = 04231072 (xp_crash + 00000022 Line 23+00000003)
*   Exception Code    = c0000005 EXCEPTION_ACCESS_VIOLATION
*   Access Violation occurred writing address 00000000
* Input Buffer 30 bytes -
*  exec xp_crash
*
*
*  MODULE                          BASE      END       SIZE
* sqlservr                       00400000  00CBAFFF  008bb000
* ntdll                          7C800000  7C8BFFFF  000c0000
* kernel32                       77E40000  77F41FFF  00102000
* ADVAPI32                       77F50000  77FEBFFF  0009c000
* RPCRT4                         77C50000  77CEEFFF  0009f000
* MSVCP71                        7C3A0000  7C41AFFF  0007b000
* MSVCR71                        7C340000  7C395FFF  00056000
* opends60                       41060000  41065FFF  00006000
* SHELL32                        7C8D0000  7D0D2FFF  00803000
* GDI32                          77C00000  77C47FFF  00048000
* USER32                         77380000  77411FFF  00092000
* msvcrt                         77BA0000  77BF9FFF  0005a000
* SHLWAPI                        77DA0000  77DF1FFF  00052000
* sqlsort                        42AE0000  42B6FFFF  00090000
* ums                            41070000  4107DFFF  0000e000
* comctl32                       77420000  77522FFF  00103000
* sqlevn70                       41080000  4108AFFF  0000b000
* NETAPI32                       02100000  02157FFF  00058000
* AUTHZ                          02160000  02173FFF  00014000
* COMRES                         02410000  024D5FFF  000c6000
* ole32                          77670000  777A3FFF  00134000
* XOLEHLP                        024E0000  024E5FFF  00006000
* MSDTCPRX                       024F0000  02567FFF  00078000
* msvcp60                        780C0000  78120FFF  00061000
* MTXCLU                         02570000  02588FFF  00019000
* VERSION                        77B90000  77B97FFF  00008000
* WSOCK32                        02590000  02598FFF  00009000
* WS2_32                         025A0000  025B6FFF  00017000
* WS2HELP                        025C0000  025C7FFF  00008000
* OLEAUT32                       77D00000  77D8BFFF  0008c000
* CLUSAPI                        025D0000  025E1FFF  00012000
* RESUTILS                       025F0000  02602FFF  00013000
* USERENV                        02610000  026D3FFF  000c4000
* secur32                        026E0000  026F2FFF  00013000
* mswsock                        02710000  02750FFF  00041000
* DNSAPI                         02760000  02788FFF  00029000
* winrnr                         027D0000  027D6FFF  00007000
* WLDAP32                        027E0000  0280DFFF  0002e000
* rasadhlp                       02830000  02834FFF  00005000
* SSNETLIB                       03000000  03015FFF  00016000
* NTMARTA                        77E00000  77E21FFF  00022000
* SAMLIB                         00030000  0003EFFF  0000f000
* security                       034F0000  034F3FFF  00004000
* crypt32                        03500000  03592FFF  00093000
* MSASN1                         03640000  03651FFF  00012000
* schannel                       03660000  03686FFF  00027000
* rsaenh                         036A0000  036CEFFF  0002f000
* PSAPI                          03750000  0375AFFF  0000b000
* dssenh                         03760000  03783FFF  00024000
* hnetcfg                        037B0000  03808FFF  00059000
* wshtcpip                       03810000  03817FFF  00008000
* wship6                         03820000  03826FFF  00007000
* SSmsLPCn                       038B0000  038B7FFF  00008000
* SSnmPN70                       410D0000  410D6FFF  00007000
* ntdsapi                        03970000  03984FFF  00015000
* msv1_0                         03990000  039B6FFF  00027000
* iphlpapi                       039E0000  039F9FFF  0001a000
* SQLFTQRY                       41020000  41045FFF  00026000
* xpsp2res                       10000000  102C4FFF  002c5000
* CLBCatQ                        777B0000  77832FFF  00083000
* SQLOLEDB                       03440000  034C0FFF  00081000
* MSDART                         034D0000  034E9FFF  0001a000
* MSDATL3                        03D70000  03D84FFF  00015000
* oledb32                        03F90000  04008FFF  00079000
* OLEDB32R                       04010000  04020FFF  00011000
* xpdemo                         04230000  04267FFF  00038000
* dbghelp                        04280000  0437FFFF  00100000
*
*        Edi: 0412DD20: 0412EAC8  00792893  42C020C0  635F7078  68736172  0412DD
00
*        Esi: 00000001:
*        Eax: 00000000:
*        Ebx: 42C020C0: 42C025B8  00000000  00000002  00040308  42C02038  000000
00
*        Ecx: 00000000:
*        Edx: 00000000:
*        Eip: 04231072: 000000C7  C0330000  8B5B5E5F  CCC35DE5  CCCCCCCC  CCCCCC
CC
*        Ebp: 0412DD20: 0412EAC8  00792893  42C020C0  635F7078  68736172  0412DD
00
*      SegCs: 0000001B:
*     EFlags: 00010212: 00720065  0063005C  0075006C  00740073  00720065  006C00
2E
*        Esp: 0412DCD0: 0423100A  00000001  42C020C0  CCCCCCCC  CCCCCCCC  CCCCCC
CC
*      SegSs: 00000023:
* ******************************************************************************
*
* ------------------------------------------------------------------------------
-
* Short Stack Dump
* 04231072 Module(xpdemo+00001072) (xp_crash+00000022 Line 23+00000003)
* 00792893 Module(sqlservr+00392893) (SQLExit+0022AD9C)
* 00770DB8 Module(sqlservr+00370DB8) (SQLExit+002092C1)
* 0062EC6C Module(sqlservr+0022EC6C) (SQLExit+000C7175)
* 0050BCEF Module(sqlservr+0010BCEF)
* 0050BB13 Module(sqlservr+0010BB13)
* 00415D04 Module(sqlservr+00015D04)
* 00416214 Module(sqlservr+00016214)
* 00415F28 Module(sqlservr+00015F28)
* 0049C32E Module(sqlservr+0009C32E)
* 0049C46A Module(sqlservr+0009C46A)
* 41075309 Module(ums+00005309) (ProcessWorkRequests+000002D9 Line 456+00000000)

* 41074978 Module(ums+00004978) (ThreadStartRoutine+00000098 Line 263+00000007)

* 7C34940F Module(MSVCR71+0000940F) (threadstart+0000006C Line 196+00000006)
* 77E66063 Module(kernel32+00026063) (GetModuleFileNameA+000000EB)
* ------------------------------------------------------------------------------
-
2005-07-24 01:55:36.00 spid51    Stack Signature for the dump is 0x9A8489B4
2005-07-24 01:55:36.02 spid51    Error: 0, Severity: 20, State: 0
2005-07-24 01:55:36.02 spid51    Stored function 'xp_crash' in the library 'xpde
mo.dll' generated an access violation. SQL Server is terminating process 51..

The short stack dump shows me where the error occurred:

* Short Stack Dump
* 04231072 Module(xpdemo+00001072) (xp_crash+00000022 Line 23+00000003)

which points to line 23:

 *p = 0;

That's right indeed. If you do want more information however, you can use WinDbg to do further debugging:

  1. Attach sqlservr.exe to the debugger using F6
  2. Dump the thread stacks using ~*kv
  3. Dump the TEB of the current thread (typically the last worker thread of SQL Server) using !teb; here you'll find information about thread local storage, fiber data, the last error value (which can be very handy; use net helpmsg #nr to find a textual description of the error)
  4. Dump the process execution block of the process (the PEB) using !peb

Nice (:s) and easy (h) demo of the extended procedure hell. Don't try this on your production box please :-)

Security tip:

If you can (and most people can), disable xp_cmdshell by running the following command: sp_dropextendedproc 'xp_cmdshell'. xp_cmdshell is a big target for hackers and attackers, which opens a big door to the whole server if SQL Server is running as a high-priviliged account. Typical situation: SQL injection vulnerability in web application, web application running as sa, SQL Server running as SYSTEM, ... You can complete the rest of the story yourself ;-).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

A first specific aspect of CLR Hosting I want to cover in this blog post series is the memory management part of the CLR Hosting APIs. It all started some years ago when the SQL Server team started to look at the possibilities to integrate the CLR into the SQL Server database engine to give developers the opportunity to use their knowledge of managed code development to extend the functionality of the database, by writing things such as stored procedures, triggers, user defined functions, user defined types in their favorite languages. One of the reasons of considering this integration was undoubtly the gain of developer productivity and the possibility to integrate database development tighter with the Visual Studio IDE tools, but also the characteristics of the CLR and the .NET Framework as such have played an important role in this decision. For example, extending the functionality of the database using an eXtended Procedure in C++ is a very dangerous thing, whileas doing the same in a managed environment using the CLR eliminates a lot of risks that are introduced by running custom code in the core of the database engine. Things such as memory management, type safety, exception handling mechanisms, etc are great aspects of the CLR and were considered to be a welcome gift in the SQL Server developers world.

However, integrating the CLR in a product like SQL Server which has very high needs on the field of performance, scalability, reliability and data integrity (transactions you know) is not as easy as it might look in the first place. One example of the difficulties that kick in is the management of memory allocation and all of the stuff around it. Why is this? Well, as I said SQL Server has a high need for performance and wants to do everything it can to keep this as high as possible and to avoid conditions that can undermine the runtime quality attributes of the product. The way to do this is in SQL Server is called the SQL Server OS, which can be seen as being the heart of the product. As the name suggests, this core part of the database engine acts like a mini OS because it's responsible to control all threading, locking and scheduling stuff (User Mode Scheduler, fibers, cooperative scheduling) but also all of the stuff around memory management. The latter one is the thing I'll be focusing on in this post.

 

Case study: How SQL Server manages memory

In order to ensure the performance and scalability of a SQL Server instance, the database engine needs a way to control and track all of the memory allocations that are done. It does so to ensure that the amount of allocated memory is kept between boundaries (by default, SQL Server will get as much memory as it can) and to avoid pages to be swapped to disk because the speed gap between primary (RAM) and secundary memory (disk) can lead to a serious performance drawback. Generally spoken, Windows offers applications three ways of allocating memory: virtual memory, shared memory and heaps. The first one is the primary means by which SQL Server allocates memory when that's needed. The virtual memory functionality in the Windows OS can be summarized by explaining the some key functions in the Win32 API to work with this mechanism:

  • VirtualAlloc - allocates memory in the address space of the process that requests the memory (there is an extension to this - suffixed with Ex - that allows more complex memory allocation)
  • VirtualFree - used to free the memory that was allocated previously using VirtualAlloc
  • VirtualLock and VirtualUnlock are used to lock virtual memory pages in the physical memory
  • VirtualProtect is used to change the protection attributes of a virtual memory page; examples of these attributes are PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READ_WRITE, etc including support for copy-on-write mechanisms and so on
  • VirtualQuery - obtain information about the virtual memory on the system

So, when we do want to embed the CLR inside SQL Server, we need to make sure SQL Server still can track all of the memory that's allocated to provide database functionality (e.g. in a managed stored procedure). The CLR Hosting API allows us to do this, by providing a memory management provider that the CLR will call every time it needs memory to do its job. Instead of sending these calls to the Win32 functions directly, these are sent to the SQL Server OS (more specifically the Memory Management Subsystem in there) to handle the request for memory, given the constraints of the SQL Server configuration concerning memory (e.g. min/max mem settings).

I won't cover the heap and shared memory in much detail over here. Instead I'll just mention the most important aspects of these. Let's start with the heap. A heap is controlled by a thing called a heap manager and consists of a region in memory divided in pages of reserved space. The heap manager can be called to get a piece of memory to work with. Typically heaps are used when objects or structures with similar sized need to be allocated in memory. An example of the usage of a heap is the new operator in C++ or the malloc function in C. The basic functions for heap management are:

  • HeapCreate and HeapDestroy are used to create and destroy so-called private heaps.
  • HeapAlloc allocates memory from the heap (cf. C's malloc)
  • HeapFree releases memory from the heap (cf. C's free)

Shared memory is the last mechanism that Windows offers to work with memory. The idea of shared memory is to allocate a memory region and to allow shared access to it for multiple processes, so it can be used for inter process data exchange. SQL Server uses shared memory as a fast way to communicate with client applications on the same machine, bypassing protocols such as TCP/IP (and therefore the whole network OSI stack) or named pipes, through the Net-Library related functionality. The basic functions include:

  • CreateFileMapping to create a so-called section object to be used with either shared memory or a memory-mapped file
  • MapViewOfFile creates a mapped view for a file in the physical memory
  • FlushViewOfFile writes modified pages in a mapped view to disk

Details about all of these functions can be found in the Platform SDK of Windows XP and Windows Server 2003.

 

Controlling Virtual Memory

In the section above, I presented a list of functions in the Win32 API that are used to manage virtual memory, including VirtualAlloc, VirtualFree, VirtualQuery and VirtualProtect. The CLR Hosting API provides an interface called IHostMemoryManager that exposes similar functionality to perform this kind of work. So, when the CLR needs to do something related to virtual memory, it will check whether a custom memory manager was hooked in by the host. If that's the case, the CLR will call that manager instead of calling the Win32 API directly to obtain, free, ... virtual memory.

The full interface of IHostMemoryManager is shown below:

interface IHostMemoryManager : IUnknown
{
    HRESULT CreateMalloc([in] BOOL fThreadSafe,
                         [out] IHostMalloc **ppMalloc);   
   
    HRESULT VirtualAlloc([in] void*       pAddress,
                         [in] SIZE_T      dwSize,
                         [in] DWORD       flAllocationType,
                         [in] DWORD       flProtect,
                         [in] EMemoryCriticalLevel eCriticalLevel,
                         [out] void**     ppMem);
   
    HRESULT VirtualFree([in] LPVOID      lpAddress,
                        [in] SIZE_T      dwSize,
                        [in] DWORD       dwFreeType);
   
    HRESULT VirtualQuery([in] void *     lpAddress,
                         [out] void*     lpBuffer,
                         [in] SIZE_T     dwLength,
                         [out] SIZE_T *  pResult);
   
    HRESULT VirtualProtect([in] void *       lpAddress,
                           [in] SIZE_T       dwSize,
                           [in] DWORD        flNewProtect,
                           [out] DWORD *     pflOldProtect);
   
    HRESULT GetMemoryLoad([out] DWORD* pMemoryLoad,
                          [out] SIZE_T *pAvailableBytes);
   
    HRESULT RegisterMemoryNotificationCallback([in] ICLRMemoryNotificationCallback * pCallback);
}

The most interesting functions in here are the ones that start with Virtual, as these are the equivalent of the well-known Win32 API functions. Actually, when you look at the Platform SDK, you'll find all of the stuff you need to know to understand the corresponding functions in the CLR Hosting API:

The explanation of the CLR Hosting virtual memory management functions can be found over here. One significant difference between the Win32 and CLR functions is the VirtualAlloc function's parameters. VirtualAlloc in the CLR Hosting API has an additional input parameter of the enumeration type EMemoryCriticalLevel:

typedef enum
{
    eTaskCritical = 0,
    eAppDomainCritical = 1,
    eProcessCritical = 2
} EMemoryCriticalLevel;

The CLR will use this parameter to inform the host about the consequences when the memory request is denied. E.g. if the eProcessCritical value is supplied, the result of denying the memory allocation can be a process termination. The greater than operator follows the gradation of severity when memory allocation fails (process > app domain > task).

The VirtualAlloc function can return several values, including S_OK to indicate the memory allocation succeeded, E_FAIL to report a catastrophic event (causing the CLR in the process to become unavailable) and E_OUTOFMEMORY (the host has decided the CLR can't get any memory right now). In the Win32 API the return value is actual the pointer to the allocated memory instead of an HRESULT value. In a similar fashion, the other Virtual* functions provide logical wrappers around the equivalent Win32 functions.

 

Heap management

The IHostMemoryManager isn't responsible for heap management by itself. Instead, it provides a function CreateMalloc to obtain an instance to a IHostMalloc object that controls heap memory:

    HRESULT CreateMalloc([in] BOOL fThreadSafe,
                         [out] IHostMalloc **ppMalloc);

The first parameter in here indicated whether thread safety is needed. The .NET Framework 2.0 build I'm using is actually a little outdated as newer releases (> 2.0.40607) have replaced this with a MALLOC_TYPE enumeration only consisting of two values. I won't cover this in detail now. The IHostMalloc interface looks as follows, offering three functions:

interface IHostMalloc : IUnknown
{
    HRESULT Alloc([in] SIZE_T  cbSize,
                  [in] EMemoryCriticalLevel eCriticalLevel,
                  [out] void** ppMem);
   
    HRESULT DebugAlloc([in] SIZE_T      cbSize,
                      [in] EMemoryCriticalLevel       eCriticalLevel,
                      [in] char*       pszFileName,
                      [in] int         iLineNo,
                      [out] void**     ppMem);

    HRESULT Free([in] void* pMem);
}

Alloc should be self-explanatory I guess, given the explanation of the EMemoryCriticalLevel type. DebugAlloc is used in debugging scenarios and allows to link to a source code file and a line number in that file. The Free function should ring a bell too for everyone who has ever done malloc/free stuff in plain old C.

 

File mapping

In the interface listing of IHostMemoryManager above, this stuff is not present yet (again because of the use of an early build). However, the second release of the .NET Framework CLR Hosting APIs will allow you as a runtime host to obtain information about the needs of the CLR concerning file mappings. To load an execute assemblies, the CLR uses the MapViewOfFile Win32 API function. It's clear that such an action to load an assembly (I'll cover assembly loading in a next post in this CLR Hosting episode on my blog) requires memory. As we want the host (think about SQL Server in particular if it helps to clarify stuff) to be able to get to know everything about memory allocations and releases, it's necessary for the CLR to report the allocation (and release) fo virtual address space (e.g. to count the total size of memory space used by the hosted CLR inside the host process). The CLR does this through the following functions:

    HRESULT NeedsVirtualAddressSpace([in] LPVOID       startAddress,
                                     [in] SIZE_T       size);


    HRESULT AcquiredVirtualAddressSpace([in] LPVOID       startAddress,
                                        [in] SIZE_T       size);


    HRESULT ReleasedVirtualAddressSpace([in] LPVOID       startAddress);

So, when the CLR uses MapViewOfFile it will call AcquiredVirtualAddressSpace to report this to the host and when the UnmapViewOfFile Win32 function was called, this is reported through ReleasedVirtualAddressSpace. The NeedsVirtualAddressSpace function gets called when a call of MapViewOfFile failed because of a low on memory condition. This gives the CLR Host a chance to make memory available in order to allow the CLR to retry this operation.

 

About garbage collection and hints to the CLR

This leaves us with two functions in the IHostMemoryManager interface that we didn't explain yet:

    HRESULT GetMemoryLoad([out] DWORD* pMemoryLoad,
                          [out] SIZE_T *pAvailableBytes);
   
    HRESULT RegisterMemoryNotificationCallback([in] ICLRMemoryNotificationCallback * pCallback);

The first function, GetMemoryLoad, is the equivalent in the CLR Hosting API for the Win32 function called GlobalMemoryStatus (more information over here). Both parameters of the CLR function are output parameters, which obviously means the host tells something to the CLR. The first parameter has to report a percentage of the memory load whileas the second one has to give the exact number of bytes that the CLR will still be able to allocate through the memory manager before an allocation error occurs. The CLR will call this function regularly to get a picture of the status of the memory managed by the host. These values are kept by the CLR to determine when the next round of garbage collection should be launched.

The second function is used by the CLR to pass through a pointer to a ICLRMemoryNotificationCallback object (remember the convention that interfaces started with ICLR are provided by the CLR, whileas the ones starting with IHost are provided by the host developer, that is you). The implementation of the function will be pretty straightforward, namely to "remember" the passed-in pointer in some global variable for later use. The functionality in the ICLRMemoryNotificationCallback interface is very limited:

interface ICLRMemoryNotificationCallback : IUnknown
{
    // Callback by Host on out of memory to request runtime to free memory.
    // Runtime will do a GC and Wait for PendingFinalizer.
    HRESULT OnMemoryNotification([in] EMemoryAvailable eMemoryAvailable);
}

Basically, what this OnMemoryNotification function allows the host to do is to tell the CLR about an out of memory condition (that is expected to happen "soon"). As a reaction on this reported status, the CLR can invoke the garbage collector to free memory. The possible values are declared in an EMemoryAvailable enumeration:

typedef enum
{
    eMemoryAvailableLow = 1,
    eMemoryAvailableNeutral = 2,
    eMemoryAvailableHigh = 3
} EMemoryAvailable;

Currently, only the first value (a low memory condition) will trigger the GC, but in the future the CLR will possible use the other values to drive the scheduling of the next GC round or so.

 

Hook in your memory manager

Assume you've written your memory manager in a class called MyHostMemoryManager (implementing the IHostMemoryManager interface). The next step is to tell the CLR about this during the initialization phase. To do this, you have to implement the IHostControl interface, for example in a class called MyHostControl. The function you need to implement is called GetHostManager.

HRESULT __stdcall MyHostControl::GetHostManager(REFIID id, void **ppHostManager)
{
   if (id == IID_IHostMemoryManager)
   {
      MyHostMemoryManager *pMemoryManager = new MyHostMemoryManager();
      *ppHostManager = (IHostMemoryManager*) pMemoryManager;
      return S_OK;
   }
   else
   {
      *ppHostManager = NULL;
      return E_NOINTERFACE; //tell the CLR we don't take care for the requested manager
   }
}

Now, the CLR is able to obtain an instance of the memory manager you've implemented. The startup code will have the following format:

ICLRRuntimeHost *pHost = NULL;
HRESULT res = CorBindToRuntimeEx(L"v2.0.40607", L"wks", STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_CLRRuntimeHost, (PVOID*) &pHost);

assert(SUCCEEDED(res));

MyHostControl *pHostControl = new MyHostControl();
pHost->SetHostControl((IHostControl*) pHostControl);

This should do the trick. By calling SetHostControl, the CLR will start to ask your CLR host what responsibilities it wants to take. When asking for a memory manager, you'll return your memory manager object as explained above. As a reaction on this, the CLR will call the RegisterMemoryNotificationCallback function to offer you a pointer to the callback object you can use to report memory status to the CLR (through OnMemoryNotication). When pHost->Start() is called later on, the memory manager will be used to handle memory requests by the CLR.

 

Controlling the garbage collector

Controlling the garbage collector of the CLR can be done by two parties: the CLR and the Host. For this particular reason, two interfaces are present in mscoree:

interface ICLRGCManager : IUnknown
{
    /*
     * Forces a collection to occur for the given generation, regardless of
     * current GC statistics.  A value of -1 means collect all generations.
     */
    HRESULT Collect([in] LONG Generation);
   
    /*
     * Returns a set of current statistics about the state of the GC system.
     * These values can then be used by a smart allocation system to help the
     * GC run, by say adding more memory or forcing a collection.
     */
    HRESULT GetStats([in][out] COR_GC_STATS *pStats);
   
    /*
     * Sets the segment size and gen 0 maximum size.  This value may only be
     * specified once and will not change if called later.
     */
    HRESULT SetGCStartupLimits([in] DWORD SegmentSize, [in] DWORD MaxGen0Size);
}

interface IHostGCManager : IUnknown
{
    // Notification that the thread making the call is about to block, perhaps for
    // a GC or other suspension.  This gives the host an opportunity to re-schedule
    // the thread for unmanaged tasks.
    HRESULT ThreadIsBlockingForSuspension();

    // Notification that the runtime is beginning a thread suspension for a GC or
    // other suspension.  Do not reschedule this thread!
    HRESULT SuspensionStarting();

    // Notification that the runtime is resuming threads after a GC or other
    // suspension.      Do not reschedule this thread!
    HRESULT SuspensionEnding(DWORD Generation);
}

The ICLRGCManager is the easier one of both, because you don't have to implement it yourself. Instead, you can ask the ICLRControl object for the CLR-provided manager for garbage collector management. The mechanism to do this is pretty straightforward:

ICLRGCManager *pCLRGCManager = NULL;
res = pHost->GetCLRManager(IID_ICLRGCManager, (void**) &pCLRGCManager);

Once you have a reference to the ICLRGCManager object, you can use it to perform the following actions:

  • Collect - ask the GC to collect a certain generation (see the part 2 post of the CLR Hosting series for more information about the garbage collector generations model); use -1 to collect all of the generations. Note: in managed code you can call System.GC.Collect();
  • GetStats - pass in an object of type COR_GC_STATS telling the CLR which statistics - of the COR_GC_STAT_TYPES enumeration - you want (COR_GC_COUNTS and/or COR_GC_MEMORYUSAGE) and obtain the stats through the same object;
  • SetGCStartupLimits - control the segment size (>= 4 MB; multiple of 1 MB) and the size of generation 0 (>= 64 KB).

In part 2 of the CLR Hosting episode on my blog I told you a bit about the GC's need to suspend threads to reach a safe point and to kick in the garbage collection in a safe way. The CLI implementation of the garbage collector mentions the following in the gcee.cpp file:

// The contract between GC and the EE, for starting and finishing a GC is as follows:
//
//      LockThreadStore
//      SetGCInProgress
//      SuspendEE
//
//      ... perform the GC ...
//
//      SetGCDone
//      RestartEE
//      UnlockThreadStore

We're now talking about the SuspectEE (execution engine) and RestartEE steps:

void GCHeap::SuspendEE(SUSPEND_REASON reason)
{
    //...
    ThreadStore::TrapReturningThreads(TRUE);
    //...
    hr = Thread::SysSuspendForGC(reason);
}

void GCHeap::RestartEE(BOOL bFinishedGC, BOOL SuspendSucceded)
{
    //...
    ThreadStore::TrapReturningThreads(FALSE);
    //...
    Thread::SysResumeFromGC(bFinishedGC, SuspendSucceded);
    //...
}

In the introduction of this post I explained the need of SQL Server to be able to control almost everything through its own "SQL OS" layer. This "everything" includes thread and synchronization management. If a host (such as SQL Server 2005) has this need, it's likely it wants to know about the thread manipulation that's performed by the CLR's GC to be able to (reach a safe point and) run. For that purpose, a host can provide a IHostGCManager implementation, providing three functions:

  • ThreadIsBlockingForSuspension - Notify the host about the fact that the thread that's calling this method will be suspended soon to perform some work (e.g. to perform a garbage collection). This is what the SysSuspendForGC function is called for. As explained in the comments for the function, this allows the host to perform scheduling work to use resources as effectively as needed (which is certainly the case of SQL Server 2005 because of performance and scalability needs as explained earlier).
  • SuspensionStarting - The thread suspension is starting now.
  • SuspensionEnding - Tells the host that suspension of the thread is stopping, also telling the host which generation was garbage collected during the thread's suspension.

 

Conclusion

The CLR Hosting APIs allow you as a developer of a CLR host to control virtually any aspect of memory management in relation to the CLR. You can take advantage of this to control memory limits, to track memory usage for statistics and performance tuning, and so on.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

IIS 7.0 is the next generation web server software from Microsoft that will ship with Windows Longhorn Vista later on, appearing in the betas from beta 2 on. In this post I'll give a sneak peak on the features of IIS 7 announced so far. For more detailed information, we'll have to wait till the PDC most likely, where quite some sessions on IIS 7 are planned. Keep your eye on www.iisseven.com too!

 

Feature overview

Configuration

Today, IIS configuration isn't that easy as it could be. Take the deployment of an ASP.NET application as an example. The configuration of the web app itself is pretty easy and straightforward, using the web.config xcopy deployment mechanism. However, when you need to change the configuration of the web server itself, e.g. to change the authentication mechanism, to set up the default page, the configure error pages, configure application pools, create new vdirs, etc you'll have to call the helpdesk of the web server administration department (your own IT department, the ISP helpdesk, ...) to make the changes in the metabase of IIS (through the inetmgr tool or through scripts).

IIS 7 solves this problem by extending ASP.NET's xcopy deployment mechanism to the whole web platform, supporting XML-based configuration of the IIS settings for the web application. This way, one can delegate the configuration of various aspects of the web application to the web developers and adminstrators, while it's also possible to block the configuration of certain options. In the end, the new configuration model allows a 100% xcopy deployment mechanism of web applications.

To give you an example, I've written an article in the past on creating an picture gallery HTTP Handler in ASP.NET. In order to setup this HTTP Handler one needs to configure it through web.config but also to register the ASP.NET ISAPI for the .jpg extension in order to intercept all of the requests for image files on the server so that thumbnailing through the HTTP Handler's code is possible.

 

Componentized server

From an architectural perspective, IIS 7 has undergone signifiant changes in order to allow customization on virtually every level. In the past, IIS was built on a monolithic DLL model, which means that one single dll was holding all of the functionality of the webserver and it just wasn't possible to change one aspect of the whole webserver software without changing this dll itself. From a security perspective, this can be seen as a disadvantage too. Because all of the functionality is in one dll it's not possible to delete certain functionalities you don't need in your scenario (e.g. say, delete the module responsible for digest authentication).

IIS 7 introduces a building blocks model, in which all of the basic blocks do have a public API that allows you as a developer to extend and/replace the functionality of the IIS webserver. For example, you could write (using either managed or native code) a module to replace the default directory listing functionality to show thumbnails of images instead of displaying a file list, or write a module to implement your own authentication mechanism, or ...

These APIs will follow the design of the IHttpModule and IHttpHandler interfaces which are present in ASP.NET today. As the matter in fact, IIS 7 is absorbing the functionality of ASP.NET in order to deliver an integrated set of functionality. This "innovation through integration" evolution is in fact ongoing since IIS 6.0 where the application pool/worker process model of ASP.NET was integrated into the web server itself (in IIS 5.x there was still a separate executable called aspnet_wp.exe to run the web application).

Note that ISAPI still will be supported, although the new recommended way to extend the server will be through the public APIs for modules and handlers and so.

Components that will ship with the IIS 7 webserver software include the following:

  • Logging and diagnostics: CustomLoggingModule, HttpLoggingModule, RequestMonitorModule, TracingModule
  • Authentication and authorization: AnonymousAuthModule, BasicAuthModule, CertificateAuthModule, DigestAuthModule, FormsAuthModule, WindowsAuthModule; AccessCheckModule, UrlAuthorizationModule
  • Extensibility: CGIModule, ISAPIFilterModule, ISAPIModule, ManagedEngineModule, ServerSideIncludeModule
  • Publishing: DavModule

Each of these components can be disabled, replaced and extended if you want to do so. Furthermore, there are modules in the core of the webserver and even lower for protocol support:

  • Core webserver: CustomErrorModule, DefaultDocumentModule, DirectoryListingModule, DynamicCompressionModule, HttpCacheModule, StaticCompressionModule, StaticFileModule
  • HTTP protocol: ClientRedirectionModule, OptionsVerbModule, TraceVerbModule, ValidationRangeModule
  • Configuration: ConfigurationModule
  • Metadata caching: FileCacheModule, SiteCacheModule, UriCacheModule

Because of this extensibility you can expect a big ecosystem of IIS 7 extension modules to appear once IIS 7 hits the roads.

 

Diagnostics

IIS 7 allows IT admins to monitor all of the aspects of the webserver in realtime while it's running. This includes the monitoring and tracing of:

  • sites
  • application pools and applications
  • requests

In this part fo the story, integration plays an important role too, because of the integration of ASP.NET and IIS 7 event collection for diagnostics. So there is an integration between IIS debugging/tracing/monitoring and the same stuff for ASP.NET applications (there are over 300 events that will be captured and reported). WMI support is present as well for the full range of events and diagnostic info.

 

Summary of quality attributes

So, IIS 7 provides the following quality attributes:

  • Compatability: it's a key goal of the IIS 7 development team that all of the old ISAPIs, CGIs, ASP and ASP.NET apps, WMI/ADSI scripts will still run
  • Componentization: the IIS 7 core has been broken down to a collection of modules instead of one big monolithic block as it was in the past
  • Delegation of administration: through the web.config model, it's possible for site owners to configure their site completely themselves, allowing for xcopy deployment too
  • Extensibility: developers can leverage the power of the public APIs that will ship with IIS 7 to write extensions to the webserver by implementing modules in native (C++) or managed (C#, VB.NET, etc) code
  • Integration: ASP.NET and IIS come more closely together in IIS 7, e.g. by unifying the HttpHandler and HttpModule stacks and by providing a unified configuration system (note: Indigo will be integrated with IIS 7 too)
  • Security: install only the modules you need in order to reduce the attack surface; use the new administration tools for easier management and use delegated administration to put the customer in control
  • Supportability: this includes the new diagnostic subsystem, delegated administration, real-time monitoring, etc

I'll post about other features once more information is available. In the meantime you can perform a MSN Search (or a *whatever search engine you like* search) for IIS 7 to find more about it.

 

Schema

In the next picture, the unified HTTP pipeline of IIS 7 is displayed (click for large image):

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Note: I wrote this post a couple of weeks ago on my way to TechEd Amsterdam somewhere between Brussels and Amsterdam on the Thalys train. Finally I found the time to finalize this first post on concurrency extensions in the Comega language. I hope you enjoy it. More on Comega will come later.

Introduction

In the previous posts I described how Comega helps to simplify the construction of data-driven applications thanks to the integration of SQL and XSD concepts into the programming language itself. Beside of these new data access constructs, Comega also extends the C# language with new asynchronous concurrency abstractions, that are partially derived from the "Polyphonic C#" project done by Microsoft Research.

Concepts

By default, methods in the C# language have a synchronous nature. When a method is called, the caller is blocked until the execution of method has come to an end and a result is returned (either a void or some value or object). Although the .NET Framework supports asynchronous method invocation too, through a bunch of class libraries, the language itself is not aware of the possible asynchronous nature of a method. Comega extends the notion of asynchronous methods by introducing a series of new language constructs that indicate that a method has an asynchronous nature. When calling an asynchronous method, the caller is not blocked until a result is returned but it can proceed immediately. Asynchronous methods are often called messages because of their one-way communication nature.

Now, how does Comega realize this mission statement? A first central concept when dealing with the concurrency extensions in Comega is the concept of a chord. In "classic languages" there is a one-on-one mapping between a method header and a method body: when method X is called that way (the "signature"), this code (the "body") has to be executed. Comega allows a method body to be associated with a set of methods, which we call a chord. This body can only be executed when all of the methods that are declared in the corresponding header have been called.

The async keyword

Okay, enough theory for now, let's take a look at some very basic samples.

Sample 1:

The first sample shows the use of the async keyword to indicate the asynchronousness of a method. When calling the method, the caller isn't blocked and can continue with its own work.

using System;

public class Test
{
   static async Do()
   {
      while(true);
   }

   static void Main()
   {
      Console.WriteLine("Before do");
      Do();
      Console.WriteLine("After do");
      Console.ReadLine();
   }
}

Take a look in the Windows Task Manager for the number of active threads for the process when executing this piece of code. When you comment out the Do() method invocation, the number of threads will be one less. So, when calling the Do-method another thread is started.

Sample 2:

In this second sample, I'll prove that parallellism introduced by the async method declaration using one of my favorite mathematical concepts, prime numbers :-).

using System;

public class Test
{
   static int n = 0;

   static
async Do(int max)
   {
      int id = ++n;
      Console.WriteLine("{0} - Started", id);

      for
(int i = 2; i <= max; i++)
      {
         bool prime = true;

         for
(int j = 2; j <= (int) Math.Sqrt(i); j++)
         {
            if (i % j == 0)
            {
               prime =
false;
               break;
            }
         }

         if
(prime)
            Console.WriteLine("{0} - {1}", id, i);
      }

     
Console.WriteLine("{0} - Finished", id);
   }

   static void Main()
   {
      Console.WriteLine("Before do");
      Do(1000000);
      Do(1000000);
      Console.WriteLine("After do");
      Console.ReadLine();
   }
}

When executing the piece of code, you should see clearly the parallellism of both method invocations as both threads will report the results of their prime search journey concurrently. Note the lack of a return type when declaring asynchronous methods.

Note: The sample above has a problem; people who're expierenced with concurrency programming should see it immediately. If you don't see it, try the next piece of code:

using System;

public class Test
{
  
static int k = 0;

  
static async Do2()
   {
      k++;
   }

   static void Main()
   {
      for (int i = 0; i < 1000; i++)
         Do2();

      Console.WriteLine(k);
   }
}

Before you do execute this piece of code, guess what the result should be.

Using chords

Time for the next step: chords. I'll kick off with the basic sample that's in the Comega documentation: the "buffer tutorial". Declare the following class:

using System;

public class Test
{
   static async Put(string s);

   static string Get() & Put(string s)
   {
      return s;
   }

   static void Main()
   {
      Put("Hello");
      string res = Get();
      Console.WriteLine(res);
      Console.ReadLine();
   }
}

The execution of this piece of code should write "Hello" on the screen. So, what happened? The call to Put takes the message as a string parameter that is. Nothing happens as the method body is missing in the class definition. Then, the Get method is called, which requires Get to be called first in order to "gain access" to the method body. As this is the case, the method can execute. In the body of the Get method, the parameter(s) from the Put method can be used in order to perform the job requested.

So, when you'd write this piece of code:

static void Main()
{
   string res = Get();
   Put("Hello");
   Console.WriteLine(res);
   Console.ReadLine();
}

nothing would happen, because the "entry condition" is not met. The following piece of code shows the queue nature of chords:

static void Main()
{
   Put("Hello");
   Put("World");
   Console.WriteLine(Get());
   Console.WriteLine(Get());
}

It should print two lines containing "Hello" and "World". The use of this kind of constructs becomes more useful when threads are used. One thread can wait on another one when a certain piece of code isn't ready executing the task requested. Let's go back to our primes sample, adapted with chords:

using System;

public class Test
{
   static async Do(int max);

   static int[] GetPrimes() & Do(int max)
   {
     
int count = 0;
      bool[] res = new bool[max + 1];

      for (int i = 2; i <= max; i++)
     
{
        
bool prime = true;

        
for (int j = 2; j <= (int) Math.Sqrt(i); j++)
         {
           
if (i % j == 0)
            {
               prime =
false;
              
break;
           
}
        
}

        
res[i] = prime;

        
if (prime)
           
count++;
     
}

     
int[] primes = new int[count];
     
int j = 0;

     
for (int i = 2; i <= max; i++)
         if (res[i])
            primes[j++] = i;

      return primes;
   }

   static void Main()
   {
     
Do(1000);

     
int[] primes = GetPrimes();
     
foreach (int p in primes)
        
Console.WriteLine(p);

     
Console.ReadLine();
   }
}

It should be clear that this code returns the right result. However, let's make some changes to show a somewhat more complicated construction:

using System;

public class Test
{
   static async Do(int max);

   static
int[] GetPrimes() & Do(int
max)
   {
      int count = 0;
      bool[] res = new bool[max + 1];

      for (int i = 2; i <= max; i++)
      {
         bool prime = true;

         for
(int j = 2; j <= (int
) Math.Sqrt(i); j++)
         {
            if (i % j == 0)
            {
               prime =
false;
               break;
            }
         }

         res[i] = prime;
         if (prime)
            count++;
      }

      int[] primes = new int[count];
      int j = 0;

      for (int i = 2; i <= max; i++)
         if (res[i])
            primes[j++] = i;

      return primes;
   }

   static async Worker()
   {
      Console.WriteLine("Worker started");
      System.Threading.Thread.Sleep(5000);
      Console.WriteLine("Okay, time for prime calculation now");
      Do(1000);
      Console.WriteLine("Command for prime calculation issued");
   }

   static void Main()
   {
      Worker();
      Console.WriteLine("I'm waiting...");

      int
[] primes = GetPrimes();
      foreach (int p in primes)
         Console.WriteLine(p);

      Console.ReadLine();
   }
}

So, what happens in here? The main thread is not sending a request for prime calculation this time. Instead, it decides to wait for a result. Normally, the sequence Wait-Start blocks forever, as we saw in the past. However, this time we launch an asynchronous Worker method that issues the command to calculate prime numbers somewhere in the future (in this case about 5 seconds after the call). Just as an example I'm using a Thread.Sleep call to wait for a fixed time, but in reality you might be waiting for an external stimulus. The next code snippet shows an extension of this "worker and waiter" concept:

static async Worker()
{
  
Console.WriteLine("Worker started");

   
Random r =
new Random();

  
while(true)
  
{
     
int wait = r.Next(1000, 10000);
     
System.Threading.Thread.Sleep(wait);
     
Console.WriteLine("Okay, time for prime calculation now");
      Do(r.Next(10000));
      Console.WriteLine("Command for prime calculation issued");
   }
}

static void Main()
{
   Worker();
   while(true
)
   {
      Console.WriteLine("I'm waiting...");
      int
[] primes = GetPrimes();
      foreach (int p in
primes)
         Console.WriteLine(p);
   }
   Console.ReadLine();
}

Note: This post was not fully completed because my train did arrive in Amsterdam before I could finish my writings. Additional stuff on concurrency extensions in Comega will be posted later on in a 6 bis (and maybe a 6 tris) post. Then I'll show some IL too :-).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Posted Saturday, July 23, 2005 1:20 PM by bart | with no comments
Filed under:

Introduction

Last night I posted about the "MSMQ Service Model" development I did earlier this year, in the context of my thesis research. In this post, I'll show you another development called the "SOAP Data Provider". I assume everyone who's reading this blog is familiar with the ADO.NET data access model used in the .NET Framework to access data. A data provider is the means by which an application can use the ADO.NET model to access a specific database implementation of some vendor. By default, the .NET Framework ships with data providers for its own database flaship product SQL Server (which is in fact a client for the TDS protocol behind the scenes), a data provider for Oracle, one for ODBC and one for OLEDB. The two latter ones are helpful when you have to talk to any database that supports OLEDB, e.g. Access, Excel (if you want to use Excel as a kind of database), Sybase, MySQL, etc. However, vendors can develop their own data provider too, and thus they are given a chance to write it using (proprietary) native protocols to talk with the underlying database, resulting in a better performance.

Data providers support the following concepts:

  • a connection - to connect to the database (or to connect to some database on a database server)
  • a command - to execute (SQL) commands against the database for data retrieval and/or data manipulation, possibly with parameters
  • a data adapter - the bridge between a DataSet and a series of command objects to allow offline data to be synchronized with the online database
  • a data reader - a forward-only reader to crawl through a result set that was retrieved by executing a query command
  • a transaction - well euhm, to support database transactions

Now, one thing that's missing (in my opinion) is a similar data adapter to access and manipulate a database through web services using the SOAP protocol. Assume you've created a database back-end and you want to expose this to several clients using web services to masquerade the physical database that's used behind the scenes. If you want to support data retrieval and data manipulation, you'll typically end up with four web methods for each operation, i.e. select, insert, update, delete. The way you implement these inside the web service is up to you. Maybe you pass the web method's arguments to parameters in a parameterized SqlCommand that's used to connect to SQL Server in the back. Or maybe you don't use some data provider in the web service logic but just call another service or call some legacy application to perform the operation. What's more important however is how to consume the webservice with its four "DML methods".

 

How to use the SOAP Data Adapter?

Because of this thought I decided to create a SOAP Data Adapter which allows you to use the DataSet-driven ADO.NET data access model combined with the power of Service-Oriented design to layer the application and to use the protocol standards such as SOAP and XML. Assume you have a web service, created by one of the following:

  • an .asmx in .NET
  • a web service through SQLXML for SQL Server 2000/2005
  • a web service created using native support for web services in SQL Server 2005
  • other web service implementation mechanisms (e.g. on another platform)

Given this service with four web methods, like this:

[WebMethod]
public SoapSampleDatabase GetProducts();

[WebMethod]
public int InsertProduct(string name, decimal price);

[WebMethod]
public int UpdateProduct(int id, string name, decimal price);

[WebMethod]
public int DeleteProduct(int id);

we want to be able to do this:

  1. add a web reference to the web service, assume this is the class ProductsService
  2. write code to build a connection and a dataadapter instance

    SoapConnection conn = new SoapConnection(new ProductsService()); //pass in a proxy class instance
    SoapDataAdapter adap = new SoapDataAdapter();

    adap.SelectCommand = new SoapCommand("GetProducts", conn); //map to web method GetProducts

    adap.InsertCommand = new SoapCommand("InsertProduct", conn); //map to web method InsertProduct
    adap.InsertCommand.Parameters.Add(new SoapParameter("@ProductName", DbType.String, "productname")); //parameterize
    adap.InsertCommand.Parameters.Add(new SoapParameter("@Price", DbType.Decimal, "price")); //parameterize

    //for clarity, the UpdateCommand and DeleteCommand were omitted

  3. fill a DataSet

    SoapSampleDatabase db = new SoapSampleDatabase(); //strongly typed DataSet
    adap.Fill(db);

  4. manipulate the DataSet, e.g. by binding it to Windows Forms controls or so
  5. synchronize changes through the adapter

    adap.Update(db.Products);

 

Simplifications

One simplification I made is the generation of the data adapter initialization code for a SOAP Data Adapter consumer. Given the web service proxy class, it's possible to generate all of this code automatically by using some reflection. This stuff is explained in the (Dutch) document on http://www.bartdesmet.net/download/thesis in Chapter 7. In the download zip file, you'll find this tool (called the SoapAdapter Code Generator) in the project SoapImporter.

 

Download

The sample code can be downloaded over here. Note there is no support for transactions in the provider, as I didn't want to go through the WS-* transactional support stuff because of the lack of support in WSE at the time of writing the code. Also keep in mind that the code provided in the sample is for demonstration purposes only. If you want to use and/or adapter it, feel free to do so. Any comments ang suggestions are highly appreciated.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

The upcoming technology release of Indigo will simplify a lot of stuff around MSMQ and other "distributed software" technologies out there. Based on attribute-based programming the underlying implementation of various application aspects such as reliable messaging, service policies, service contracts, etc is abstracted and hidden for the developer, while relying on the various standards that were developed recently on the WS-* field. In a similar way the use of various transportation channels such as HTTP, MSMQ, etc is made easier and more transparent. So far for some Indigo evangelism on my blog for now (I'll do extensive posting on Indigo later this year once beta 1 of "Windows Vista" hits the road).

However, nowadays the development of a SOA-based application with reliable messaging support is not that straightforward as it could be. WSE currently lacks support for reliable messaging support as such through the WS-* series of standards. There is however a sample by architect George Copeland on how to do implement a reliable messaging solution using WSE 3.0 that can be found on MSDN on http://msdn.microsoft.com/webservices/default.aspx?pull=/library/en-us/dnwse/html/wseandws-rm.asp. In the first half of this year however (this article is dated in June this year) I came across the problem of implementing such a reliable messaging solution that could be combined with a service-oriented approach, as part of my last year's dissertation (which can be downloaded on http://www.bartdesmet.net/download/thesis/, in Dutch however). In this post I'll show you some of this stuff.

 

Basics of MSMQ

If you ask a developer for the Windows platform for a messaging solution in the Windows OS, he/she should come up with MSMQ as the answer number one. I won't explain MSMQ itself over here, more information can be found on www.microsoft.com/msmq (look for MSMQ 3.0, which is the XP/2003 version of the technology). In .NET you can use the MSMQ technology by taking advantage of the System.Messaging namespace which is pretty straightforward to use. The basic steps look like this:

  1. Create a message queue using the MSMQ management tools, through an installer class or using code (MessageQueue.Create). You can choose to create a public queue or a private queue, with or without transactional support.
  2. Put messages on the queue in application A, using the Send method on the MessageQueue class. The message object should be marked as [Serializable] in order to serialize it and put it on the queue as a message. To send more complex messages with support for encryption, priority based delivery, timeout values, etc you have to use the Message class instead.
  3. Receive the message at the other side (application B) using the Receive method on the MessageQueue class, pointing to the same queue. This method returns a Message object, from which you can get the message itself by using the Body property. In reality, you'll have to attach a formatter to the message queue instance to make deserialization possible, based on the original type of the message object.

In code, this looks as follows:

//sender
MessageQueue mq = new MessageQueue(path);
Message msg = new Message(body_object_of_message); //assume the type of this object to be MyMessageType
mq.Send(msg, somelabel); //th
e label can be a GUID or a textual message

//receiver
MessageQueue mq = new MessageQueue(path);
mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(MyMessageType) }); //alternatively use a binary formatter
Message msg = mq.Receive(); //use a
MyMessageType original_message = (MyMessageType) msg.Body;

As you can see, this approach does not have a service-oriented look and feel. First of all, the messaging stuff is far too heavy to code using the System.Messaging namespace. Let's say it's too low-level to be really handy to program. A second drawback is the fact that you need to wrap a series of parameters to some call in a (serializable) message object. In contrast, a web service is far more easy to use:

//sender
object res = proxy.DoSomething(par1, par2, par3);

//receiver
[WebMethod]
object DoSomething(object par1, object par2, object par3)
{
   //perform work
}

When doing the same in MSMQ, you have to wrap the service operation parameters in a data carrier object (called the message) in order to do the same. Another disadvantage of the use of MSMQ for developing kind of a service-oriented application is the hosting of the app: you'll be responsible to write a server application that listens on the queue to take in requests, process them (on a background thread maybe) and send the result back to the client (using a response queue, because messaging in MSMQ is one-way messaging by design).

So, the drawbacks of MSMQ and System.Messaging are:

  • Object-based, not method-based (read: no "service operation" concept)
  • Low-level API, little abstraction for SOA
  • No hosting functionality (server app), no abstraction on the client (proxy client)
  • Uni-directional communication by default

Writing the server application really isn't that straightforward at all. When bidirectional communication or message acknowledgements (as part of a reliable messaging solution) are needed, a signficant amount of additional code ("plubming"?) is needed. Furthermore, threading will be required to have a better scalability and the use of a Windows Service as the application host is likely to be your choice. Combined, you'll have pretty much work to do in order to get the whole thing up and running.

 

Services built on top of MSMQ

Defining an MSMQ service

Somewhere in March/April I decided to write an abstraction layer for MSMQ that allows developers to create a service that uses the MSMQ protocol for (reliable) message delivery while maintaining the advantages of attribute-based development to write the service. That way, developers would not have to worry about creating a service application or a proxy manually, or to create the wrapper types for service operation calls. As a result, the development of this allows developers to write code like this:

[MsmqService] //compare with WebService
public class MyService
{
   [MsmqMethod] //compare with WebMethod
   public int Sum(int a, int b)
   {
      return a + b;
   }
}

Once this code is written and compiled, it's time to make it available over MSMQ. To do this, we need to generate a service application to host the code needed for multi-threading, message-to-operation translation, etc. As the matter in fact, this little piece of code results in:

  • 2 message queues, one for the input parameters (a, b) and one for the answer (the sum)
  • 2 carrier types, one for the input parameters (a, b) and one for the answer (the sum); both need to be serializable

Generating a service contract

In order to make things easier, a tool called mqdl.exe is available to convert the class definition (compiled to a .dll assembly) to a so-called MQDL service contract. This is called "export" mode. For the sample mentioned above, this looks as follows:

<?xml version="1.0" standalone="yes"?>
<MqdlDocument>
  <Service svc="SomeService" component="SomeTestService.SomeService">
    <Operation id="Sum">
      <Input>
        <Parameter name="a" type="System.Int32" />
        <Parameter name="b" type="System.Int32" />
      </Input>
      <Output type="System.Int32" />
    </Operation>
  </Service>
</MqdlDocument>

Creating a service application

Next, the mqdl.exe tool can be used to generate a service application and a proxy class. Let's start with the service application, called "svcman" mode. For each operation, a monitor is created that's watching a message queue. When a message is received, it's deserialized and passed to the service class (that we did create earlier) for processing. The answer that comes back is wrapped into a response message object, that is sent back on the response queue that's associated with the incoming message queue. The generated code for the Sum operation looks like this:

public class SumMonitor
{
        delegate System.Int32 ProcessDelegate(System.Int32 in1,
                                              System.Int32 in2);
       
        private class ProcessCallbackData
        {
            public ProcessDelegate pdel;
            public Message msg;
        }
   
        private MessageQueue queue;
        private bool stop = false;
        private bool stopped = false;
       
        public SumMonitor(string prefix)
        {
            string path = prefix + "SumRequests";
            if (!MessageQueue.Exists(path))
                MessageQueue.Create(path);
            queue = new MessageQueue(path);
            queue.Formatter = new XmlMessageFormatter
   (new Type [] { typeof(InputSum) });
        }
       
        public void Stop()
        {
            stop = true;
           
            while(!stopped);
        }
       
        public void Start()
        {
            while(!stop)
            {
                try
                {
                    Message msg = queue.Receive(TimeSpan.FromSeconds(5));

                    InputSum input = (InputSum) msg.Body;
                   
                    SomeTestService.SomeService svc
    = new SomeTestService.SomeService();
                    ProcessDelegate d = new ProcessDelegate(svc.Sum);
                    ProcessCallbackData pcd = new ProcessCallbackData();
                    pcd.pdel = d;
                    pcd.msg = msg;
                    d.BeginInvoke(input.in1, input.in2,
                                  new AsyncCallback(Done), pcd);
                }
                catch {}
            }
           
            stopped = true;
        }
       
        private void Done(IAsyncResult res)
        {
            ProcessCallbackData pcd = (ProcessCallbackData) res.AsyncState;
           
            OutputSum output = new OutputSum();
            output.output = pcd.pdel.EndInvoke(res);
           
            Message resp = new Message(output);
            resp.CorrelationId = pcd.msg.Id;
            pcd.msg.ResponseQueue.Send(resp);
        }
}

Note that callbacks are used to call the underlying service class in an asynchronous fashion. Furthermore, transport types are generated based on the MQDL contract:

[Serializable]
public class InputSum
{           
    public System.Int32 in1;
    public System.Int32 in2;
}
       
[Serializable]
public class OutputSum
{           
    public System.Int32 output;
}

Next, a service manager class is generated to host all of the monitors and start these on different threads:

public class ServiceManager
{
    private SumMonitor m1;
   
    public ServiceManager(string path)
    {
        m1 = new SumMonitor(path);
    }
   
    public void Start()
    {
        new Thread(new ThreadStart(m1.Start)).Start();
    }
   
    public void Stop()
    {
        m1.Stop();
    }
}

In the end, you just need to write a simply application (e.g. a Windows Service) that calls the Start and Stop methods on the ServiceManager, like this:

public static void Main()
{
 ServiceManager mgr = new ServiceManager(".\\private$\\");
 mgr.Start();
}

Consuming the service

At the other side, we need a proxy to hide all of the details of translating a service call to a transport type, sending it to a request queue, polling the response queue and translate the answer back to a method call return value. This is done by the "proxy" mode of mqdl.exe, which generates code like this:

public System.Int32 Sum(System.Int32 a, System.Int32 b)
{
 MessageQueue __queueReq = new MessageQueue(__path + "SumRequests");
 __queueReq.Formatter = new XmlMessageFormatter(
  new Type [] { typeof(InputSum) });

 string __pathResp = ".\\private$\\" + Guid.NewGuid().ToString();
 MessageQueue __queueResp = MessageQueue.Create(__pathResp);
 __queueResp.Formatter = new XmlMessageFormatter(
  new Type [] { typeof(OutputSum) });
   
 InputSum __input = new InputSum();
   
 __input.in1 = a;
 __input.in2 = b;
   
 Message __req = new Message(__input);
 __req.ResponseQueue = __queueResp;
 __queueReq.Send(__req);

 OutputSum __output = new OutputSum();
 try
 {
  string __id = __req.Id;
  Message __msg = __queueResp.ReceiveByCorrelationId(__id,     TimeSpan.FromSeconds(__timeout));
  __output = (OutputSum) __msg.Body;
 }
 finally
 {
  MessageQueue.Delete(__pathResp);
 }

 return __output.output;
}

People who do understand Dutch can read the details of this in my thesis (see http://www.bartdesmet.net/download/thesis/), chapter 10.

Schematic overview

The following picture illustrates the overall mechanism of mqdl.exe:

The mqdl.exe tool has three modi as I explained:

  • export mode, takes an assembly (DLL) and generates the MQDL contract (XML) using reflection
  • svcman mode, takes the MQDL contract (XML) and generates monitors and a service manager, both in C# using pretty large XSLT transforms
  • proxy mode, takes the MQDL contract (XML) and generates a proxy class in C# using another XSLT transform

 

Give me some code

You can take a look at this (experimental) implementation over here. Feel free to use it if it looks useful to you, suggestions and additions are welcome (but keep in mind the Indigo story that's due to be released soon).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Let's join the club and announce that Windows Longhorn, the next generation Windows OS after Windows XP, will be renamed to Windows Vista. The first beta is expected to hit the road on July 27th (next week that is). Followed by a second beta and a so-called Release Client Zero, the RTM is planned now for June 28th 2006.

Update:

 

Clear Confident Connected

Bringing clarity to your world

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

In the previous posts on CLR Hosting with .NET v2.0 I explained the basic principles of - guess what - hosting the CLR inside a process by using the mscoree library. This post will explain what the various options are for starting the CLR and how the basic lifecycle of a hosted CLR will look like. In the next episodes we'll dive into more detailed stuff on how to customize the hosted CLR in much more detail.

 

Basic code skeleton for CLR hosting

In part 1 of this episode, I showed the following code fragment to launch the CLR:

ICLRRuntimeHost *pHost = NULL;
HRESULT res = CorBindToRuntimeEx(L"v2.0.40607", L"wks", STARTUP_SERVER_GC, CLSID_CLRRuntimeHost, IID_CLRRuntimeHost, (PVOID*) &pHost);
assert(SUCCEEDED(res));

res = pHost->Start();
assert(SUCCEEDED(res));

Let's now explain in somewhat more detail what the various parameters of the CorBindToRuntimeEx function mean and how you can use these parameters to customize the behavior of the CLR.

 

CLR versioning

Being in the heart of the .NET Framework, the CLR is upgraded in every release of the .NET Framework and thus there should be a versioning concept when talking about the CLR as such. In order not to break existing applications, the .NET Framework supports side-by-side installation of different versions of the CLR (and the .NET Framework BCL assemblies in the GAC). The various versions of the CLR on your machine can be found in the Windows\Microsoft.NET\Framework folder on the system, where every subfolder contains a specific version of the .NET Framework (e.g. 1.0.3705, 1.1.4322). Furthermore, there's a key in the registry that enumerates all of the installed versions of the .NET Framework. This key can be found in the registry under HKLM\SOFTWARE\Microsoft\.NETFramework\Policy. However, although you can have multiple versions of the .NET Framework and CLR on the same machine, there will be only one startup shim on the machine, with the name mscoree.dll. This DLL is using the registry to locate the .NET Framework installation files for the requested CLR version and to hand over the execution control to the requested CLR engine which is installed in one of the Windows\Microsoft.NET\Framework subfolders.

When loading a specific version of the CLR, the shim still has a choice to make: whether to load the server build or workstation build, as explained in the next paragraph. However, as the implementer of a CLR Host you do have to make a decision too, which is the version loading strategy you want to follow:

  • A first choice is to go hand in hand with a certain version of the CLR, which is in my opinion the best possible choice. SQL Server 2005 uses this approach and will always load the version of the CLR that it was originally built with. I can't give a version number yet, as the final builds of SQL Server 2005 and the .NET Framework v2.0 are not available yet. But if you even wondered why the SQL Server 2005 and .NET Framework v2.0/Visual Studio 2005 releases are so tightly bound to each other, this is the reason why: in order to finalize the SQL Server 2005 development and make the RTM build, the v2.0 build of the CLR and .NET Framework has to be frozen first.
  • The other option is to load the latest version of the CLR that's available on the system. I won't cover this because I do not prefer this strategy as it can lead to compatibility problems (although this should be reduced to a minimum, it's better to stick with a specific version of the CLR to avoid this kind of problems proactively).

As a result, the first parameter of the CorBindToRuntimeEx function contains the version number in the format "v<major>.<minor>.<build>". Tip: also look at CorBindToCurrentRuntime which uses a configuration file to retrieve information about which version of the CLR to load.

Warning: The version which will be loaded does not have to match the one you specify, because of possible policy configuration on the machine. You can force the shim to load the specified version "literally" by using the STARTUP_LOADER_SAFEMODE flag in the third parameter of CorBindToRuntimeEx.

If not implementing a custom CLR Host, you can use the app.exe.config file to configure a specific version to be loaded. If multiple supportedRuntime-elements are specified, evaluation occurs from top to bottom. A requiredRuntime-element is only needed for backward compat on machines that only do have version 1.0.3705 of the .NET Framework, so I do not mention it in the configuration file below.

<configuration>
   <startup>
      <supportedRuntime version="v<major>.<minor>.<build>" /> <!-- specify version that is supported by app-->
      ...
   </startup>
</configuration>

 

Which build?

The CLR always ships with two different builds of the core execution engine, being the workstation build (mscorwks.dll) and the server build (mscorsvr.dll). Now, what's the difference between the two? Let's start with the begin: one of the key architectural elements of the CLR is the garbage collector for automatic memory management (in contrast to unmanaged code, e.g. in C with malloc/free or C++ with new/delete). Now, how does garbage collection work? An intermezzo...

<INTERMEZZO Title="Garbage collection in the .NET Framework">

The lifecycle of an object in the CLR is pretty straightforward. First of all memory has to be allocated, which is done by the newobj instruction in MSIL-code that is accessibly through the new keyword in C# and similar keywords in other managed languages. This operator is mapped on low level calls to allocate memory, just as the malloc function in C has to do to obtain memory. Next, the object has to be initialized which is done by calling the constructor of the type. After these two initial steps, the object is ready to be used and starts its lifetime in the hard .NET world :-). Now, at a certain point in time the object is not longer needed. When this is the case, the developer can optionally (!) explicitly (or implicitly) indicate that the object is no longer needed by calling the (optional) Dispose method on the object (cf. the IDisposable pattern and the using keyword in C#). This phase allows clean-up of the allocated resources and the state embedded in the object itself. Once this phase (if needed) has completed, it's time to free the memory which is done by the garbage collector.

This leaves us with some questions. The first one is how the CLR does allocate memory when the newobj instruction is called. First of all, remark that newobj is kind of an object oriented virtual machine language instruction, so in the end it has to mapped on a low-level processor instruction asking for n bytes of memory. Therefore, the CLR starts by calculating how much space is needed on the managed heap to hold the object and additional information that the CLR uses to do housekeeping stuff. Basically, the managed heap looks like an array of objects sitting next to each other and a pointer (NextObjPtr) that indicates the next position where an object can be allocated on the heap. This mechanism allows fast memory allocation, as there is no need to a list traversal to find free memory. Memory in the managed environment thus has a contigious look-n-feel. However, what I told you so far is pretty wishful thinking: the available memory isn't infinite so at a certain point in time newobj will come to the conclusion that there is no address space left to allocate the object because the heap is full (NextObjPtr + n bytes > end of address space). So far for the easy stuff :-).

The CLR has a so-called ephemeral garbage collector which means that objects are grouped in generations that are related to an object's lifetime. Basically this means that newly allocated objects will be living in generation 0. Assume the CLR is running for a while now and 10 objects have been created of which 4 are not longer needed. In comes a request for memory allocation on the managed heap through a newobj call. The CLR however has a built-in threshold value for the size of generation 0. Assume this threshold has been reached and therefore it's not possible to allocate memory directly for the new-to-be-created object in generation 0. At this point in time, the garbage collector comes into play. It takes a look at the objects in the managed heap and concludes that 4 of these objects are not longer needed. Assume these are objects 3,6,7,8, then the managed heap will be compacted to 1,2,4,5,9,10. When the garbage collector has finished its job, these objects no longer belong to generation 0 but are moved to generation 1. As a result, generation 0 is now empty (NextObjPtr is reset to the initial position in generation 0) and the newobj call can continue (aforementioned condition for available address space is met). Collection generation 0 generally reclaims enough memory to continue and will be quite effective because generation 0 is rather small (and therefore analysis and garbage collection goes fast) but also because of the fact that a lot of objects do only live for a short time. If objects survive this collection, they end up in generation 1 that consists of objects that have a longer lifetime. It's however not difficult to see that generation 1 will grow too, therefore it has a threshold too. When this situation occurs (triggered by a full generation 0 and an unsuccessful move of generation 0 objects to generation 1), generation 1 (which is larger than generation 0) will be collected too. Objects that survive this garbage collection process will move to generation 2 (the last generation in the CLR). When this is done, generation 0 is analyzed and collected, promoting the survivor objects to generation 1. It's clear that a generation 1 level collection takes more time than a generation 0 level collection, but also that collections of generation 1 will occur less frequently than the collections of generation 0. In the end, the characteristics of an ephemeral garbage collector can be explained by the very basic assumption that newly created objects are likely to have a short lifetime and old (surviving) objects are likely to live longer. When garbage collection can't free any memory, the CLR will throw an OutOfMemoryException.

Note the whole garbage collection mechanisme is far more complex than what I did describe above. A few points to take care of include:

  • The garbage collector changes the addresses of objects in memory. Therefore, thread safety is a must so that other parties do not access wrong memory locations when garbage collection is being performed. As the matter in fact, all managed code threads have to be suspended before the garbage collector can start its mission. In order to do this safely, the CLR has to take track of a lot of things in order to make sure the suspension does not hurt the thread when it has to be resumed after the garbage collection took place. This is based on so-called safe points. If a thread does not reach a safe point in a timely fashion, the CLR will perform a trick called thread hijacking to modify the thread's stack. To make things even more complicated, unmanaged code needs special treatment in some cases too. This would bring us too far, so I do refer to the book "Applied .NET Framework Programming" for more information about this.
  • Large objects (larger than about 85 KB) are allocated on a separate large object heap and start their lifecycle in generation 2. The reason for this special treatment is to reduce shifting of large memory blocks when performing garbage collection. As a result, it's recommended to use large objects only when these are long-lived.
  • Objects that are collected during a garbage collector run can have a finalizer (in C# defined by a destructor-like syntax; e.g. ~MyObject). Such an object has a Finalize method that should be called when the object is deleted and ends its lifecycle. The garbage collector uses a finalization list to determine whether an object needs finalization first. If that's the case, the object's pointer is put on a freachable queue (f stands for finalization). Such an object is not (!) considered to be garbage (yet) because it's still reachable (as the name tells us). A special thread in the CLR checks this queue on a regular basis to finalize the objects that are listed in there. During the next run of the garbage collector it can be determined which objects have now become real garbage because the finalization took place and the objects have disappeared from the freachable queue.

In this discussion I made one big assumption: the CLR knows which objects are not needed anymore. How does it do that? The mechanism that allows the CLR to do this is based on the concept of a root, which is "some location" that contains a memory pointer to an object. Examples are global variables, static variables, variables on the current thread stack and CPU registers that point to a reference type. When the Just-In-Time compiler does its work, it maintains an internal table which maps begin and end code offsets (with code I do mean native code, the result of jitting) to the root(s) of the method that's executing. During the execution of the native code, the garbage collector can be called (because a running-out-of-memory condition as explained above). It's clear that this will happen at a certain code offset. Each offset is embeddded in a region between a begin offset and end offset. By looking in the table created by the JIT compiler, a set of roots can be found. Beside of this table information we also do have thread stack information when the execution is interrupted by the garbage collecting process. Using the thread's call stack, the garbage collector can perform a thread stack walk to find the roots for all of the calling methods, again by using internal tables constructed by the JIT compiler at runtime for each method. Once this information is known, the garbage collector can create a reachable objects graph that is used to find out which objects are still needed. When recursing through this graph, object that are still in use are marked. Any unmarked objects after this phase are considered to be garbage (as these are not reachable starting from a root anymore) and therefore can be collected. In the last phase, the garbage collector walks over the managed heap and looks for large (to avoid little memory shifts that wouldn't give much of gain to the end result) contiguous regions of free space (i.e. unmarked garbage objects). When such a region is found, the garbage collector compacts the heap by shifting the objects in memory. When doing this, the roots are updated because memory addresses of the moved objects have changed of course.

I'll tell more about garbage collector in a later post when talking about memory management in CLR Hosting.

</INTERMEZZO>

Okay, now you should have some picture of how garbage collection works. Back to the difference between the server and workstation builds of the CLR. In the intermezzo I explained how threads have to be suspended so that the garbage collector (thread) can kick in to do its job. On server machines (I'll define the term "server" in a moment) we like to minimize the overhead of this garbage collection as much as we can. Assume you have a machine with multiple processors. In that case it's possible to run garbage collections in parallel on the machine. This is exactly what the server build supports. By default the workstation build will be loaded and the server build can't even be loaded when you don't have a multiprocessor machine. This explains the second parameter of the CorBindToRuntimeEx function. It can take the following two values:

  • wks
  • svr

Now, the third parameter of the CorBindToRuntimeEx function is related to the garbage collector too. For the workstation build, there is support for two different modi to run the garbage collector in. The first is the concurrent mode (STARTUP_CONCURRENT_GC) that will work on multiprocessor machines (but we're not running the server build of the CLR, remember that, we're talking about the workstation build specifically). In this mode, collections will happen concurrently in a background thread while the foreground threads are working. On a uniprocessor machine, collections happen on the same threads as the foreground code. Nonconcurrent mode does the collections in the same threads as the foreground code. The server build always uses nonconcurrent mode and nonconcurrent mode while running the workstation build is the recommended value for non UI-intensive apps (e.g. SQL Server 2005 uses nonconcurrent mode).

Warning: there is a trick to load the server build on non-supported configurations (see above) by specifying the "svr" parameter in combination with concurrent mode.

The default of concurrent collection in workstation builds is a nice one. If you want to disable this without going through the process of creating a full CLR Host, you can use a configuration file (<app.exe>.config) to specify the garbage collector's behavior in relation to this:

<configuration>
   <runtime>
      <gcConcurrent enabled="..." /> <!-- true is the default; useful when running in workstation build (default) to turn concurrent collection off, e.g. in batch processing apps -->
      <gcServer enabled="..." /> <!-- set to true to load the server build (not the default); however, if non on a multiproc, the workstation build will be loaded instead -->
   </runtime>
</configuration>

 

Domain-neutral code introduced

A last concept for now is the concept of domain-neutral code, of which the behavior can be set through the third parameter of CorBindToRuntimeEx too. The three possible options are:

STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN // no domain neutral loading
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN // all domain neutral loading
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST // strong name domain neutral loading

Now, what is domain-neutral code? Let's start with a refresh of the mechanism of DLLs in Windows. DLL stands for Dynamic Linked Library and contains a library of code that can be used by various applications on the machine. The good is the idea, the bad and the ugly is the DLL Hell as a result of versioning troubles. However, the concept is fine in a sense that when multiple applications use the same DLL at the same time, it's possible for the OS to keep the instructions of the dll only once in memory, therefore reducing the working set of the applications that use it. One copy of the code (which does not change) is sufficient for all apps that are dependent on it to execute.

Domain-neutral code is the equivalent in .NET of this kind of sharing of common code across multiple dependent applications. In managed code, things are a little more complicated however due to the JIT compiler. CLR Hosting allows you to customize the behavior of domain-neutral assembly code loading thorugh the startup parameters and through the implementation of the IHostControl interface which I'll explain later on in this episode. The three possible startup parameters dictate the CLR to disable all domain neutral loading (except for mscorlib, the core of the class library, which is always domain-neutral) or to enable domain neutral loading for all assemblies or some way in between based on strong named assemblies. In fact, all assemblies that are to be loaded domain-neutrally have to form a "closure", that is all referenced assemblies of a given domain-neutral loaded assembly have to be loaded domain-neutral too. These three default startup parameter values follow this rule.

Q&A: Why are not all assemblies loaded domain-neutrally to reduce the overall working set as much as possible? The answer is that once an assembly is loaded domain-neutrally, it can't be unloaded anymore without unloading the appdomain and the entire process. It's clear that this behavior is not desirable for CLR hosts such as ASP.NET or SQL Server 2005, where it should be possible to replace an assembly without restarting the server (service) to free resources.

 

Starting and stopping the CLR

The call to CorBindToRuntimeEx actually initializes the CLR. The result of this is a return parameter of the type ICLRRuntimeHost that can be used for further interaction with the CLR. The first call you'll make is a call to the method Start to start the mission of the CLR in your process. Once this is done, there is no real way back. Although you can stop the CLR by calling the method Stop, it's not possible to restart the CLR. Neither is it possible to completely unload the CLR from the process. Once the CLR has been in a process, it can't be reinitialized or restarted without creating a new process.

 

Delay load

To finish this post on CLR Hosting basics, I want to tell you something about delay loading. It's clear that loading the CLR takes some time to complete and maybe you come to the conclusion you have been doing this work for nothing, because during the lifetime of the process no managed code has to be executed. Suppose for example you want to offer managed code support in a database engine. It would be a waste of resources to load the CLR always by default when the database engine starts if nobody needs it later on. Instead, it would be nicer to be able to load the CLR when it's needed to do so (e.g. because of a COM component that calls managed code). For that purpose the CLR Hosting API provides some mechanisms to defer loading.

The first (easy) way to defer loading is to prepare the loading but wait until it's actually needed. This is called deferred startup and can be initiated by using the STARTUP_LOADER_SETPREFERENCE flag as the third parameter of the CorBindToRuntimeEx function. The only thing this does is saving the passed value for the version parameter. When the CLR needs to be loaded, that version will be used (e.g. by calling CorBindToRuntimeEx a second time). This is however very limited in a sense that you can't control other startup parameters for the CLR.

To support that, a function called LockClrVersion is available in the startup shim (see mscoree.IDL) that takes a callback to a function that needs to be called when the real initialization takes place, giving you as a host a chance to manipulate various settings. The signature looks as follows:

STDAPI LockClrVersion(FLockClrVersionCallback hostCallback,FLockClrVersionCallback *pBeginHostSetup,FLockClrVersionCallback *pEndHostSetup);

The first parameter is very straightforward. The two next parameters contain pointers to callback functions (provided to us by the shim) that are to be called before and after initialization to allow housekeeping by the CLR to know which thread is the owner of the initialization to control the overall (exclusively-granted-to-one-thread) loading process and to block any managed code requests that could interfere with this. Skeleton code for lazy CLR loading takes the following form:

FLockClrVersionCallback begin_init, end_init;

STDAPI init()
{
   //we're in control; notify the shim to grant us the exclusive initialization right
   begin_init();

   //CorBindToRuntimeEx stuff goes here

   //mission completed; tell the shim we're ready
   end_init();
}

void prepare_init()
{
   //tell the shim we want to take control when needed
   LockClrVersion(init, &begin_init, &end_init);

   //wait till something happens that causes the CLR to be loaded
}

 

Conclusion

Initialization and startup of the CLR is fully customizable as you saw in this post, even in a delayed loading scenario. In the next posts I'll show how to control the behavior of the CLR even further using self-written CLR Hosting API implementations.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

As from .NET v2.0 on, the System.Security namespace has a series of classes that allow you to control ACLs and other related security aspects of various objects on the operating system, with the most common example being files. When exploring this stuff I came across one of the tricky properties of the ownership mechanism in Windows concerning Administrators. For normal users, ownership of objects is user-bound. However, for members of the Administrators group the owner of the created object will be set to the Administrators group instead of the user account of the object creator. So, as a member of the Administrators group you're automatically sharing ownership with all other members of that group.

A second caveat of working with object ownership is the use of the "Full Control" access right. When you grant someone this right, you're implicitly granting him/her the right to "Take Ownership" over the object you've been editing the ACLs for. Yet another place where the maximum possible setting isn't the best choice, even worse: you should try to avoid it whenever you can, just as you should avoid running as an administrator.

Now, why this post on access control and ownership? A couple of years ago I had to implement some account provisioning mechanism that had to create folders for users. As part of it, there was an extension that allowed to migrate users' data from older systems to the new system. However, that copy operation was running in the context of some (configurable) service account in the background after calling the migration processor through .NET Remoting from a front-end application where a user with sufficient rights had to start the process. Starting with some impersonation stuff, followed by a lot of concerns around access rights on the old to-be-migrated system, I ended up with one last problem: assigning file ownership to the user in the new domain. And actually that part of the development took the majority of the time (P/Invoke all the time). Luckily .NET v2.0 has made this a lot easier to do in managed code:

SecurityIdentifier sid = ...;
FileSecurity fs = File.GetAccessControl(file);
fs.SetOwner(sid);
File.SetAccessControl(file, fs);

That's about it. Maybe I'll pick up the development of that piece of software again later on if it needs to be migrated to v2.0 at some point in time.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

This is the first real post in my "CLR Hosting" blogseries that's coming up in the next couple of weeks (or months?). CLR Hosting is in my opinion a great API in a sense that it allows third parties to integrate the CLR in their products. Well, that's completely true of course, but what's the value "normal" developers? My personal answer to this question is: you can learn a lot of how the CLR actually works by taking a look at this API. It also helps you to understand how SQL Server 2005 can adopt the CLR in the database engine in such a way that the CLR behaves in exactly the way the SQL OS folks want it to behave (compare with "parental control").

 

The CLR's execution engine

Let's start by taking a look at the basics of the CLR when it comes to executing code. The central file in this story is mscoree.dll (Component Object Runtime Execution Engine), which contains the execution engine. Well, that's not completely true actually. Mscoree is called the "startup shim" and is unique on the machine, regardless of the number of side-by-side installations of the .NET Framework (e.g. 1.0.3705, 1.1.4322, 2.0.x). You'll find the file in the system32 folder on your system. It's the task of the mscoree.dll file to hand over execution to a specific version of the CLR depending on a number of factors. Such an installation of a version of the CLR contains a bunch of files starting with mscor, such as:

  • mscorwks.dll - the workstation version of the CLR
  • mscorsvr.dll - the server version of the CLR (I'll talk about the workstation and server versions in a later post when talking about the garbage collector etc)
  • mscorlib.dll - contains a part of the System namespace of managed classes (e.g. System.Activator is in there, whileas System.Uri lives in the System.dll assembly); this file contains low-level functionality that has a close relationship with the CLR itself (e.g. code to support concepts such as application domains)
  • mscorjit.dll - the just-in-time compiler of the CLR to compile IL-code to native code at runtime

You can find all these files in the Microsoft.NET\Framework folders in your Windows directory. As you can have multiple different versions of the CLR on one machine, it's the job of the startup shim (which is not installed on a version-per-version basis) to load a specific version of the CLR and to hand over execution to that particular version.

Now, how does the CLR get loaded when a managed assembly is started? The answer depends on the operating system you're running. Let's start at the end of the story: mscoree.dll contains an "entry-point" for managed execution of an assembly, called _CorExeMain (and _CorDllMain). This function has to be called to hand over execution of a managed assembly (which is wrapped inside a standard PE - portable execution format - file) to the CLR. Machines with Windows XP and Windows Server 2003 know how to recognize a managed assembly and call this function directly when such an assembly is loaded by the PE operating system loader. On other versions of the Windows operating system, a small launch routine is inserted in the PE-file to hand over control to the CLR, by calling the _CorExeMain function. You can find another post on this subject on http://blogs.wwwcoder.com/rajaganesh/archive/2005/06/30/5386.aspx. To find out about a PE file containing managed code, XP and W2K3 (and later) check the "COM Descriptor Directory" entry in the file header. You can take a look at this information yourself by using the dumpbin tool with the switch /headers:

C:\Documents and Settings\BartDS>dumpbin /headers hello.exe
Microsoft (R) COFF/PE Dumper Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file hello.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
               2 number of sections
        422F31FB time date stamp Wed Mar 09 18:27:23 2005
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             10E characteristics
                   Executable
                   Line numbers stripped
                   Symbols stripped
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic # (PE32)
            8.00 linker version
             400 size of code
             200 size of initialized data
               0 size of uninitialized data
            22DE entry point (004022DE)
            2000 base of code
            4000 base of data
          400000 image base (00400000 to 00405FFF)
            2000 section alignment
             200 file alignment
            4.00 operating system version
            0.00 image version
            4.00 subsystem version
               0 Win32 version
            6000 size of image
             200 size of headers
               0 checksum
               3 subsystem (Windows CUI)
             400 DLL characteristics
                   No safe exception handler
          400000 size of stack reserve
            1000 size of stack commit
          100000 size of heap reserve
            1000 size of heap commit
               0 loader flags
              10 number of directories
               0 [       0] RVA [size] of Export Directory
            228C [      4F] RVA [size] of Import Directory
               0 [       0] RVA [size] of Resource Directory
               0 [       0] RVA [size] of Exception Directory
               0 [       0] RVA [size] of Certificates Directory
            4000 [       C] RVA [size] of Base Relocation Directory
               0 [       0] RVA [size] of Debug Directory
               0 [       0] RVA [size] of Architecture Directory
               0 [       0] RVA [size] of Global Pointer Directory
               0 [       0] RVA [size] of Thread Storage Directory
               0 [       0] RVA [size] of Load Configuration Directory
               0 [       0] RVA [size] of Bound Import Directory
            2000 [       8] RVA [size] of Import Address Table Directory
               0 [       0] RVA [size] of Delay Import Directory
            2008 [      48] RVA [size] of COM Descriptor Directory
               0 [       0] RVA [size] of Reserved Directory


SECTION HEADER #1
   .text name
     2E4 virtual size
    2000 virtual address (00402000 to 004022E3)
     400 size of raw data
     200 file pointer to raw data (00000200 to 000005FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         Execute Read

SECTION HEADER #2
  .reloc name
       C virtual size
    4000 virtual address (00404000 to 0040400B)
     200 size of raw data
     600 file pointer to raw data (00000600 to 000007FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         Read Only

  Summary

        2000 .reloc
        2000 .text

C:\Documents and Settings\BartDS>

Make sure you're using the 7.0 version of dumpbin as in earlier versions the "COM Descriptor Directory" is still called "Reserved Directory" and there are a couple of these reserved directory entries in the list :-). The 7.0 (and higher) versions also have a switch /CLRHEADER that is useful to display the CLR-header that's embedded in a PE file:

C:\Documents and Settings\BartDS>dumpbin /clrheader hello.exe
Microsoft (R) COFF/PE Dumper Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file hello.exe

File Type: EXECUTABLE IMAGE

  clr Header:

              48 cb
            2.00 runtime version
            2068 [     224] RVA [size] of MetaData Directory
               1 flags
         6000001 entry point token
               0 [       0] RVA [size] of Resources Directory
               0 [       0] RVA [size] of StrongNameSignature Directory
               0 [       0] RVA [size] of CodeManagerTable Directory
               0 [       0] RVA [size] of VTableFixups Directory
               0 [       0] RVA [size] of ExportAddressTableJumps Directory


  Summary

        2000 .reloc
        2000 .text

C:\Documents and Settings\BartDS>

On a non-managed file, you won't get any information other than the summary when running a dumpbin /clrheaders against the file.

 

Introduction to the CLR Hosting API

Now it's time to JMP to the real stuff. As I've explained, mscoree.dll is responsible to load a specific version of the CLR and thus to tell that particular version how to initialize. The mscoree.dll version will always be the version of the most recent CLR version running on your system and has to maintain things as backward compatible as possible as the file is subject to the old "COM DLL Hell" (it resides in system32 and has to be registered on the system). In the bin\include subdirectory of your SDK installation folder of that particular most recent version of the .NET Framework, you'll find a mscoree.idl file that contains the public export information of the functions inside the library. For version 2.0 of the .NET Framework you'll find a section marked with:

//*****************************************************************************
// New interface for hosting mscoree
//*****************************************************************************

The stuff in this section will be the subject of this and upcoming posts in the "CLR Hosting" series, i.e.:

interface ICLRRuntimeHost : IUnknown

The functions that go in there will be explained later on, but some of these should look familiar: Start, Stop, ExecuteApplication, etc. Others give access to the IHostControl object that can be used to configure the CLR prior to startup. The general principle of writing a CLR Host is implementing interfaces that start with IHost. These implementations contain your code to tell the CLR how to behave and allow you to control the overall behavior of the CLR that you want to control in your specific scenario. Examples are assembly loading, memory management, threading and locking, and so on. A more complete list follows. On the other hand there are a bunch of interfaces that start with ICLR which indicates that the CLR itself is responsible to provide an implementation. So, how do we get an instance of an ICLRRuntimeHost object to kick off with our hosting stuff? The answer is CorBindToRuntimeEx, which is the function to load the CLR in a process. Open up mscoree.h and you should find this line:

STDAPI CorBindToRuntimeEx(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);

The first parameter takes the version (e.g. L"v2.0.40607"), the second one the build flavor being workstation or server (e.g. L"wks" for the workstation build, more information follows later when talking about the GC), next we have a DWORD variable for startup flags of the following enum:

// By default GC is non-concurrent and only the base system library is loaded into the domain-neutral area.
typedef enum {
  STARTUP_CONCURRENT_GC         = 0x1,

  STARTUP_LOADER_OPTIMIZATION_MASK = 0x3<<1,                    // loader optimization mask
  STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN = 0x1<<1,           // no domain neutral loading
  STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN = 0x2<<1,            // all domain neutral loading
  STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST = 0x3<<1,       // strong name domain neutral loading


  STARTUP_LOADER_SAFEMODE = 0x10,                               // Do not apply runtime version policy to the version passed in
  STARTUP_LOADER_SETPREFERENCE = 0x100,                         // Set preferred runtime. Do not actally start it

  STARTUP_SERVER_GC             = 0x1000,                       // Use server GC
  STARTUP_HOARD_GC_VM           = 0x2000,                       // GC keeps virtual address used
  STARTUP_LEGACY_IMPERSONATION             = 0x10000,                        // Do not flow impersonation across async points by default
} STARTUP_FLAGS;

I'll explain the difference between the concurrent_gc and server_gc later on. The next two parameters take some information about the runtime host, more specifically the CLSID and the IID of the interface:

EXTERN_GUID(CLSID_CLRRuntimeHost, 0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x01);
EXTERN_GUID(IID_ICLRRuntimeHost, 0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x01);

Finally, the last parameter is the one you need to obtain an object to work with. It's a pointer to an object that will contain the ICLRRuntimeHost instance that can be used to do, well all of the stuff that's mentioned in the IDL definition for this interface. So, the way to use it is to pass in an address of a pointer of the type ICLRRuntimeHost*, with some casting (C++ you know :-)). The overall result with my installation of the .NET Framework v2.0 looks like this:

ICLRRuntimeHost *pHost = NULL;
HRESULT res = CorBindToRuntimeEx(L"v2.0.40607", L"wks", STARTUP_SERVER_GC, CLSID_CLRRuntimeHost, IID_CLRRuntimeHost, (PVOID*) &pHost);

Once this code has been executed the CLR has been loaded in the process space and is ready to be launched but still waiting on a Start command. Before calling start you can actually manipulate the CLR's settings as I'll explain in much more detail later on. To start the CLR, just call Start:

pHost->Start();

 

Who's implementing what?

Time for the discovery phase. In the previous section I explained briefly that it's possible to take responsibility for certain CLR-related functionality by implementing certain interfaces. This way, you just implement what you need. For example, you might need more control over memory allocation whileas you don't need to control threading in your scenario. This flexibility results in another complexity however and that's the problem of discovery: finding out who implements what. For example, the CLR needs to know what responsibilities the host wants to take over. For that purpose, there's an interface called IHostControl:

interface IHostControl : IUnknown
{
    HRESULT GetHostManager(
        [in] REFIID riid,
        [out] void **ppObject);

    /* Notify Host with IUnknown with the pointer to AppDomainManager */
        HRESULT SetAppDomainManager(
        [in] DWORD dwAppDomainID,
        [in] IUnknown* pUnkAppDomainManager);

    HRESULT GetDomainNeutralAssemblies(
        [out] ICLRAssemblyReferenceList **ppReferenceList);
}

We're interested in the first method, being GetHostManager. It's your task as a CLR hosting developer to implement this interface and this method. When the CLR is started and the host control is bound to the CLR runtime host object, the CLR initiates a dialog based on a series of IIDs for everything the host can take responsibility for. To put this in simple words, a dialog like this is going on:

  • Host to CLR runtime host (pHost): here's the host control object (MyHostControl)
  • CLR to host control object (MyHostControl) via method GetHostManager:
    • Hi there, do you implement a memory manager? If so, please give me a reference to it?
    • Hi there, do you implement a garbage collector manager? If so, please give me a reference to it?
    • Hi there, do you implement a thread pool manager? If so, please give me a reference to it?
    • and so on...

What you have to do, is implementing the IHostControl interface and give an implementation for the GetHostManager method that looks like this:

HRESULT __stdcall MyHostControl::GetHostManager(REFIID id, void **ppHostManager)
{
   if (id == IID_IHost...Manager)
   {
      MyHost...Manager *p...Manager = new MyHost...Manager(); //implements IHost...Manager

      //other stuff to initialize the manager

      *ppHostManager = (IHost...Manager*) p...Manager;
      return S_OK;
   }
   else if (id == IID_IHost...Manager)
   {
      //same story over here for this particular manager
   }
   else if (id == IID_IHost...Manager)
   {
      //same story over here for this particular manager
   }
   else
   {
      *ppHostManager = NULL;
      return E_NOINTERFACE; //tell the CLR we don't take care for the requested manager
   }
}

In this case, initialization looks as follows:

ICLRRuntimeHost *pHost = NULL;
HRESULT res = CorBindToRuntimeEx(L"v2.0.40607", L"wks", STARTUP_SERVER_GC, CLSID_CLRRuntimeHost, IID_CLRRuntimeHost, (PVOID*) &pHost);
MyHostControl *pHostControl = new MyHostControl();
pHost->SetHostControl((IHostControl*) pHostControl);

If you as a host want to know what the CLR's managers are for various tasks, the process works in a similar fashion. First, you do obtain a reference to the ICLRControl (just substitute Host with CLR and you're usually right):

ICLRControl pCLRControl = NULL;
pHost->GetCLRControl(&pCLRControl);

Next, you use the IID_ICLR...Manager values to ask the CLR for a particular manager. You'll find the complete list of managers in the mscoree.idl file when looking for IID_ICLR...Manager values in there. The skeleton looks like this:

ICLR...Manager *p...Manager = NULL;
pCLRControl->GetCLRManager(IID_ICLR...Manager, (void**) &p...Manager);

I won't give an overview of the various managers you can decide to implement, as I'll focus on these individually later on.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

More Posts « Previous page - Next page »