Skip to content

Commit

Permalink
Fix deadlock in transform async (#405)
Browse files Browse the repository at this point in the history
* Fix diabolical deadlock inducing aberration of an  implementation of transform async.  This fixes #157 hopefully

* Fix transform async error handling test
  • Loading branch information
RolandPheasant authored Sep 15, 2020
1 parent 755250a commit 32c9c93
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 197 deletions.
357 changes: 177 additions & 180 deletions src/DynamicData.Tests/Cache/TransformAsyncFixture.cs
Original file line number Diff line number Diff line change
@@ -1,193 +1,190 @@
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using DynamicData.Tests.Domain;
using FluentAssertions;
using Xunit;

namespace DynamicData.Tests.Cache
{
[Obsolete("Not obsolete - test commented out due to test run freezing on Appveyor")]
public class TransformAsyncFixture
{
//[Fact]
//public void ReTransformAll()
//{
// var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray();
// var forceTransform = new Subject<Unit>();

// using (var stub = new TransformStub(forceTransform))
// {
// stub.Source.AddOrUpdate(people);
// forceTransform.OnNext(Unit.Default);

// stub.Results.Messages.Count.Should().Be(2);
// stub.Results.Messages[1].Updates.Should().Be(10);

// for (int i = 1; i <= 10; i++)
// {
// var original = stub.Results.Messages[0].ElementAt(i - 1).Current;
// var updated = stub.Results.Messages[1].ElementAt(i - 1).Current;

// updated.Should().Be(original);
// ReferenceEquals(original, updated).Should().BeFalse();
// }
// }
//}

//[Fact]
//public void ReTransformSelected()
//{
// var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray();
// var forceTransform = new Subject<Func<Person, bool>>();

// using (var stub = new TransformStub(forceTransform))
// {
// stub.Source.AddOrUpdate(people);
// forceTransform.OnNext(person => person.Age <= 5);

// stub.Results.Messages.Count.Should().Be(2);
// stub.Results.Messages[1].Updates.Should().Be(5);

// for (int i = 1; i <= 5; i++)
// {
// var original = stub.Results.Messages[0].ElementAt(i - 1).Current;
// var updated = stub.Results.Messages[1].ElementAt(i - 1).Current;
// updated.Should().Be(original);
// ReferenceEquals(original, updated).Should().BeFalse();
// }
// }
//}

//[Fact]
//public async Task Add()
//{
// using (var stub = new TransformStub())
// {
// var person = new Person("Adult1", 50);
// stub.Source.AddOrUpdate(person);

// stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
// stub.Results.Data.Count.Should().Be(1, "Should be 1 item in the cache");

// var firstPerson = await stub.TransformFactory(person);

// stub.Results.Data.Items.First().Should().Be(firstPerson, "Should be same person");
// }
//}

//[Fact]
//public void Remove()
//{
// const string key = "Adult1";
// var person = new Person(key, 50);

// using (var stub = new TransformStub())
// {
// stub.Source.AddOrUpdate(person);
// stub.Source.Remove(key);

// stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
// stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
// stub.Results.Messages[0].Adds.Should().Be(1, "Should be 80 addes");
// stub.Results.Messages[1].Removes.Should().Be(1, "Should be 80 removes");
// stub.Results.Data.Count.Should().Be(0, "Should be nothing cached");
// }
//}

//[Fact]
//public void Update()
//{
// const string key = "Adult1";
// var newperson = new Person(key, 50);
// var updated = new Person(key, 51);

// using (var stub = new TransformStub())
// {
// stub.Source.AddOrUpdate(newperson);
// stub.Source.AddOrUpdate(updated);

// stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
// stub.Results.Messages[0].Adds.Should().Be(1, "Should be 1 adds");
// stub.Results.Messages[1].Updates.Should().Be(1, "Should be 1 update");
// }
//}

//[Fact]
//public async Task BatchOfUniqueUpdates()
//{
// var people = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray();
// using (var stub = new TransformStub())
// {
// stub.Source.AddOrUpdate(people);

// stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
// stub.Results.Messages[0].Adds.Should().Be(100, "Should return 100 adds");

// var result = await Task.WhenAll(people.Select(stub.TransformFactory));
// var transformed = result.OrderBy(p => p.Age).ToArray();
// stub.Results.Data.Items.OrderBy(p => p.Age).Should().BeEquivalentTo(stub.Results.Data.Items.OrderBy(p => p.Age), "Incorrect transform result");
// }
//}

//[Fact]
//public async Task SameKeyChanges()
//{
// using (var stub = new TransformStub())
// {
// var people = Enumerable.Range(1, 10).Select(i => new Person("Name", i)).ToArray();

// stub.Source.AddOrUpdate(people);

// stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
// stub.Results.Messages[0].Adds.Should().Be(1, "Should return 1 adds");
// stub.Results.Messages[0].Updates.Should().Be(9, "Should return 9 adds");
// stub.Results.Data.Count.Should().Be(1, "Should result in 1 record");

// var lastTransformed = await stub.TransformFactory(people.Last());
// var onlyItemInCache = stub.Results.Data.Items.First();

// onlyItemInCache.Should().Be(lastTransformed, "Incorrect transform result");
// }
//}

//[Fact]
//public void Clear()
//{
// using (var stub = new TransformStub())
// {
// var people = Enumerable.Range(1, 100).Select(l => new Person("Name" + l, l)).ToArray();

// stub.Source.AddOrUpdate(people);
// stub.Source.Clear();

// stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
// stub.Results.Messages[0].Adds.Should().Be(100, "Should be 80 addes");
// stub.Results.Messages[1].Removes.Should().Be(100, "Should be 80 removes");
// stub.Results.Data.Count.Should().Be(0, "Should be nothing cached");
// }
//}

//[Fact]
//public void HandleError()
//{
// using (var stub = new TransformStub(p =>
// {
// throw new Exception("Broken");
// }))
// {
// var people = Enumerable.Range(1, 100).Select(l => new Person("Name" + l, l)).ToArray();
// stub.Source.AddOrUpdate(people);

// stub.Results.Error.Should().NotBeNull();

// Exception error = null;
// stub.Source.Connect()
// .Subscribe(changes => { }, ex => error = ex);

// error.Should().NotBeNull();

// }
//}
[Fact]
public void ReTransformAll()
{
var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray();
var forceTransform = new Subject<Unit>();

using (var stub = new TransformStub(forceTransform))
{
stub.Source.AddOrUpdate(people);
forceTransform.OnNext(Unit.Default);

stub.Results.Messages.Count.Should().Be(2);
stub.Results.Messages[1].Updates.Should().Be(10);

for (int i = 1; i <= 10; i++)
{
var original = stub.Results.Messages[0].ElementAt(i - 1).Current;
var updated = stub.Results.Messages[1].ElementAt(i - 1).Current;

updated.Should().Be(original);
ReferenceEquals(original, updated).Should().BeFalse();
}
}
}

[Fact]
public void ReTransformSelected()
{
var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray();
var forceTransform = new Subject<Func<Person, bool>>();

using (var stub = new TransformStub(forceTransform))
{
stub.Source.AddOrUpdate(people);
forceTransform.OnNext(person => person.Age <= 5);

stub.Results.Messages.Count.Should().Be(2);
stub.Results.Messages[1].Updates.Should().Be(5);

for (int i = 1; i <= 5; i++)
{
var original = stub.Results.Messages[0].ElementAt(i - 1).Current;
var updated = stub.Results.Messages[1].ElementAt(i - 1).Current;
updated.Should().Be(original);
ReferenceEquals(original, updated).Should().BeFalse();
}
}
}

[Fact]
public async Task Add()
{
using (var stub = new TransformStub())
{
var person = new Person("Adult1", 50);
stub.Source.AddOrUpdate(person);

stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
stub.Results.Data.Count.Should().Be(1, "Should be 1 item in the cache");

var firstPerson = await stub.TransformFactory(person);

stub.Results.Data.Items.First().Should().Be(firstPerson, "Should be same person");
}
}

[Fact]
public void Remove()
{
const string key = "Adult1";
var person = new Person(key, 50);

using (var stub = new TransformStub())
{
stub.Source.AddOrUpdate(person);
stub.Source.Remove(key);

stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
stub.Results.Messages[0].Adds.Should().Be(1, "Should be 80 addes");
stub.Results.Messages[1].Removes.Should().Be(1, "Should be 80 removes");
stub.Results.Data.Count.Should().Be(0, "Should be nothing cached");
}
}

[Fact]
public void Update()
{
const string key = "Adult1";
var newperson = new Person(key, 50);
var updated = new Person(key, 51);

using (var stub = new TransformStub())
{
stub.Source.AddOrUpdate(newperson);
stub.Source.AddOrUpdate(updated);

stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
stub.Results.Messages[0].Adds.Should().Be(1, "Should be 1 adds");
stub.Results.Messages[1].Updates.Should().Be(1, "Should be 1 update");
}
}

[Fact]
public async Task BatchOfUniqueUpdates()
{
var people = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray();
using (var stub = new TransformStub())
{
stub.Source.AddOrUpdate(people);

// Thread.Sleep(10000);

stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
stub.Results.Messages[0].Adds.Should().Be(100, "Should return 100 adds");

var result = await Task.WhenAll(people.Select(stub.TransformFactory));
var transformed = result.OrderBy(p => p.Age).ToArray();
stub.Results.Data.Items.OrderBy(p => p.Age).Should().BeEquivalentTo(stub.Results.Data.Items.OrderBy(p => p.Age), "Incorrect transform result");
}


}

[Fact]
public async Task SameKeyChanges()
{
using (var stub = new TransformStub())
{
var people = Enumerable.Range(1, 10).Select(i => new Person("Name", i)).ToArray();

stub.Source.AddOrUpdate(people);

stub.Results.Messages.Count.Should().Be(1, "Should be 1 updates");
stub.Results.Messages[0].Adds.Should().Be(1, "Should return 1 adds");
stub.Results.Messages[0].Updates.Should().Be(9, "Should return 9 adds");
stub.Results.Data.Count.Should().Be(1, "Should result in 1 record");

var lastTransformed = await stub.TransformFactory(people.Last());
var onlyItemInCache = stub.Results.Data.Items.First();

onlyItemInCache.Should().Be(lastTransformed, "Incorrect transform result");
}
}

[Fact]
public void Clear()
{
using (var stub = new TransformStub())
{
var people = Enumerable.Range(1, 100).Select(l => new Person("Name" + l, l)).ToArray();

stub.Source.AddOrUpdate(people);
stub.Source.Clear();

stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates");
stub.Results.Messages[0].Adds.Should().Be(100, "Should be 80 adds");
stub.Results.Messages[1].Removes.Should().Be(100, "Should be 80 removes");
stub.Results.Data.Count.Should().Be(0, "Should be nothing cached");
}
}

[Fact]
public void HandleError()
{
using (var stub = new TransformStub(p => throw new Exception("Broken")))
{
stub.Source.AddOrUpdate(new Person("Name1" ,1));

stub.Results.Error.Should().NotBeNull();

}
}

private class TransformStub : IDisposable
{
Expand Down
Loading

0 comments on commit 32c9c93

Please sign in to comment.