Transactional File System Operations

24. January 2009

UPDATE: The use of TxF and KTM for long running transactions is not good.

Most of us are familiar with Transactions from the ADO.NET. To briefly explain what a transaction is, I can say that in a transaction, all operations occur in all-or-none basis. If an exception occurs and transaction is rolled back, then the system is in exactly the state it was before all those operations.

During our work on BlogSharp, I realized that I need to have transaction support for File System Operations. The scenario was simple, the author tries to create a post, upload some images and files. The user may save the post, or cancels it. Until now, it is easy to deal with those cases, but there is the third case: User may close the browser directly(or because of an exception). Keeping track of file and deleting them when they are no longer used is easy but it is not the way it should be. A better solution is possible with the use of transactions.

I searched if someone else had the same need, and found out that Martijn Boland(from Cuyahoga) gave it a try here using Castle Transactions. He uses a temporary directory to do the file operations, and after commit he copies them back to final location. This is definitely a good way of doing it.

However, I wanted to see if the file system supports transactions natively, and the answer turned out to be yes and no. NTFS of XP doesn’t support transactions while that of Vista(and Win2k8) does. I also learnt that Vista has kernel transactions via Kernal Transaction Manager which can be used against registry, too. Imagine an installer which doesn’t use its own transaction mechanism but KTM. When an error occurs, there will be no left overs since the transaction is not committed. The MSDN Magazine has an article that goes deep in this topic and provides some managed classes around TxF but I don’t like it. I then found Michael Kennedy's implementation here and it was better than the previous one. However, it still doesn’t provide any abstraction which I need for testing purposes. I gave it a go, too and here is the result:

[Fact]
public void Open_write_returns_stream_to_be_written()
{
    string data;
    using (TransactionScope scope = new TransactionScope())
    {
        
        using (var fileStream = fileService.OpenFileForWrite(this.textfile))
        {
            Assert.True(fileStream.CanWrite);
            Assert.False(fileStream.CanRead);
            Assert.True(fileStream.CanWrite);
            fileStream.Write(new byte[] {126}, 0, 1);
        }
        data = GetFileContentTransactional(this.textfile);
        Assert.Equal("~lah", data);
    }
    data = GetFileContentNonTransactional(this.textfile);
    Assert.Equal("blah",data);
}


I change the file content in transaction, and this change is only valid within the transaction. Unless we complete the scope, the change doesn’t take effect.

Please note that this is a managed wrapper with many many simplifications which may lead to improper results. It is by no means a production quality code. I have written many tests for basic cases but not advanced ones which may include file security problems. Moreover, there is no exception handling mechanism taking place to convert native I/O exceptions to human readable ones. There may also be memory leaks somehow(even though i paid special attention to this). Those mean that use it at your own risk. All feedback is welcome.

The native methods for transactional file operations can be found here.
The managed wrapper with FS abstractions can be found here

kick it on DotNetKicks.comShout it

, ,