Writing a resource manager that needs to be completed when the TransactionScope ends
Hi,
I have created a simple resource manager using the ISinglePhaseNotification interface but when I have enlisted in a transaction and the transaction is commited, the TransactionManager calls the resource managers Commit method too late (after the transactionscope has ended).
I guess that I have missed a fundamental part in the way to use resource managers, or else I am at a loss.
I have tried to describe the problem in detail at
http://dhvik.blogspot.com/2007/06/writing-resource-manager-for.htmlLook there for complete background information and code samples.
Regards
Dan,
The transaction system on Windows (System.Transactions and MSDTC) return from a Commit call after the commit decision has been made. That means that it is indeterminate as to whether or not the Commit returns before all resource managers have received their Phase 2 notifications. The expectation is that a resource manager will properly handle access to in-doubt (i.e. prepared) data.
In looking at your blog entry, your resource manager receives a prepare, then System.Transactions makes a commit decision, the Commit returns, and your resource manager's Commit notification is fired. That is consistent with the rules above.
As an aside, note that a single phase notification acts as the commit decision, so that will happen before the Commit call returns. You're not getting one here because you have a volatile resource, as does TransactionScope (internally). Fwiw, if the resource was durable, and this was the only durable resource, then you would get a single phase notification. That is an optimization, however, and not a correctness tool that you can count on.
Jim.
Jim,
I see. So basically if iI understand this correctly there is no way to use a resource manager in a way that lets me commit/rollback immediately after the Commit/Rollback decision has been made?
My base issue is that I have an entity object that I want to store the state on when the transaction starts and if a Rollback occurs I wan't to be able to rollback the state of the entity object that it had when the transaction was started. See the following pseudo code.
MyEntity x = new MyEntity();
x.State = State.New;
using(TransactionScope s = new TransactionScope()) {
MyResourceManagerOrWhatEver.EnlistInTransaction(x);
//do stuff with other ResourceManagers (for instance read/write data from one or more datasources)
//update the state of the entity object
x.Id = 4;
x.State = State.Persisted;
//do some other stuff with the database that wasn't successful
//don't complete the transaction (transaction will be aborted)
}
//here I need the entity object to be restored to the state it had when it was enlisted in the transaction since I need to use the object at its current state. Its not possible to continue until all rollback has occured (atleast in the MyResourceManagerOrWhatEver).
//if I will check the state of x (x.State) it will surely be State.Persisted since the rollback hasn't been fired yet.
Is there some way to wait for the resource manager to complete its operation or do I manually need to code this?
Or better, can i set the ResourceManager/Transaction in a state that waits until the commit/rollback has been completed?
I tried the TransactionCompleted event on the Transaction class but this event fired after the resource managers where completed. (perhaps I can use this event as a callback for the code to continue?)
Dan,
This is something that really needs to be part of your resource manager.Generally speaking, the issue at hand has to do with isolation, which is something that a resource manager is expected to provide.In general, a resource manager needs to make sure that data does not inadvertently leak from one transaction to another, and that has to cover the period after it has voted and before it has received a commit or abort request.
Normally, at least for the tighter isolation levels, the resource manager will typically block access to data that is in the prepared state.
This feature of a resource manager is related to the handling around when to return from Commit.A transaction manager can either return immediately after the commit decision has been made, or wait for all phase 2 processing to end.Do note that this consideration is global -- it is insufficient to only consider the in-appdomain resources.
There are two tradeoffs in this decision: first, if we can assume that a percentage of transactions do not immediately hit a data collision with the last transaction, then we can improve throughput by overlapping the next transaction with the cleanup phase of the last one.Second, if we attempt to hold the Commit from returning to the user until all phase 2 processing is complete then either the Commit call will be subject to a number of paths that lead to a persistent or relatively persistent hang or will have cases where the Commit returns before all phase 2 processing is complete.
Given these three points, Commit operations for System.Transactions and MSDTC have always returned after the commit decision has been made, and the resource managers have been expected to provide isolation of their data.
Note that we have considered supplying transaction aware isolation classes that resource managers could use to simplify the code required to meet the isolation requirement.Would you be interested in such a feature?
Jim.
Jim,
Thanks for clearing that out. Can you give me an example on how a ResourceManager could block access to its data between the prepare and rollback/commit phases?
Features for simplifying isolation should be handy.
In my case I would be interested to have a feature for telling the TransactionManager that not return from commit/rollback until a specific ResourceManager has its Commit/Rollback method called. You stated that this behavior could be set globally for the TransactionManager but since this feature is highly ResourceManager specific, and can be different for multiple managers participating in the same Transaction, this should be configurable per ResourceManager instance.
An example, if I have two ResourceManagers with different isolation needs
RMA - ResourceManager for a sqlserver (can be committed after transaction scope ends)
RMB - ResourceManager for a appdomain data (cannot be committed after a transactionscope ends)
An output from this setup could look like
TransactionScope Starts
Enlist RMA
Enlist RMB
TransactionScope Dispose Starts
TransactionManager starts preparing the ResourceMangers
Prepare RMA
Prepare RMB
RMB tells the TransactionManager to wait until its 2phase has been called
TransactionManager start processing the registered "2phaseCompleteResourceManagers"
Commit RMB
TransactionManager threads the remaining ResourceManager calls.
Thread Commit RMA
TransactionScope Dispose Ends
TransactionScope Ends
Commit RMA (this can be activated anywhere after the Thread has started)
The above scenario should be enough to meet my needs.
Dan,
I think it may be useful to look at the model for a resource manager for a second.A resource manager is a component that provides a 'transactionally perfect' view of the underlying data.It ensures that the data responds to commit and abort, it is capable of voting, and (as appropriate) capable of recovery.It is also responsible for ensuring that the correct level of isolation on the data is retained.
The typical (in fact, the only, in my experience) way to do this is to encapsulate the data behind a component interface.That interface mediates access to the data, and performs whatever locking and fixups are necessary to provide this view.In the case of isolation this is normally done using some form of transactionally-aware lock manager.So, a getter might look something like:
Foo MyValue
{
get
{
GetLockOnTx (Transaction.Current); // This waits for the lock
return FixedUpValueOfFoo (); // This returns the view data
}
}
The classes we've been considering would make the GetLockOnTx operation much simpler.
Note that it isn't just 'get lock' as two accessors coming in on the same transaction should be able to see dirty data.
This locking operation is also sufficient to answer the commit/abort return.
Jim.