One of the things I've had to get used to at Microsoft is operating with cutting-edge code.  By that I mean, somebody just changed it yesterday, new things are going in, plus fixes, and on such a large codebase, problems do happen.

A couple of days ago, somebody asked whether Transactional NTFS would be able to copy a System32 directory.  I tested it, and confirmed that it could.  But how far would it go? I've never really looked at that, so I thought it would be a fun exercise to write some more code to check.  Here, for your reference, is that very code:


#include <windows.h>
#include <ktmw32.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>

int __cdecl _tmain(int argc, TCHAR ** argv)
{
HANDLE hTrans;
HANDLE hFile;
DWORD dwFileNum;
TCHAR szFileName[MAX_PATH];

// Create the transaction.
hTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);

if (hTrans == INVALID_HANDLE_VALUE) {
_tprintf(_T("Could not create transaction.\n"));
return EXIT_FAILURE;
}

// Currently, we're still operating as a normal, non-transacted
// thread. To use the transaction we've just created, call
// SetCurrentTransaction.
if (!SetCurrentTransaction(hTrans)) {
_tprintf(_T("Could not change transactions.\n"));
return EXIT_FAILURE;
}

dwFileNum = 1;
while (TRUE) {
StringCchPrintf(szFileName, sizeof(szFileName)/sizeof(szFileName[0]), _T("%i"), dwFileNum);
hFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (hFile == INVALID_HANDLE_VALUE) {
_tprintf(_T("Failed to create file number %i, GLE %i\n"), dwFileNum, GetLastError());

if (!RollbackTransaction(hTrans)) {
_tprintf(_T("Failed to rollback transaction\n"));
}

CloseHandle(hTrans);

if (!SetCurrentTransaction(NULL)) {
_tprintf(_T("Could not switch back to non-transactional state.\n"));
}

return EXIT_SUCCESS;
}


dwFileNum++;

if (dwFileNum % 10000 == 0) {
_tprintf(_T("%i\n"), dwFileNum);
}
}

return EXIT_SUCCESS;
}
I'd hoped this code would give me some idea of how many files I could create in an uncommitted Transaction. All Transacted applications should be aware that Transactions can be failed by the system in exceptional circumstances; this means that applications should check for this possibility. Most importantly, all memory-mapped I/O should catch exceptions, because if a Transaction is rolled back and exceptions aren't handled, that app will just crash, seemingly randomly, in this eventuality.  The application will still be associated with a Transaction, but since the Transaction is no longer active, all the Transacted calls made by that application are expected to fail, at least, in theory.  Because you can't fail a memory access, all it can do is raise an exception.

Working with pre-beta code makes things always a little uncertain.  Sometimes things just don't go according to plan.  So while I do know that I can create more than 110,000 files in a single directory within a transaction, I'll probably spend tomorrow morning investigating what happened after that.  Bear in mind that that 110,000 figure is not a final figure, and you shouldn't rely on it except to seed your random number generator, or as some kind of rough benchmark about where we are right now.  I can't make any promises about tomorrow :)

Update: well, as I was writing this post, my app actually did finish.  So the final answer is 117,341, and the failed error was ERROR_TRANSACTION_NOT_ACTIVE.  There are still quirks, and I'll still end up working on it tomorrow :)