public async Task Transaction_Avoid_Dirty_Read() { var data1 = DataGen.Person(1, 100).ToArray(); var data2 = DataGen.Person(101, 200).ToArray(); using (var db = new LiteDatabase(new MemoryStream())) using (var asyncDb = new LiteDatabaseAsync(db, false)) { var asyncPerson = asyncDb.GetCollection <Person>(); var person = db.GetCollection <Person>(); // init person collection with 100 document await asyncPerson.InsertAsync(data1); var taskASemaphore = new SemaphoreSlim(0, 1); var taskBSemaphore = new SemaphoreSlim(0, 1); // task A will open transaction and will insert +100 documents // but will commit only 1s later - this plus +100 document must be visible only inside task A var ta = Task.Run(async() => { await asyncDb.BeginTransAsync(); await asyncPerson.InsertAsync(data2); taskBSemaphore.Release(); await taskASemaphore.WaitAsync(); var count = await asyncPerson.CountAsync(); count.Should().Be(data1.Length + data2.Length); await asyncDb.CommitAsync(); taskBSemaphore.Release(); }); // task B will not open transaction and will wait 250ms before and count collection - // at this time, task A already insert +100 document but here I can't see (are not committed yet) // after task A finish, I can see now all 200 documents var tb = Task.Run(() => { taskBSemaphore.Wait(); var count = person.Count(); // read 100 documents count.Should().Be(data1.Length); taskASemaphore.Release(); taskBSemaphore.Wait(); // read 200 documents count = person.Count(); count.Should().Be(data1.Length + data2.Length); }); await Task.WhenAll(ta, tb); } }
public async Task Transaction_Read_Version() { var data1 = DataGen.Person(1, 100).ToArray(); var data2 = DataGen.Person(101, 200).ToArray(); using (var db = new LiteDatabase(new MemoryStream())) using (var asyncDb = new LiteDatabaseAsync(db, false)) { var asyncPerson = asyncDb.GetCollection <Person>(); var person = db.GetCollection <Person>(); // init person collection with 100 document await asyncPerson.InsertAsync(data1); var taskASemaphore = new SemaphoreSlim(0, 1); var taskBSemaphore = new SemaphoreSlim(0, 1); // task A will insert more 100 documents but will commit only 1s later var ta = Task.Run(async() => { await asyncDb.BeginTransAsync(); await asyncPerson.InsertAsync(data2); taskBSemaphore.Release(); taskASemaphore.Wait(); await asyncDb.CommitAsync(); taskBSemaphore.Release(); }); // task B will open transaction too and will count 100 original documents only // but now, will wait task A finish - but is in transaction and must see only initial version var tb = Task.Run(() => { db.BeginTrans(); taskBSemaphore.Wait(); var count = person.Count(); // read 100 documents count.Should().Be(data1.Length); taskASemaphore.Release(); taskBSemaphore.Wait(); // keep reading 100 documents because i'm still in same transaction count = person.Count(); count.Should().Be(data1.Length); }); await Task.WhenAll(ta, tb); } }
public async Task Transaction_Write_Lock_Timeout() { var data1 = DataGen.Person(1, 100).ToArray(); var data2 = DataGen.Person(101, 200).ToArray(); using (var db = new LiteDatabase("filename=:memory:")) using (var asyncDb = new LiteDatabaseAsync(db, false)) { // small timeout await asyncDb.PragmaAsync(Pragmas.TIMEOUT, 1); var asyncPerson = asyncDb.GetCollection <Person>(); var person = db.GetCollection <Person>(); // init person collection with 100 document await asyncPerson.InsertAsync(data1); var taskASemaphore = new SemaphoreSlim(0, 1); var taskBSemaphore = new SemaphoreSlim(0, 1); // task A will open transaction and will insert +100 documents // but will commit only 2s later var ta = Task.Run(async() => { await asyncDb.BeginTransAsync(); await asyncPerson.InsertAsync(data2); taskBSemaphore.Release(); taskASemaphore.Wait(); var count = await asyncPerson.CountAsync(); count.Should().Be(data1.Length + data2.Length); await asyncDb.CommitAsync(); }); // task B will try delete all documents but will be locked during 1 second var tb = Task.Run(() => { taskBSemaphore.Wait(); db.BeginTrans(); person .Invoking(personCol => personCol.DeleteMany("1 = 1")) .Should() .Throw <LiteException>() .Where(ex => ex.ErrorCode == LiteException.LOCK_TIMEOUT); taskASemaphore.Release(); }); await Task.WhenAll(ta, tb); } }
public async Task Test_Transaction_States() { var data0 = DataGen.Person(1, 10).ToArray(); var data1 = DataGen.Person(11, 20).ToArray(); using (var db = new LiteDatabaseAsync(new MemoryStream())) { var person = db.GetCollection <Person>(); // first time transaction will be opened (await db.BeginTransAsync()).Should().BeTrue(); // but in second type transaction will be same (await db.BeginTransAsync()).Should().BeFalse(); await person.InsertAsync(data0); // must commit transaction (await db.CommitAsync()).Should().BeTrue(); // no transaction to commit (await db.CommitAsync()).Should().BeFalse(); // no transaction to rollback; (await db.RollbackAsync()).Should().BeFalse(); (await db.BeginTransAsync()).Should().BeTrue(); // no page was changed but ok, let's rollback anyway (await db.RollbackAsync()).Should().BeTrue(); // auto-commit await person.InsertAsync(data1); (await person.CountAsync()).Should().Be(20); } }