It's here. What I bascially do is
1) I have a document in Raven (of type LocationView with given id)
2) I create a task (thread) in which I delete this document but after calling SaveChanges I wait for 10 seconds and then rollback the transaction (by not completing)
3) On the main thread I wait for 5 seconds after starting the above thread and try to read the document
4) I then wait another 10 seconds and try to read the document again.
The idea is that 3) will try to read the document after SaveChanges have been called on the deletion but before the transaction (and thereby the deletion) have rolledback. 4) will try to read the document after the deletion have rolledback.
By using forceDistributedTransaction (true/false) I control if a distributed transaction should be used (in my test simply by also using MSMQ as part of the transaction but that could be other RavenDb datatabase or any other resourcemanager that forces a distributed transaction).
Test is green for me no matter If I use distributed transaction or not, but that is just because I show the actual behavior, rather than testing the expected. If not using distributed transaction then I in 3) can not
read the document (because It has been deleted by other transaction and deletion has not rolledback yet).
[Test, Explicit]
public void TransactionTest()
{
var id = new Guid("9e15c907-68da-44a0-8197-81c0ef1d88c9");
const bool forceDistributedTransaction = true;
var documentStore = new DocumentStore() {Url = "
http://localhost:8080", EnlistInDistributedTransactions = true}.Initialize();
// Delete location (savechanges immediately) but rollback efter sleeping 10 seconds
var task1 = new Task(() => DeleteLocationButRollback(documentStore, id, 10000, forceDistributedTransaction), TaskCreationOptions.LongRunning);
task1.Start();
// Try reading location after 5 seconds (deletion have been done by above thread but transaction not committed/rolledback yet)
Thread.Sleep(5000);
if (forceDistributedTransaction == false)
Assert.That(CanReadLocation(documentStore, id, forceDistributedTransaction), Is.False);
// !!!!! This is not expected behavior. This transaction should not be effected by the
// deletion in the other transaction, before it's committed or rolledbck.
else
Assert.That(CanReadLocation(documentStore, id, forceDistributedTransaction), Is.True);
// Try reading location after 15 seconds (now deletion should have rollback)
Thread.Sleep(10000);
Assert.That(CanReadLocation(documentStore, id, forceDistributedTransaction), Is.True);
}
private bool CanReadLocation(IDocumentStore documentStore, Guid locationId, bool forceDistributedTrasaction)
{
using (var tx = new TransactionScope())
{
if (forceDistributedTrasaction) ForceDistributedTransaction();
using (var session = documentStore.OpenSession())
{
session.Advanced.AllowNonAuthoritativeInformation = false;
var locationView = session.Load<LocationView>(locationId);
return locationView != null;
}
tx.Complete();
}
}
private void DeleteLocationButRollback(IDocumentStore documentStore, Guid locationId, Int32 rollbackDelay, bool forceDistributedTransaction)
{
using (var tx = new TransactionScope())
{
if (forceDistributedTransaction) ForceDistributedTransaction();
using (var session = documentStore.OpenSession())
{
session.Advanced.AllowNonAuthoritativeInformation = false;
var locationView = session.Load<LocationView>(locationId);
session.Delete(locationView);
session.SaveChanges();
}
Thread.Sleep(rollbackDelay);
// We don't complete so the deletion will rollback
}
}
/// <summary>
/// Force using distributed transaction by sending message to messagequeue
/// </summary>
private void ForceDistributedTransaction()
{
var messageQueue = new MessageQueue(TestConfiguration.MsmqQueueNameForDomainEvents1);
var message = new Message() {Body = "test"};
messageQueue.Send(message, MessageQueueTransactionType.Automatic);
}