June 2008 - Posts

At the beginning of this month, we released the first CTP of Velocity, an early preview of our distributed object cache solution. You can download it here. Notice it's a very early preview so things will definitely change moving forward. This post introduces how to install and use Velocity.

 

Introduction

But first... what's in a name? Multi-tiered distributed applications are common-sense nowadays and with cloud computing within reach the need to build scalable distributed services has never been bigger. One of the core aspects in enabling those scenarios is to have intelligent caching of objects, not only to reduce the number of accesses to the underlying data source but also to boost availability by employing scale out techniques. Obviously, developers want to be able to do all of this without having to worry about the complexities that this brings, having to deal with load balancing and availability themselves. That's where Velocity comes into play.

The core idea is very straightforward: we have a cache that behind the scenes is distributed and replicated across a bunch of machines called the cluster. Storing data in the distributed cache is as easy as calling some Add or Put method, and retrieving it is as easy as calling Get. With some creative stealing from the documentation we end up with the following picture:

image

An important thing to emphasize is the fact the cache clients deal with regular .NET objects all the time and don't have to worry about storing those objects. Indeed, .NET serialization takes care of the rest. There are more concepts to it such as cache eviction policies (when objects are removed from the cache, such as least-recently used or LRU), the distribution mechanism where simple clients just contact the cluster "in the cloud" through any cache host and get redirected to whatever host the object is available on versus routing clients that have awareness of object placement through a routing table. Other important pieces include the supported concurrency models and associated locking mechanisms but let's not go there in this introductory post.

 

Installation

Installing Velocity is fairly straightforward. Just run the MSI. After a while you'll see the following window:

image

This is where you configure the "cache host". Under Cluster Configuration Share you can enter the UNC path to the cluster's configuration share. This is the place where an XML file is kept that makes sure that configuration settings are consistent across all hosts in the cluster. Create this folder and grant the Everyone account full access to it; this is known issue in the CTP which by no means will become the final design. Also notice that currently in the first CTP this is a single point of failure: if the share goes down, the cluster can die. Obviously this will be addressed in subsequent releases. For now, we'll just specify a local path and enter a new name under "Cluster Name". The Cluster Size is self-explanatory and for the purpose of this introductory sample, we'll stick with a one-host cluster (a degenerate case of a cluster if you will...).

Next, two ports are being specified: the service port and the cluster port. The defaults are just fine here. Basically the service port number is what clients connect to in order to talk to the cache host. The cluster port on the other hand is used by the servers in the cluster to talk to one another (there's another port, called the arbitration port which is listening on cluster+1, i.e. 22235 in the sample above).

Last but not least, there's the Max Server Memory setting which defaults to half of the available physical memory. I've reduced it to 256 MB since I'm using my main dev machine as my playground but obviously in production scenarios boxes will get dedicated to the distributed cache cluster, where it makes sense to boost this.

After clicking Save & Close, setup will ask you to open the required ports on the firewall, which can be done easily by allowing the DistributedCache.exe program (that runs as a service) through the firewall.

image 

Doing so isn't hard at all. Geeks can go the netsh way as illustrated below. Alternatively one can use the Firewall Settings in the Control Panel (click to enlarge):

image image

With this, setup has completed.

 

Configuration

Before we can start to use the service, we need to make a few changes to its configuration. Let's take a look at the configuration share's file structure first:

image

The XML file contains the configuration that's shared by all hosts in the cluster. In addition the ConfigStore file is a SQL CE 3.5 database that keeps additional information about partitions, nodes, regions, etc which you can find more information about in the CTP's documentation. Notice that you won't find the cached data here since we're talking about an in memory distributed cache. Geeks can investigate what goes inside this little database, but we'll instead just focus on the XML file:

image

In here you can find the list of hosts and caches that are part of the cluster. We'll take a look again at this file in just a minute when we've altered the cluster configuration. In order to configure the service, Velocity comes with a command-line driven tool named originally the "Administration Tool" which you can find through the start menu (click to enlarge).

image

In the configuration steps below, we're adding a cache to the cluster after investigating the hosts and caches that are part of the cluster. Once we've added the cache, the cluster is started which puts all the hosts online by starting their Windows Services.

image

The service is called DistributedCacheService which is kept in the cacheHostName property in the XML file:

image

Taking a look back the XML file, you'll notice a new cache configuration entry has been added:

image

The new cache has an eviction type of Least Recently Used (LRU) which means that the - as the name implies - least recently used objects get evicted from the cache when necessary. In addition, objects expire from the cache after a time-to-live of 10 minutes. The type of the cache is set to partitioned allowing objects to be distributed across the hosts in the cluster.

 

Using it

After covering the server side, we should take a look at the client side picture of using Velocity. Here's the simple program we want to use:

namespace VelocityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            CacheFactory factory = new CacheFactory();
            Cache cache = factory.GetCache("Test");

            cache.Put("Name", "Bart De Smet");

            string name = (string)cache.Get("Name");
            Console.WriteLine(name);
        }
    }
}

Notice how we get access to the cache called Test through a factory object, after which we simply use a Put method to add an item (key, value pair) to the cache which subsequently can be retrieved (on any client connected to the cache) using the corresponding Get method. Obviously more complex serializable objects will be stored in the cache but this simply shows the main idea.

In order to make this work we need to add a couple of references to the project. Notice that in the CTP the number of client-side assemblies that need to be references on the client isn't optimized in any way, so here it goes (click to enlarge):

image

Core ones are CacheBaseLibrary and ClientLibrary. All of these carry the System.Data.Caching namespace:

image

But wait a minute, how can the client know where to find the cache retrieved through the factory? Indeed, it can't:

image

In order to make things work, the exception hints you to create a client configuration file. The documentation that comes with the CTP contains such a configuration file, so I won't paste it here but the key takeaway here is that mirrored nature of client-side and server-side files:

image

Basically, the client just needs to point at a host in the "distributed caching cloud" to gain access to the cache (depending on the type of client, things look a little different - in the sample above the deployment type is for a simple client - more information can be found in the documentation).

Once you've run the program you can take a look at the cluster's cache by means of the show cachestats command in the administration tool:

image

To see the cache-behavior in action, try caching the current time (DateTime.Now) and get it back. Here's a piece of sample code illustrating this (using indexers instead of Get/Put calls):

while (true)
{
    object data = DateTime.Now;
    Console.WriteLine("Add to cache: {0}", data);
    cache["Time"] = data;

    data = cache["Time"];
    while (data != null)
    {
        Console.WriteLine("Retrieved from cache: {0}", data);
        Thread.Sleep(10000);
        data = cache["Time"];
    }

    Console.WriteLine();
}

For sake of this demo, I've lowered the TTL to 1 minute (stop the cluster using stop cluster, change the TTL value in the config file and restart the cluster using start cluster). Here's a sample output:

image

You can clearly see how the data becomes null when the object has been evicted from the cache - this is an important thing: an application should never assume a cache hit will occur and be prepared to handle a cache miss. If such a miss occurs, the data in the cache can be refreshed if the client feels the need for it. Notice there's a method called ResetObjectTimeout that can be used to reset the TTL counter for an object specified through its key value.

 

More stuff

There's more to Velocity than just the stuff covered in the post. For example, there's the concept of regions that allows objects to be located on a specific node, offering additional search capabilities to find cached objects at the price of scalability across hosts. To handle concurrency when dealing with cached objects, Velocity supports optimistic and pessimistic locking semantics. And finally for ASP.NET applications, there's integration with session state. Obviously there's much more to come in upcoming CTPs but there's more than enough to explore already in this CTP.

Enjoy playing with Velocity (but as usual, keep in mind the CTP quality)!

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

In the last couple of posts in the series, we've been establishing the interop baseline ready to be used by the query provider layer on top, which we're about to build now. Since the goal of this blog series is to live an IQueryable-free live, for reasons explained earlier including demo-simplification goals, this post will introduce a series of "fluent classes" that provide a limited query pattern.

 

The query provider

At the entry-point for our API, we provide a query provider class. Its sole role (at least for now) is to provide access to the underlying MSI database through an MsiConnection object introduced previously. If one wants to support update tracking on the objects retrieved through the provider, the query provider context is the best place to store this tracking information since it can keep a global view on the different tables and their underlying relationships. Since MSI has some notion of relationships between tables, such an implementation could be a good idea but obviously one can avoid all sorts of update tracking whatsoever by directly updating records in the database; after all we don't have a huge price to pay for the "connection" since the MSI database is a local file (i.e. no network traffic cost etc). However, if the end-user expects transactional semantics when updating multiple records as a a way to ensure database consistency, the global view of a context object is a plus as well (although one has to put the provided consistency mechanisms of the underlying database in the equation, see MSDN on the Windows Installer Database to get an idea about the capabilities of the MSI database engine).

To summarize this discussion, we'll provide a query provider object as the entry-point, from which we'll derive to provide access to the various tables. Users of the API can derive from the context themselves to provide access to other custom tables as well. Here's how it looks:

public abstract class MsiQueryProvider : IDisposable
{
     private MsiConnection _conn;

     protected MsiQueryProvider(string file)
     {
          _conn = new MsiConnection(file);
     }

     public void Dispose()
     {
          if (_conn != null)
          {
               _conn.Dispose();
               _conn = null;
          }
     }
}

 

Representing tables

Next we have to talk about tables. MSI tables are fairly simple; they just have a name and there's not much more interesting metadata (apart from the fields they hold of course) associated to it. One caveat though is the fact the names are singular (like Property) so it might be tempting to introduce some metadata through custom properties to create a mapping. That would work, but to simplify manners we'll just have the name of the entity class stand for the table's name. Where does the entity class come into play? A generic parameter does the trick:

public class MsiTable<T> : IEnumerable<T>
{
    ...
}

Given the following entity class:

public class Property
{
     [MsiColumn("Property")]
     public string Name { get; set; }

     public string Value { get; set; }
}

we can construct a database context for our MSI database as follows:

public class MyMsi : MsiQueryProvider
{
     public MyMsi(string file) : base(file)
     {
          Properties = new MsiTable<Property>(this);
     }

     public MsiTable<Property> Properties { get; private set; }
}

which can be used like:

var ctx = new MyMsi("c:\\temp\\demo.msi");
var res = from p in ctx.Properties where p.Name == "ProductCode" select p.Value;

Notice the table-object implements the IEnumerable interface which obviously simply queries the entire table. Since it queries the table, internally it can use the same querying infrastructure as derived queries (which we'll talk about next).

 

The built-in query pattern

C# and VB provide query comprehensions built-in into the language, which translate in chains of method calls. I've been showing this a lot, but one more time for the query above:

var res = from p in ctx.Properties where p.Name == "ProductCode" select p.Value;

becomes

var res = ctx.Properties.Where(p => p.Name == "ProductCode").Select(p => p.Value);

But one could well write other queries using orderby (translates into OrderBy* and ThenBy* method calls), or leaving out certain parts (e.g. no where clause). Since we don't want to support all query operators (dropping the I in IQueryable, just keeping someting queryable) we'll stick with a fluent pattern that allows enough expressiveness and plays nicely together with the built-in comprehensions that are used typically (where, select, orderby). Depending how flexible you want to make a query provider, you can provide more or less of those operators with the ultimate end of the spectrum being a full IQueryable<T> (trading compile-time errors for runtime exceptions in case of not-supported operators).

What we're going to build in this blog series is in fact a little state machine that accumulates query information (you could therefore - not surprisingly - call it a query accumulator) that supports those three basic operators (restriction using where, ordering using orderby and projection using select) in different orders:

image

Each of those classes inherits from a common query base class that looks like this:

abstract class MsiQueryBase<T> : IEnumerable<T>
{
     protected QueryData _query;

     protected MsiQueryBase(QueryData query)
     {
          _query = query;
     }

     IEnumerator IEnumerable.GetEnumerator()
     {
          return GetEnumerator();
     }

     public IEnumerator<T> GetEnumerator()
     {
          return _query.Execute<T>();
     }
}

The accumulative nature lies in the QueryData class we'll outline below. Each time the query gets extended with an additional piece of information, a new QueryData object is created. The reason for this is to keep intermediate query objects available so that multiple views can be constructed. This is more of a general design choice than one that's crucial for the LINQ to MSI provider (since our provider is so limited this "viewing" mechanism would be less useful anyway). With views I mean things like:

var expensive = from p in products where p.UnitPrice > 10 select p;
var notTooExpensive = from p in expensive where p.UnitPrice < 100 select p;

The hidden piece of crucial information here is the fact the chain of method invocations silently continues, conceptually like:

var notTooExpensive = products.Where(p => p.UnitPrice > 10).Select(p => p).Where(p.UnitPrice < 100).Select(p => p);

If the second query were to modify the query data encapsulated by the first query, iterating the first query would yield the results of the second one. Or in other words, how immutability is a must in this case. Notice that IQueryable<T> doesn't have this problem because of the way expression trees are generated (or better: composed). With IQueryable, the first query would be an expression tree which the second query simply refers to. We could either mimic this behavior by composing all pieces of query information into an expression tree, or make our internal query data representation immutable. We'll stick with the last approach as shown further.

Also note that every select operation results in what's called a closed query. Why closed? Because the projection (possibly) looses all track of the original entity information, through anonymous types used in the projection. It's sort of a semi-permeable membrane which can cause everything that follows to be "lost in translation". An example:

var res = (from p in products select new { Name = p.ProductName, Price = p.UnitPrice }).Where(p => p.Price > 100);

If the target query language allows nesting of queries, there's no problem. For example in SQL one can write:

SELECT Price FROM (SELECT ProductName AS Name, UnitPrice AS Price FROM Products) WHERE Price > 100

but lots of other query languages don't allow such flexibility (and indeed, if we were to try this in MSI's SQL implementation, it wouldn't work). In order to reflect this restriction, we make the close the query once a projection has been carried out. If grouping were supported, that's typically subject to analogous restrictions of course again depending on the underlying data store's flexibility.

 

Query data

The query data object we're going to use to represent the pieces of a query encountered during the chaining of query operators is shown below. It simply consists of a predicate (from Where), a set of orderings (from OrderBy* and ThenBy* calls) and a projection (from Select) as well as the original entity type and a reference to the query provider in order to gain access to the underlying data store. Alternatively, one could mimic the Queryable behavior by constructing an expression tree on the fly using Expression.Call, specifying the current expression tree as the left-hand side (the "this" operand if you will) and the passed-in parameters (like the predicate parameter for Where) as the call operands. I'll come back to this later in this series; for now, we'll construct a simple object like this:

internal class QueryData : ICloneable
{
     public MsiQueryProvider Provider;
     public Expression Where;
     public List<OrderClause> Order;
     public Expression Select;
     public Type EntityType;

     public QueryData()
     {
          Order = new List<OrderClause>();
     }

     public object Clone()
     {
          return new QueryData() { Provider = Provider, EntityType = EntityType, Where = Where, Order = new List<OrderClause>(Order), Select = Select };
     }

     public IEnumerator<T> Execute<T>()
     {
          yield break;
     }
}

Remember C#'s public is only as public as the container, so don't blame me for public fields (or feel free to add automatic property syntax on my behalf). Notice the internal object isn't immutable by itself, something you can debate about of course (or feel free to modify the constructor and add automatic property syntax with private setters on my behalf - just a matter of distributing work between the writer and the readers :-)), but we keep control over it ourselves (the "circle of trust"). Also notice that Expression objects are immutable by their design. Obviously, Execute will be where the real work is to be done, but we keep that for a future post.

 

Back to the Table<T>

Now that we have the query object and the idea of the fluent pattern, we can return to the MsiTable<T> object:

public class MsiTable<T> : IEnumerable<T>
{
     private QueryData _query;

     public MsiTable(MsiQueryProvider provider)
     {
          _query = new QueryData() { Provider = provider, EntityType = typeof(T) };
     }

     IEnumerator IEnumerable.GetEnumerator()
     {
          return GetEnumerator();
     }

     public IEnumerator<T> GetEnumerator()
     {
          return _query.Execute<T>();
     }

     public MsiQuery<T> Where(Expression<Func<T,bool>> predicate)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Where = predicate;

          return new MsiQuery<T>(query);
     }

     public MsiOrderedQuery<T> OrderBy<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = false });

          return new MsiOrderedQuery<T>(query);
     }

     public MsiOrderedQuery<T> OrderByDescending<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = true });

          return new MsiOrderedQuery<T>(query);
     }

     public MsiClosedQuery<T, R> Select<R>(Expression<Func<T,R>> projection)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Select = projection;

          return new MsiClosedQuery<T, R>(query);
     }
}

The constructor is straightforward, and so is the implementation of IEnumerable<T>, simply asking the query data to execute the query. Actually this code is in common with the MsiQueryBase<T> class, so you could use it as the base class here if you want. However, and this is again philosophical, a table by itself is not a query - it just so happens to be that internally we fetch results using the same query infrastructure upon enumeration of the table, which votes against using MsiQueryBase<T> as the base class here.

More interesting are the Where, OrderBy* and Select methods. The most important thing here is the signature:

public MsiQuery<T> Where(Expression<Func<T,bool>> predicate)
public MsiOrderedQuery<T> OrderBy<K>(Expression<Func<T,K>> orderClause)
public MsiOrderedQuery<T> OrderByDescending<K>(Expression<Func<T,K>> orderClause)
public MsiClosedQuery<T, R> Select<R>(Expression<Func<T,R>> projection)

All are little expression trees, representing the lambda passed in as the argument. These are captured in a clone of the QueryData object and wrapped inside another MsiQueryBase<T>-derived object, as depicted above in our state machine diagram. To distinguish between ascending and descending ordering, an OrderClause with a Descending property either set to false or true is added to the list of order clauses. This is an ordered list because obviously primary and n-ary (where n >= 2, represented by subsequent ThenBy* calls, see below) orderings are by themselves ordered.

 

Query, OrderedQuery and ClosedQuery

Finally we've arrived with the other query classes. Starting with MsiQuery which represents a query capturing a predicate (Where):

public class MsiQuery<T> : MsiQueryBase<T>
{
     internal MsiQuery(QueryData query) : base(query)
     {
          _query = query;
     }

     public MsiOrderedQuery<T> OrderBy<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = false });

          return new MsiOrderedQuery<T>(query);
     }

     public MsiOrderedQuery<T> OrderByDescending<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = true });

          return new MsiOrderedQuery<T>(query);
     }

     public MsiClosedQuery<T, R> Select<R>(Expression<Func<T,R>> projection)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Select = projection;

          return new MsiClosedQuery<T, R>(query);
     }
}

Actually there's nothing new in here. All methods have been seen before in exactly the same shape as before. This method-level cloning is the result from trading IQueryable's endless composability for compile-time verified limited composition in our fluent pattern. Obviously, there are ways to "share" the common log (which in this case results in 3 shared lines of code per method). The reader should feel free to perform this refactoring. The core take-away is the limited number of methods per query class, in this particular case there's no Where method (since an object of this type itself is the result of a Where-application in the depicted state machine). If we were to support multiple Where-calls we'd have to AND them together, which wouldn't be a problem since Where and OrderBy*/ThenBy* operations are mutually commutative (i.e. you can put them everywhere in the chain of method calls, as long as you don't pass a Select boundary which introduces tricky nested queries as outlined above), but we deliberately choose to restrict the implementation in the scope of this series.

The MsiOrderedQuery<T> class is a little different since two new methods, ThenBy and ThenByDescending are introduced. However, those are no rocket science either:

public class MsiOrderedQuery<T> : MsiQueryBase<T>
{
     internal MsiOrderedQuery(QueryData query) : base(query)
     {
          _query = query;
     }

     public MsiClosedQuery<T, R> Select<R>(Expression<Func<T,R>> projection)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Select = projection;

          return new MsiClosedQuery<T, R>(query);
     }

     public MsiOrderedQuery<T> ThenBy<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = false });

          return new MsiOrderedQuery<T>(query);
     }

     public MsiOrderedQuery<T> ThenByDescending<K>(Expression<Func<T,K>> orderClause)
     {
          QueryData query = (QueryData)_query.Clone();
          query.Order.Add(new OrderClause { Mapper = orderClause, Descending = true });

          return new MsiOrderedQuery<T>(query);
     }
}

And finally, there's our closed query object, its closed nature being reflected by the lack of further query operators.

public class MsiClosedQuery<T, R> : MsiQueryBase<R>
{
     internal MsiClosedQuery(QueryData query) : base(query)
     {
          _query = query;
     }
}

In fact, this isn't the end of everything: the object still is an IEnumerable`1, so the user can continue to query using LINQ to Objects if System.Linq is in scope. The careful reader will have noticed this from the very beginning when MsiQueryBase<T> was introduced. For LINQ to MSI, we can see this as a feature but for "remotable" kinds of LINQ providers, I'd consider this a bug in the provider implementation. Why? The compiler would silently fall back to LINQ to Objects operators and you could well take a "bad start":

var res = from p in products join c in categories on p.Category equals c ...

Assume products and categories both represent a remote object but the provider doesn't support a GroupJoin operation. If the objects still derive from IEnumerable<T>, the LINQ to Objects operators will be in scope and you've silently created a local LINQ query that will suck down all objects from both tables for a client-side join. In LINQ to MSI such problems are not that much of a deal since the "remote database" isn't really remote; it's just a local file. And obviously, any logging mechanism on the provider (.Log property typically) will reveal the query sent to the database, so if that shows a "SELECT * fetch all" kind of query, it should trigger suspicion. IQueryable's AsEnumerable acts as an explicit boundary for remote to local execution and solves this problem.

There are ways around this problem of course (apart from buying in to the whole IQueryable stuff). For example, you could require explicit execution by means of an Execute method that returns the enumerator - a little less user-friendly but it rescues the mission anyway. Notice I said to return an "enumerator", not a memory-persisted list with all the pre-fetched results - this leaves room for lazy fetching of results through a data reader object which can be stopped at any moment without wasting network resources (e.g. you might still have a client-side Take(5) operation which would only cause at most five calls to *DataReader.Read()).

 

Known limitations

The query structure for our LINQ to MSI implementation supports Where, OrderBy*, ThenBy* and Select operators. MSI supports the following:

SELECT [DISTINCT]{column-list} FROM {table-list} [WHERE {operation-list}] [ORDER BY {column-list}]

which apparently only leaves out the DISTINCT operator (which is trivial to implement). However, there's one more restriction hidden in the FROM clause: {table-list}. We won't support joins in this implementation (for now) although MSI has limited support for them:

Only inner joins are supported and are specified by a comparison of columns from different tables. Circular joins are not supported. A circular join is a SQL query that links three or more tables together into a circuit.

I'll come back to this in a later post, outlining what it would take to implement support for this.

 

Next time...

...we'll start the translation battle. Stay tuned!

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

A rather unexpected intermezzo in this series. Why? The first rule of blogging is that blog readers are always right, and this time it was no different. Although I pointed out yesterday "We're just doing raw interop here without more fancy SafeHandle stuff or so, but feel free to fine-tune the interop layer." there's no way for me to answer for example Steven's question:

Bart, Can you explain why to chose to implement finalizer methods on your classes instead of using a from SafeHandle inherited class to close the handle?

apart from the lazy answer "It's only a sample, dude!". But I definitely agree, quality needs to come first especially when you have the luxury to sit down and write the code in a relaxed atmosphere (as opposed to on-the-fly coding in an on-stage setting at some conference, not that such a setting wouldn't be relaxed of course...). I'm not completely guilty though, since I promised myself to fix this fit-and-finish issue by the time I'd publish the sources for this series. I was under the impression to have written about SafeHandles somewhere in the Whidbey timeframe but couldn't find a trace of it, so likely it never grew out of the draft stage. Therefore, this looks like an ideal opportunity to talk a bit about SafeHandles.

 

What's a SafeHandle?

The pattern I used in my yesterday's post is an old-fashioned one. The IDisposable part of it is still relevant and allows you to dispose off resources in a controllable fashion, e.g. by means of a using block in C# or VB. The reason I call it controllable is the fact you don't rely on the garbage collector to clean up the object, using the finalizer (the thing with destructor syntax in C# which was a little unfortunate choice of syntax). The fact I still have a finalizer implemented is the old-fashioned part of it. Why is that? In short, there are too little guarantees provided by finalizers (and the runtime having the task of calling them) to ensure reliability in all circumstances. During the v2.0 "Whidbey" timeframe, the .NET Framework team fixed this and there was the lucky coincidence of SQL Server 2005 "Yukon" looking into running managed code inside the server for what later became SQLCLR, by hosting the CLR inside the server process which obviously needs to provide strong guarantees about the stability and overall behavior of the CLR and managed code being run by it.

This whole reliability requirement isn't just realized through one single feature. Indeed, there are many pieces working together to establish this:

  • Host Protection Attributes - categorizes functionality of frameworks so that hosts like SQL Server can allow or disallow certain categories of code to be executed (e.g. no WinForms in SQLCLR)
  • Critical Finalizers - ensure that the finalizer gets executed properly, even under extreme circumstances (guaranteed to run during app domain unload, which means app domains can't leak handles and can be used as recycle units in case the application - e.g. SQL Server - gets into troubles)
  • Constrained Execution Regions - regions of code with guarantees that no asynchronous exceptions will be thrown by the runtime (the runtime ensures those will only happen before or after the region); a CER also puts strong restrictions on the developer about what can be used inside the region (which makes it hard to use them correctly directly), therefore providing more control over what specific pieces of code do

The core problem with old-fashioned IntPtr-based interop is the fact handles could be leaked in case asynchronous exceptions occur (an example is a rude thread abort causing ThreadAbortException). In addition to this, there's also a security risk by means of so-called recycle attacks. To understand this issue, you need to know that handles are just integer numbers kept on a per-process basis, pointing at some object maintained by the system. Once a handle is freed, that number becomes available again to point at some other resource, so once a new handle is obtained by some other function call it might well get the same number. If the old handle value is still kept around, incorrectly assuming it still points at the original resource, it now points at a possibly security-sensitive resource. This is another argument why it's a good practice (amongst others) to pair every call to CloseHandle with a zero-out assignment on the handle being closed (operating on an invalid handle also has the chance to surface obscure problems earlier than operating on a stale handle value that in the meantime might point to something else).

Finally, SafeHandles are implemented using Critical Finalizers and hence provide a way to ensure proper clean-up. The essence of a SafeHandle is to act as a wrapper for an IntPtr but one which the CLR knows about, so that the runtime can do the right thing to release the handle (not surprisingly there's a ReleaseHandle method).

I can't possibly cover all details on those features here and others have done it already, so if you want to learn more, here are some good pointers:

 

MsiSafeHandle

Here's a dump of an MsiSafeHandle implementation, deriving from SafeHandleZeroOrMinusOneIsInvalid (meaning that IntPtr values wrapped by it are invalid when they are -1 or 0, something implemented inside the IsInvalid property getter).

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class MsiSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
     private MsiSafeHandle() : base(true) /* take ownership of the handle */
     {
     }

     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
     override protected bool ReleaseHandle()
     {
         return 0 == MsiInterop.MsiCloseHandle(handle);
     }
}

The CLR knows that whenever this class is used in an interop signature, it can create an instance of this class, adding the IntPtr handle value to it. When the runtime (or the application itself by calling Dispose on the object explicitly) decides the handle needs to be cleaned up, it calls ReleaseHandle which we implement by calling our interop function. Notice the ReliabilityContract attribute as well, which is part of CER (System.Runtime.ConstrainedExecution namespace), exposing the guarantees the method implementation makes (something that can't be enforced by the runtime and is still the responsibility of the developer).

 

Revised interop signatures

For our interop class, some changes are required to use the MsiSafeHandle class instead of IntPtr and to attribute the CloseHandle method with a CER attribute:

[SuppressUnmanagedCodeSecurity]
internal static class MsiInterop
{
     [DllImport("msi.dll", EntryPoint="MsiOpenDatabaseW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiOpenDatabase(string databasePath, int persist, out MsiSafeHandle database);

     [DllImport("msi.dll", EntryPoint="MsiDatabaseOpenViewW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiDatabaseOpenView(MsiSafeHandle database, string query, out MsiSafeHandle view);

     [DllImport("msi.dll", EntryPoint="MsiViewExecute", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiViewExecute(MsiSafeHandle view, MsiSafeHandle parameters);

     [DllImport("msi.dll", EntryPoint="MsiViewFetch", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiViewFetch(MsiSafeHandle view, out MsiSafeHandle record);

     [DllImport("msi.dll", EntryPoint="MsiRecordGetStringW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiRecordGetString(MsiSafeHandle record, uint field, StringBuilder value, ref int bufferSize);

     [DllImport("msi.dll", EntryPoint="MsiRecordGetInteger", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern int MsiRecordGetInteger(MsiSafeHandle record, uint field);

     [DllImport("msi.dll", EntryPoint="MsiCloseHandle", CharSet=CharSet.Unicode, ExactSpelling=true)]
     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
     public static extern uint MsiCloseHandle(IntPtr handle);
}

Notice the MsiCloseHandle function is the only one which still takes an IntPtr. This is required because we still need to call this function directly inside our MsiSafeHandle class. However, other than in that particular spot, direct calls to MsiCloseHandle shouldn't be made anymore, since the handle will always be wrapped inside an MsiSafeHandle which implements IDisposable, ultimately calling into ReleaseHandle to do the heavy work.

 

Using it

I'll just give one sample here, based on our MsiConnection class. Other classes can be changed completely analogously:

public sealed class MsiConnection : IDisposable
{
     private string _fileName;
     private MsiSafeHandle _database;
     private bool _disposed;

     public MsiConnection(string fileName)
     {
          if (!File.Exists(fileName))
          {
               throw new FileNotFoundException(fileName + " not found.");
          }

          _fileName = fileName;
     }

     public void Open()
     {
          CheckDisposed();

          uint res = MsiInterop.MsiOpenDatabase(_fileName, 0, out _database);
          if (res != 0)
          {
               throw new Exception("Failed to open database. Error code = " + res);
          }
     }

     internal MsiSafeHandle Handle
     {
          get { CheckDisposed(); return _database; }
     }

     public void Dispose()
     {
          Dispose(true);
          GC.SuppressFinalize(this);
     }

     private void Dispose(bool disposing)
     {
          if (!_disposed && _database != null && !_database.IsInvalid)
          {
               _database.Dispose();
          }

          _disposed = true;
     }

     private void CheckDisposed()
     {
          if (_disposed)
              throw new ObjectDisposedException("MsiConnection");
     }
}

Notice we still are IDisposable, nothing's changed there. However, now we're storing MsiSafeHandle instead of an IntPtr which gets disposed by our Dispose method (but also automatically during critical finalization, so we get the guarantees we've always wanted). Depending on the class design you prefer for the Msi* classes, the helper Dispose method would either be private or protected virtual. In this case, I'm sealing the class which means the helper method should be private. If you were to choose otherwise, the helper method should be protected virtual and be decorated with a Demand for unmanaged code security permissions.

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

PattThis should really be the least interesting part of this series. But still, without any physical data access layer, there's no way to build abstractions on top of it. So, in this post of this series we'll take a look at some very simple MSI interop, giving us a basic data provider for MSI databases, mirrored after the typical structure of .NET data providers like System.Data.SqlClient.

 

Interop signatures

As all of you know, interop of native components in the world of the managed code CLR isn't that hard: System.Runtime.InteropServices is your friend. More specifically, we'll be importing stuff from msi.dll using DllImports. To see what's available there, you can either open up msi.h from the Windows SDK or run dumpbin /exports on the dll, or in a less geeky way you can simple go to MSDN to get some information. Ultimately you'll come up with the following signatures that are relevant to data access, just focusing on read operations for now:

internal static class MsiInterop
{
     [DllImport("msi.dll", EntryPoint="MsiOpenDatabaseW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiOpenDatabase(string databasePath, int persist, out IntPtr database);

     [DllImport("msi.dll", EntryPoint="MsiDatabaseOpenViewW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiDatabaseOpenView(IntPtr database, string query, out IntPtr view);

     [DllImport("msi.dll", EntryPoint="MsiViewExecute", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiViewExecute(IntPtr view, IntPtr parameters);

     [DllImport("msi.dll", EntryPoint="MsiViewFetch", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiViewFetch(IntPtr view, out IntPtr record);

     [DllImport("msi.dll", EntryPoint="MsiRecordGetStringW", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiRecordGetString(IntPtr record, uint field, StringBuilder value, ref int bufferSize);

     [DllImport("msi.dll", EntryPoint="MsiRecordGetInteger", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern int MsiRecordGetInteger(IntPtr record, uint field);

     [DllImport("msi.dll", EntryPoint="MsiCloseHandle", CharSet=CharSet.Unicode, ExactSpelling=true)]
     public static extern uint MsiCloseHandle(IntPtr handle);
}

These functions are pretty simple to understand: using MsiOpenDatabase one opens a database, which can be used to define a query view using MsiDatabaseOpenView that subsequently can be executed using MsiViewExecute to yield results using MsiViewFetch, each of which can be accessed on a per-column basis using MsiRecordGetString and MsiRecordGetInteger. Finally, MsiCloseHandle is used to close any obtained handles. We're just doing raw interop here without more fancy SafeHandle stuff or so, but feel free to fine-tune the interop layer.

 

Respecting well-known patterns

Patterns deserve respect. Most of the time it are random designs that haven proven successful and received a career lifetime achievement award because of that, giving them the right to use the title "Pattern" (with capital P). I'd like to promote the design of data providers in .NET to this status. Essentially there are three players in the core pattern:

  • Connection - maintains the connection-oriented access to an underlying data store
  • Command - used to execute an arbitrary possibly parameterized command on the data store
  • DataReader - fetches results from a command in a sequential forward-only fashion

If we'd be really respectful, we'd go all the way implementing the System.Data.IDb* interfaces but since this is a sample only, we reserve ourselves the right to take some shortcuts and ignore concepts like transactions (although MsiOpenDatabase has such notions!). Obviously, the reader is free to extend this data provider at will to embrace those interfaces end-to-end providing meaningful implementations where applicable.

 

The connection

Nothing fancy here: just a call to MsiOpenDatabase, a way to obtain the handle for use in the command class and some clean-up using IDisposable and a finalizer. Ignore the fact I'm not subclass System.Exception for the purpose of this sample.

public class MsiConnection : IDisposable
{
     private string _fileName;
     private IntPtr _database;
     private bool _disposed;

     public MsiConnection(string fileName)
     {
          if (!File.Exists(fileName))
          {
               throw new FileNotFoundException(fileName + " not found.");
          }

          _fileName = fileName;
     }

     public void Open()
     {
          CheckDisposed();

          if (_database != IntPtr.Zero)
          {
               throw new InvalidOperationException("Database already opened.");
          }

          uint res = MsiInterop.MsiOpenDatabase(_fileName, 0, out _database);
          if (res != 0)
          {
               throw new Exception("Failed to open database. Error code = " + res);
          }
     }

     public void Close()
     {
          CheckDisposed();

          if (_database != IntPtr.Zero)
          {
               MsiInterop.MsiCloseHandle(_database);
               _database = IntPtr.Zero;
          }
     }

     internal IntPtr Handle
     {
          get { CheckDisposed(); return _database; }
     }

     public void Dispose()
     {
          Dispose(true);
          GC.SuppressFinalize(this);
     }

     private void Dispose(bool disposing)
     {
          if (!_disposed && _database != IntPtr.Zero)
          {
               MsiInterop.MsiCloseHandle(_database);
               _database = IntPtr.Zero;
               _disposed = true;
          }
     } 

     private void CheckDisposed()
     {
          if (_disposed)
               throw new ObjectDisposedException();
     }

     ~MsiConnection()
     {
          Dispose(false);
     }
}

 

The command

Just like the connection maps to the concept of an MSI connection, the command maps to the concept of an MSI view. Again we implement IDisposable but for the rest, everything should be self-expanatory:

public class MsiCommand : IDisposable
{
     private IntPtr _view;
     private string _command;
     private MsiConnection _conn;
     private bool _disposed;

     public MsiCommand(string command, MsiConnection conn)
     {
          _command = command;
          _conn = conn;
     }

     public MsiDataReader ExecuteReader()
     {
          CheckDisposed();

          if (_conn.Handle == IntPtr.Zero)
          {
               throw new Exception("Database connection not opened.");
          }

          uint res = MsiInterop.MsiDatabaseOpenView(_conn.Handle, _command, out _view);
          if (res != 0)
          {
               throw new Exception("Failed to create command. Error code = " + res);
          }

          return new MsiDataReader(this);
     }

     internal IntPtr Handle
     {
          get { CheckDisposed(); return _view; }
     }

     public void Dispose()
     {
          Dispose(true);
          GC.SuppressFinalize(this);
     }

     private void Dispose(bool disposing)
     {
          if (!_disposed && _view != IntPtr.Zero)
          {
               MsiInterop.MsiCloseHandle(_view);
               _view = IntPtr.Zero;
               _disposed = true;
          }
     } 

     private void CheckDisposed()
     {
          if (_disposed)
               throw new ObjectDisposedException();
     }

     ~MsiCommand()
     {
          Dispose(false);
     }