I've written before about the underlying principles behind our transaction management work:
- our aim is to provide a ubiquitous transaction service, that makes the use of transactions a natural fit for applications seeking reliability or improved concurrency.
In order to accomplish that, we produce transaction services:
- that provide a simple, trivial to use, transaction demarcation interface;
- that offer as little overhead as possible; and
- that do not change the developer experience as either the type of resource or the complexity of the transaction grows.
We've put a lot of effort into these, with the first obvious signs showing up in System.Transactions, available in the current .Net 2.0 betas.
Now, Longhorn is adding support for a transacted file system (TxF), and it would be instructive to take a look at how we approached incorporating suppport into our overall transaction model. Our goal is that it is just another first class resource -- it should 'just work', to whatever extent possible.
With the upcoming beta you should be able to take an existing application that uses COM+ or System.EnterpriseServices application, or in .Net 2.0, one using System.Transactions, and add transacted file system operations without any great changes to the code.
Here is a sample System.Transactions application (put together by Mike Clark of the video fame):
using System;
using System.IO;
using System.Transactions;
public class TextToFile
{
[System.Runtime.InteropServices.DllImport("kernel32"),
System.Security.SuppressUnmanagedCodeSecurityAttribute()]
internal static extern bool EnterTransactionScope();
[System.Runtime.InteropServices.DllImport("kernel32"),
System.Security.SuppressUnmanagedCodeSecurityAttribute()]
internal static extern bool ExitTransactionScope();
private const string FILE_NAME_ABORT = "AbortFile.txt";
private const string FILE_NAME_COMMIT = "CommitFile.txt";
public static void Main(String[] args)
{
if (File.Exists(FILE_NAME_ABORT))
{
Console.WriteLine("{0} already exists.", FILE_NAME_ABORT);
return;
}
Console.WriteLine("Creating TransactionScope");
try
{
using(TransactionScope s = new TransactionScope(
TransactionScopeOption.Required, new TransactionOptions(),
EnterpriseServicesInteropOption.Full))
{
Console.WriteLine("Calling KTM EnterTransactionScope");
EnterTransactionScope();
Console.WriteLine("Doing File IO");
StreamWriter sr = File.CreateText(FILE_NAME_ABORT);
sr.WriteLine ("This is my file.");
sr.WriteLine ("I can write ints {0} or floats {1}, and so on.",
1, 4.2);
sr.Close();
Console.WriteLine("Calling KTM ExitTransactionScope");
ExitTransactionScope();
// note that the s.Complete is not called so the transaction
// will roll back.
}
using(TransactionScope s = new TransactionScope(
TransactionScopeOption.Required, new TransactionOptions(),
EnterpriseServicesInteropOption.Full))
{
Console.WriteLine("Calling KTM EnterTransactionScope");
EnterTransactionScope();
Console.WriteLine("Doing File IO");
StreamWriter sr = File.CreateText(FILE_NAME_COMMIT);
sr.WriteLine ("This is my file.");
sr.WriteLine ("I can write ints {0} or floats {1}, and so on.",
1, 4.2);
sr.Close();
Console.WriteLine("Calling KTM ExitTransactionScope");
ExitTransactionScope();
s.Complete();
}
}
catch(Exception e)
{
Console.WriteLine("UHANDLED EXCEPTION");
Console.WriteLine(e);
}
}
}
Let me point out some things about this sample:
EnterTransactionScope/ExitTransactionScope: These are two new system calls that establish a kernel ambient transaction that is used by file system calls. Since the file system calls are not specific to transaction behavior, it is important that you establish a kernel ambient transaction around as small a region as possible. Note that the cost of entering and exiting transaction scopes multiple times is very low -- the only significant work is done on the first one.
EnterpriseServiceInteropOption.Full: This is used as a simple way to establish the internal connections necessary to connect a user mode transaction to a potential kernel ambient transaction. Internally, this uses COM+ Services Without Components to establish the correct running context. There are mechanisms that do not end up using COM+, but they do require some extra coding.
Aside from these points, this is just a normal System.Transactions application. All the features that are otherwise available, from distribution, mutiple thread support, database integration, and so on, continue to work just as they have.
There is equivalent support in COM+/System.EnterpriseServices, and the underlying support is built into the MSDTC interfaces. Therefore, there's a very equivalent example that can be written in any of those systems. I'll try to post those examples in the coming days.
UPDATE: This approach is no longer appropriate as of the Vista RC update. See http://pluralsight.com/blogs/jimjohn/archive/2006/08/31/36819.aspx for more information.
Posted
Apr 27 2005, 08:41 PM
by
jim-johnson