ATL support for Transaction File System

ATL support for Transaction File System

  • Comments 11

Hello,

 

My name is Weidong Huang, and I am an SDET on the Visual C++ team. Today, I would like to talk a little about ATL support for Transaction File System in Visual Studio 2010.

 

What is Transactional File System?

 

Transactional File System is a new technology first introduced in Windows Vista. It enables you to roll back operations made on the file system and registry.  It is helpful in improving application reliability and data consistency without having to add your own tooling.  By using transactional file systems in VC++ you can treat a set of operations to files and registry as a transaction which can either be completed as a whole or reversed.

 

One example that could use Transactional File System:  A developer creates several new files, writes some information into them, deletes one existing file and wishes to either complete all the operations or do nothing at all.  He could use the Transactional File System to create a transaction, add all the steps into this transaction, commit it when everything works fine, or rollback if there is anything wrong with a step.  

 

 

What is implemented in Visual Studio 2010?

 

1.      Added a new ATL class “CAtlTransactionalManager”:  CAtlTransactionManager is a new class wrapping Transaction File System Windows APIs and provides most of Transactional File System functionalities that are implemented in VC++, including:

·         File Operations: CreateFile, DeleteFile, FindFirstFile, GetFileAttributes, SetFileAttributes, MoveFile.

·         Registry Operations: RegCreateKeyEx, RegDeleteKey, RegOpenKeyEx

 

2.      Added transaction support to Existing Classes and APIs, including:

·         Classes: CFile, CStdioFile, CAtlFile, CFileFind by adding a constructor with TransactionManager as following format:

CFile(CAtlTransactionManager *);

CStdioFile(CAtlTransactionManager *);

CAtlFile(CAtlTransactionManager *);

CFileFind(CAtlTransactionManager *);

·         APIs: AfxRegCreateKey, AfxRegOpenKey, AfxRegDeleteKey by adding a parameter TransactionManager to the API.

 

How to use Transaction File System?

1.      With CAtlTransactionManager:

(1)   Create a CAtlTransactionManager;

(2)   Call functions in CAtlTransationManager to do operations on File or registry

(3)   Call Transaction Manager Commit() to submit the change or RollBack() to reverse the change.

Note: Please refer to Walkthrough Step 2, 3, 4 and 5 for code example.

 

2.      With CFile, CStdioFile, CAtlFile, CFileFind:

(1)   Create a CAtlTransactionManager;

(2)   Create class with the Transaction Manager created in the first step;

(3)   Call class member functions to do operations on File;

(4)   Call Transaction Manager Commit() to submit the change or RollBack() to reverse the change.

Note: Please refer to Walkthrough Step6 for code example.

3.      With Registry APIs:

(1)   Create a CAtlTransactionManager;

(2)   Call API functions with the Transaction Manager created in the first step to do operations on File;

(3)   Call Transaction Manager Commit() to submit the change or RollBack() to reverse the change.

What will happen in operating systems prior to Windows Vista?

 

Transactional File System is only supported in Windows Vista or up level OSes and not available on operating systems prior Vista such as Windows XP and Windows 2003.  Will the application crash if it uses Transactional System Functionality in an operating system that doesn’t support it?  The answer is no.  When creating CAtlTransactionManager, there is an option called “Fallback” which is use to control the behavior in operating systems prior to Vista.  By default, the fallback is TRUE which means you will get the same behavior just as that of non-transactional version if Transaction File System is not supported.  If you set the fallback to FALSE, then the operation will fail when it is not supported. 

 

Walkthrough to use Transaction File System:

 

Here is the walkthrough to help you understand the usage of Transaction File System:

Step1: Create project

1.      Create a default Dialog Type MFC Application call “TransactionalFileSystemDemo”.

2.       Double click the OK button in default dialog (not file dialog) and create the default event handler “void CTransactionalFileSystemDemoDlg::OnBnClickedOk()”. All codes in following steps will be added to this function.

Step2: Use CAtlTransactionManager to Commit File Transaction

 

Add codes

(1)   Add following code to CTransactionalFileSystemDemoDlg::OnBnClickedOk()

                        CAtlTransactionManager tmFileCommit1;

                        HANDLE hFile;

                        hFile = tmFileCommit1.CreateFile(_T("c:\\TransactionFileSystemDemo\\file1.txt"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);

                        CloseHandle(hFile);

                        hFile = tmFileCommit1.CreateFile(_T("c:\\TransactionFileSystemDemo\\file2.txt"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);

                        CloseHandle(hFile);

                        hFile = tmFileCommit1.CreateFile(_T("c:\\TransactionFileSystemDemo\\file3.txt"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);

                        CloseHandle(hFile);

                        WIN32_FIND_DATA nextFile;

                        tmFileCommit1.FindFirstFile(_T("c:\\TransactionFileSystemDemo\\file1.txt"), &nextFile);

                        tmFileCommit1.SetFileAttributes(_T("c:\\TransactionFileSystemDemo\\file1.txt"), FILE_ATTRIBUTE_READONLY);

                        DWORD dwAttributes = tmFileCommit1.GetFileAttributes(_T("c:\\TransactionFileSystemDemo\\file1.txt"));

                        tmFileCommit1.Commit();

Result

                        CAtlTransactionManager tmFileCommit2;

                        tmFileCommit2.DeleteFile(_T("c:\\TransactionFileSystemDemo\\file2.txt"));

                        tmFileCommit2.MoveFile(_T("c:\\TransactionFileSystemDemo\\file3.txt"), _T("c:\\TransactionFileSystemDemo\\file4.txt"));

                        tmFileCommit2.Commit();

                        tmFileCommit2.Close();

Experience:

1.      Create new folder “c:\\TransactionFileSystemDemo”;

2.      Build the project, set break point at “tmFileCommit1.Commit()” and “tmFileCommit2.Commit()”.

3.      Debug the application, click the default “OK” button and stop at the first break point;

a.       Watch the value of nextFile and dwAttributes, they should have expected values.

b.      Open Explorer and check folder “c:\\TransactionFileSystemDemo”: no file should be created.

4.      Continue debug and go one more step, stop just after “tmFileCommit1.Commit()”

a.       Check folder “c:\\TransactionFileSystemDemo”: file1.txt, file2.txt and file3.txt should be created in this folder.

5.      Continue debug and stop at the second break point;

a.       Check folder “c:\\TransactionFileSystemDemo”: file1.txt, file2.txt and file3.txt are still there.

6.      Continue debug and go one more step, stop just after “tmFileCommit1.Commit()”

a.        Check folder “c:\\TransactionFileSystemDemo”: file1.txt should be deleted and file3.txt should be renamed to file4.txt.

 

Step3: Use CAtlTransactionManager to RollBack File Transaction 

Add codes

(1)   Add following code after the codes added in step2.

                        CAtlTransactionManager tmFileRollback;

                        HANDLE hFile4 = tmFileCommit1.CreateFile(_T("c:\\TransactionFileSystemDemo\\file5.txt"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);

                        CloseHandle(hFile4);

                        tmFileRollback.DeleteFile(_T("c:\\TransactionFileSystemDemo\\file1.txt"));

                        tmFileRollback.MoveFile(_T("c:\\TransactionFileSystemDemo\\file4.txt"), _T("c:\\TransactionFileSystemDemo\\file3.txt"));

                        tmFileRollback.Rollback();

                        tmFileRollback.Close();                       

Experience:

 

1.      Build the project and run the application, click the default “OK”.

a.       Open Explorer and check folder “c:\\TransactionFileSystemDemo”: no change happens

                                                              i.      File5.txt is not created

                                                            ii.      File1.txt is still there

                                                          iii.      File4.txt is not renamed to file3.txt

Step4: Use CAtlTransactionManager to Commit Registry Transaction 

Add codes

(1)   Add following code after the codes added in step3

                        DWORD dwDisp = 0;

                        HKEY hkResult = NULL;

 

                        CAtlTransactionManager tmRegCommit1;

                        tmRegCommit1.RegCreateKeyEx(HKEY_CURRENT_USER, _T("New Key 1"), 0, NULL, REG_OPTION_NON_VOLATILE,KEY_WRITE, NULL, &hkResult, &dwDisp);

                        RegCloseKey(hkResult);

                        tmRegCommit1.RegCreateKeyEx(HKEY_CURRENT_USER, _T("New Key 2"), 0, NULL, REG_OPTION_NON_VOLATILE,KEY_WRITE, NULL, &hkResult, &dwDisp);

                        RegCloseKey(hkResult);

                        tmRegCommit1.RegCreateKeyEx(HKEY_CURRENT_USER, _T("New Key 3"), 0, NULL, REG_OPTION_NON_VOLATILE,KEY_WRITE, NULL, &hkResult, &dwDisp);

                        RegCloseKey(hkResult);

                        tmRegCommit1.RegOpenKeyEx(HKEY_CURRENT_USER, _T("New Key 1"), 0, KEY_READ, &hkResult);

                        tmRegCommit1.Commit();

                        CAtlTransactionManager tmRegCommit2;

                        tmRegCommit2.RegDeleteKeyEx(HKEY_CURRENT_USER, _T("New Key 2"), KEY_WOW64_32KEY, 0);

                        tmRegCommit2.Commit();

                        tmRegCommit2.Close();

Note:  if you are running on x64 OS, change “KEY_WOW64_32KEY” to “KEY_WOW64_64KEY”.

 

Experience:  (Please refresh registry every time before checking registry)

 

1.      Build the project, clear all break point and then set break point at “tmRegCommit1.Commit()” and “tmRegCommit12.Commit()”

2.      Debug the application, click the default “OK” button and stop at the first break point;

a.       Watch the value of hkResult, it should be a valid handle.

b.      Run “regedit” and open registry: check keys under “HKEY_CURRENT_USER”, no “new key *” is created here.

3.      Continue debug and go one more step, stop just after “tmRegCommit1.Commit()”

a.       Check registry: “new key 1”, “new key 2” and “new key 3” should be created under “HKEY_CURRENT_USER”.

4.      Continue debug and stop at the second break point;

a.       Check registry under “HKEY_CURRENT_USER”. “new key 2” should still be there.

5.      Continue debug and go one more step, stop just after “tmFileCommit2.Commit()”

a.       Check registry under “HKEY_CURRENT_USER”. “new key 2” should be deleted.

Step5: Use CAtlTransactionManager to RollBack Registry Transaction 

Add codes

(1)   Add following code after the codes added in step4.

                        CAtlTransactionManager tmRegRollback;

                        tmRegRollback.RegCreateKeyEx(HKEY_CURRENT_USER, _T("New Key 4"), 0, NULL, REG_OPTION_NON_VOLATILE,KEY_WRITE, NULL, &hkResult, &dwDisp);

                        tmRegRollback.RegDeleteKeyEx(HKEY_CURRENT_USER, _T("New Key 3"), KEY_WOW64_32KEY, 0);

                        tmRegRollback.Rollback();

                        tmRegRollback.Close();

Note:  if you are running on x64 OS, change “KEY_WOW64_32KEY” to “KEY_WOW64_64KEY”.

 

Experience:

2.      Build the project and run the application, click the default “OK”.

a.       Check registry under “HKEY_CURRENT_USER”: no change happends

                                                              i.      “New Key 4” is not created

                                                            ii.      “New Key 3” is still there and not deleted.

Step6: Use CFile, CStdioFile and CAtlFile for File Transaction 

Add codes

(1)   Add following code after the codes added in step6.

                        CAtlTransactionManager tmCommit;

                        CFile cfile1(_T("c:\\TransactionFileSystemDemo\\cfile1.txt"), CFile::modeCreate, &tmCommit);

                        CStdioFile stdiofile1(_T("c:\\TransactionFileSystemDemo\\cstdiofile1.txt"), CFile::modeCreate, &tmCommit);

                        CAtlFile atlfile1(&tmCommit);

                        atlfile1.Create(_T("c:\\TransactionFileSystemDemo\\atlfile1.txt"), STANDARD_RIGHTS_REQUIRED, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL);

                        tmCommit.Commit();

                        tmCommit.Close();

 

                        CAtlTransactionManager tmRollback;

                        CFile cfile2(_T("c:\\TransactionFileSystemDemo\\cfile2.txt"), CFile::modeCreate, &tmRollback);

                        CStdioFile stdiofile2(_T("c:\\TransactionFileSystemDemo\\cstdiofile2.txt"), CFile::modeCreate, &tmRollback);

                        CAtlFile atlfile2(&tmRollback);

                        atlfile2.Create(_T("c:\\TransactionFileSystemDemo\\atlfile2.txt"), STANDARD_RIGHTS_REQUIRED, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL);

                        tmRollback.Rollback();

                        tmRollback.Close();

Experience:

 

1.      Build the project, clear all break point and then set break point at “tmCommit.Commit()”.

2.      Debug the application, click the default “OK” button and stop at the break point;

a.       Check folder “c:\\TransactionFileSystemDemo”: no change happens.

3.      Continue debug and go one more step, stop just after “tmCommit1.Commit()”

a.       Check folder “c:\\TransactionFileSystemDemo”: cfile1.txt, cstdiofile1.txt and atlfile1.txt should be created here.

4.      Continue debug and finish the code:

a.       Check folder “c:\\TransactionFileSystemDemo”: no change happens. None of cfile2.txt, cstdiofile2.txt and atlfile2.txt is created here.

 

Thanks,

Weidong

  • <quote>By default, the fallback is TRUE which means the operation will fail if Transaction File System is not supported.  If you set the fallback to FALSE, you will get the same behavior as that of a non-transactional version.</quote>

    I hope this is a typo and the actual behavior is opposite (fallback=TRUE allows non-transactional, fallback=FALSE just fails).

  • Do we know what the overhead will cost us in terms of timing?  

    In other words, how milliseconds will this add in "typical" scenarios of no rollback?

  • Ben, Thanks for your comments. It was a typo and I've corrected it .

  • The overhead of committing is minimal. When new pages are written to disk within a transaction, they are actually written directly in place while the previous pages are saved for retrieval. When the transaction is committed, the altered pages are already in place on disk.

  • I've implemented the similar functionality in Beta 1 in order to "teach" ATL Registrar component to use transactions, when they are available on target system.

    It's great to see the infrastructure ready in Beta 2 (or did I miss it in Beta 1?..)

    Do you have plans to update ATL Registrar to support transactions too? It would be great for deployment code.

  • Alex, sorry we won't have ATL Registrar to support transactions in VS 2010. It would be very great that you could provide us your scenario to help improve the product. We will definitely consider this in next product cycle.

  • >When creating CAtlTransactionManager, there is an option called “Fallback” which is use to control the behavior in operating systems prior to Vista.

    Great!

    But there are some edge cases even in Vista or later.

    For example, the target file may exist in a FAT32 drive. Perhaps the target file is written with EFS (Encrypted file systems).

    Unfortunately, these situations are still out of the scope of TxF.

    http://msdn.microsoft.com/en-us/library/aa365738.aspx

    So it would be very useful if CAtlTransactionManager supports such kind of fallbacks!

  • One more thing.

    In atltransactionmanager.h, there are still some boring typedefs such as:

    >> typedef HANDLE (WINAPI* PFNCREATEFILETRANSACTED)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE, HANDLE, PUSHORT, PVOID);

    How about using decltype?

    typedef decltype(CreateFileTransactedW)* PFNCREATEFILETRANSACTED;

  • Why isn't the name CAtlTransactionalFileManager, since it only does files?

    The current name is suggesting a general transaction manager, which it isn't.

  • Isn’t the ATL Registrar transactional?  I mean, under older systems as well?  So you cannot register partly?

    (Because if it is not, it is not worth using; I have never come to test that though because I was compelled to implement my own registrar because the ATL registrar did not support registering for the current user or nonpersistent entries).

  • Is there any support for managed code?

Page 1 of 1 (11 items)