Transactional File System

| | Comments (0)

One neat new stability feature of Vista is the transactional file system functionality that the kernel exposes. This makes error checking almost trivial for file operations which are required to be completed as a set. The basic idea is that you start a transaction to obtain a handle, then do a bunch of operations using the transaction handle, and then commit (or rollback) the transaction using the handle. If something goes horribly wrong in the middle of the operations, you don't have to worry because the entire transaction is rolled back automatically (unless you commit it).

So, let's say the OS was applying a patch and the power went out in the middle of this process. That would leave the system in a weird "half state" where part of the patch was applied, and part of it was not. When the system rebooted, chances are pretty good that the OS would simply fail to boot because of this. With transactions, the OS would first rollback any files from the transaction, and then finish booting as normal -- much safer!

But you don't have to be an OS vendor to want to use transactional functionality. Let's say you have a preferences system that writes data out to the registry. You could use transactions to ensure that your preferences are written out properly as a group, instead of getting into a half-state. Or, what if your application does a lot of work on its initial launch, saving files out to disk and keys out to the registry and so-on -- you could use a single transaction for all of these operations so that if the application crashes, you aren't left with corrupted, half-finished data. Transactions are extremely powerful!

So the basic idea is that you use CreateTransaction to make a new transaction handle, then call whatever "Transacted" APIs you'd like and pass in the handle to them. When your transaction is complete, you call CommitTransaction or CommitTransactionAsync to commit it. Finally, you call CloseHandle to release memory. Note, if you call CloseHandle before calling CommitTransaction, then the transaction is automatically rolled back for you. But if you want to explicitly roll the transaction back, then you can call RollbackTransaction or RollbackTransactionAsync to do so. Here's a simple example of how to move a file:


const DWORD timeout = 0;  // No timeout, but we could specify one
HANDLE hTransaction = CreateTransaction( NULL, 0, 0, 0, 0, 
                             timeout, L"Description for the kiddies" );
if (hTransaction != INVALID_HANDLE_VALUE) {
  MoveFileTransacted( existingFile, destinationFile, NULL, 
           NULL, MOVEFILE_COPY_ALLOWED, hTransaction );
  CommitTransaction( hTransaction );
  CloseHandle( hTransaction );
}

Now, that's obviously a contrived example, but it's still a functional one (it could be a very long, very important move operation -- if the computer crashes in the middle of it, you want to be in a stable state). If you run that example, and step through the code, you'll notice something interesting happening. The call to MoveFileTransacted doesn't appear to move the file! If you look at the file in Explorer, you'll see it in the original location. That's because it's the commit operation which actually does the work.

If you're going to be doing any sort of batch operations in your application, or otherwise be able to put your application into a corrupted state by working with the file system or the registry, I would highly recommend you look at using transacted operations on Vista and up as they will make your application much more stable. It's a very powerful API, that is also very easy to use since it relies on all the same APIs you've been using for years.

Update (Jul 13 2007)
So it turns out that I had some misinformation in the original blog posting, which I've now corrected. There is no issue with regards to transaction stacking so long as you are using the transacted versions of the APIs. The time when you do run into problems is when you mix and match. For instance, if you were to create a folder transacted, and then attempt to create a file in that folder without being transacted, then the CreateFile operation will fail (since it knows nothing about the transacted file).

There is still one gotcha with transactions -- MSDN has some incorrect documentation on GetFileAttributesTransacted. MSDN claims that the function returns a DWORD which represents the attributes. That's wrong! It actually returns a BOOL to tell you whether the function succeeded or not, and the attributes are in the structure passed to the API. Don't let that catch you (as it was what caught me in some of my tests).

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.