As I said there, I have the following scenario in which NHibernate seems inefficient in handling rollback of a transaction.
My class is basically an email message, whose body is not stored onto database but on disk, so every email is bound to a binary file. I also have 3 event listeners, as described on SO:
1. PostInsert: creates the file from binary payload 2. PostLoad: loads the binary payload from disk, so clients are able to read it 3. PostDelete: deletes the file from disk
The problem is very simple: if I delete an email, then rollback the transaction, the file has been already deleted and then the application is inconsistent (future attempts to access the file result in FileNotFoundException). The opposite occurs if I rollback after insert: a garbage file is stored on disk and won't be deleted any more.
It seems, from my current experience and from the discussion on Stack Overflow, that NHibernate is missing facilites to handle the pre-commit, post-commit and rollback events.
As an experienced software engineer, I would suggest the NHibernate team to do some redesign to the event infrastructure. Here are my tips:
****OPTION 1 Simply add IPreCommitListener, IPostCommitListener and IRollbackListener interfaces. In particular, the IPreCommitListener interface must be used by developers to detect final anomalies and have the final chance to request roll-back. The PreCommit event actually reminds a lot what occurs in 2PC protocol, where the transaction manager, after having accepted all commands from client and received the COMMIT command, tries to commit the transaction on all resource managers and if at least one fails, the whole transaction gets rolled back. The sequence of events should be something like this: 1. Client commits session 2. For-each entity in current transaction 1. Raise event PreCommit 3. If nobody requested rollback 1. SQL commit 2. For-each entity in current transaction 1. Raise event PostCommit 4. Else 1. For-each entity in current transaction 1. Raise event Rollback
Otherwise if the transaction is rolled back by client, for each entity raise Rollback event.
I believe this is the smartest situation but requires scanning through the entities again.
****OPTION 2 This second design allows easier usage of delegates/lambdas, especially in situations like mine. With the current set of events, one could think about registering to the same events as described before, BUT... register a callback method to be executed after transaction commit/rollback. In this case, the @event argument of method (or the ITransaction object associated to ISession) may expose public RegisterOnCommit(delegate) and RegisterOnRollback(delegate) methods. Applied to my case, this means that when a message is deleted, I just "schedule" its real deletion for commit, or, on the other hand, when a message is inserted, I create it and "schedule" its deletion for rollback in case it occurs.
For completeness of information, I may have found that a workaround to this situation is possible, possibly using ITransaction.RegisterSynchronization method, but I haven't tried it. I believe the event redesign is a better choice because coding gets more engineered, especially with option 1, with which a complex system made of a transactional DB and other non-transactional subsystems (such as file system) can turn into transactional!
If you need some coding, I may help in my spare time, but NHibernate is a very complex project so I don't fully understand how to start. Coding would be needed when raising such events, but also when REGISTERING event handlers from the various types of configurations.
Thank you for your attention.
PS I haven't currently attached a test case, even if I was working on one, because the matter seems really simple to be treated just with words, and, by the way, I was having some problems making the test case run
Environment
None
Activity
Oskar Berggren
September 16, 2012 at 8:26 PM
Have you considered implementing your file handling as a resource manager that itself enlists in an ambient transaction?
Antonio Anzivino
February 20, 2011 at 9:21 AM
Let me clarify.
I CURRENTLY use NHibernate in a "mixed transaction" that is made both of SQL and file operation. What worries me the most is to prevent the deletion of a file after the corresponding entity was deleted but rolled back to DB (because I won't store the blob into the database)
DISTRIBUTED TRANSACTIONS are a suggestion fot the team. The phrase about 2 phase commit protocol is just a comparison of the possible behaviour. In my scenario, even if the transaction is local to the process, the fact that file system is involved (through event listeners) could be COMPARED to a transaction DISTRIBUTED between RDBMS and file system.
Just that.
Fabio Maulo
February 19, 2011 at 6:12 AM
Are you using a NHibernate's transaction inside a distributed transaction ?
Greetings,
I'm a devloper using NHibernate 3.0 and I found some difficulties programming with event listeners when I had to deal with the file system. My full scenario is being discussed at StackOverflow on http://stackoverflow.com/questions/4834681/nhibernate-event-ipostdeleteeventlistener-not-working-good-with-transactions
As I said there, I have the following scenario in which NHibernate seems inefficient in handling rollback of a transaction.
My class is basically an email message, whose body is not stored onto database but on disk, so every email is bound to a binary file. I also have 3 event listeners, as described on SO:
1. PostInsert: creates the file from binary payload
2. PostLoad: loads the binary payload from disk, so clients are able to read it
3. PostDelete: deletes the file from disk
The problem is very simple: if I delete an email, then rollback the transaction, the file has been already deleted and then the application is inconsistent (future attempts to access the file result in FileNotFoundException). The opposite occurs if I rollback after insert: a garbage file is stored on disk and won't be deleted any more.
It seems, from my current experience and from the discussion on Stack Overflow, that NHibernate is missing facilites to handle the pre-commit, post-commit and rollback events.
As an experienced software engineer, I would suggest the NHibernate team to do some redesign to the event infrastructure.
Here are my tips:
****OPTION 1
Simply add IPreCommitListener, IPostCommitListener and IRollbackListener interfaces. In particular, the IPreCommitListener interface must be used by developers to detect final anomalies and have the final chance to request roll-back. The PreCommit event actually reminds a lot what occurs in 2PC protocol, where the transaction manager, after having accepted all commands from client and received the COMMIT command, tries to commit the transaction on all resource managers and if at least one fails, the whole transaction gets rolled back.
The sequence of events should be something like this:
1. Client commits session
2. For-each entity in current transaction
1. Raise event PreCommit
3. If nobody requested rollback
1. SQL commit
2. For-each entity in current transaction
1. Raise event PostCommit
4. Else
1. For-each entity in current transaction
1. Raise event Rollback
Otherwise if the transaction is rolled back by client, for each entity raise Rollback event.
I believe this is the smartest situation but requires scanning through the entities again.
****OPTION 2
This second design allows easier usage of delegates/lambdas, especially in situations like mine. With the current set of events, one could think about registering to the same events as described before, BUT... register a callback method to be executed after transaction commit/rollback.
In this case, the @event argument of method (or the ITransaction object associated to ISession) may expose public RegisterOnCommit(delegate) and RegisterOnRollback(delegate) methods.
Applied to my case, this means that when a message is deleted, I just "schedule" its real deletion for commit, or, on the other hand, when a message is inserted, I create it and "schedule" its deletion for rollback in case it occurs.
For completeness of information, I may have found that a workaround to this situation is possible, possibly using ITransaction.RegisterSynchronization method, but I haven't tried it. I believe the event redesign is a better choice because coding gets more engineered, especially with option 1, with which a complex system made of a transactional DB and other non-transactional subsystems (such as file system) can turn into transactional!
If you need some coding, I may help in my spare time, but NHibernate is a very complex project so I don't fully understand how to start. Coding would be needed when raising such events, but also when REGISTERING event handlers from the various types of configurations.
Thank you for your attention.
PS I haven't currently attached a test case, even if I was working on one, because the matter seems really simple to be treated just with words, and, by the way, I was having some problems making the test case run