Skip to content

Commit

Permalink
Merge pull request #7 from Ceilidh-Team/shutdown
Browse files Browse the repository at this point in the history
Dispose and version
  • Loading branch information
OrionNebula authored Oct 10, 2018
2 parents 266df53 + ebc8f5a commit 929a87f
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 27 deletions.
26 changes: 26 additions & 0 deletions ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,32 @@ public async Task DictInstanceGenerator()
Assert.Equal("Hi", testUnit.TestValue);
}

[Fact]
public async Task DisposeTest()
{
_context.AddManaged<DisposeTestUnit>();

await _context.ExecuteAsync();
Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed);
_context.Dispose();
Assert.True(testUnit.IsDisposed);
}

private class DisposeTestUnit : IDisposable
{
public bool IsDisposed { get; private set; }

public DisposeTestUnit()
{
IsDisposed = false;
}

public void Dispose()
{
IsDisposed = true;
}
}

private interface ITestUnit
{
string TestValue { get; }
Expand Down
33 changes: 32 additions & 1 deletion ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace ProjectCeilidh.Cobble.Tests
{
public class CobbleContextTests
public class CobbleContextTests : IDisposable
{
private readonly CobbleContext _context;

Expand Down Expand Up @@ -107,6 +107,32 @@ public void DictInstanceGenerator()
Assert.Equal("Hi", testUnit.TestValue);
}

[Fact]
public void DisposeTest()
{
_context.AddManaged<DisposeTestUnit>();

_context.Execute();
Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed);
_context.Dispose();
Assert.True(testUnit.IsDisposed);
}

private class DisposeTestUnit : IDisposable
{
public bool IsDisposed { get; private set; }

public DisposeTestUnit()
{
IsDisposed = false;
}

public void Dispose()
{
IsDisposed = true;
}
}

private interface ITestUnit
{
string TestValue { get; }
Expand Down Expand Up @@ -151,5 +177,10 @@ public void UnitLoaded(ITestUnit unit)
TestUnits.Add(unit);
}
}

public void Dispose()
{
_context.Dispose();
}
}
}
41 changes: 27 additions & 14 deletions ProjectCeilidh.Cobble/CobbleContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using ProjectCeilidh.Cobble.Data;
using ProjectCeilidh.Cobble.Generator;
Expand All @@ -11,7 +12,8 @@ namespace ProjectCeilidh.Cobble
/// <summary>
/// An application context which allows for dependency graph construction and object creation.
/// </summary>
public sealed class CobbleContext
/// <inheritdoc />
public sealed class CobbleContext : IDisposable
{
public delegate object DuplicateResolutionHandler(Type dependencyType, object[] instances);

Expand All @@ -22,6 +24,7 @@ public sealed class CobbleContext
private readonly List<IInstanceGenerator> _instanceGenerators;
private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _lateInjectInstances;
private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _implementations;
private readonly ConcurrentBag<IDisposable> _disposeHooks;

/// <summary>
/// Construct a new CobbleContext.
Expand All @@ -31,6 +34,7 @@ public CobbleContext()
_instanceGenerators = new List<IInstanceGenerator>();
_lateInjectInstances = new ConcurrentDictionary<Type, ConcurrentBag<object>>();
_implementations = new ConcurrentDictionary<Type, ConcurrentBag<object>>();
_disposeHooks = new ConcurrentBag<IDisposable>();

AddUnmanaged(this);
}
Expand Down Expand Up @@ -94,7 +98,7 @@ public void AddManaged(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));

if (type.GetConstructors().Length != 1)
if (type.GetTypeInfo().DeclaredConstructors.Count() != 1)
throw new ArgumentException("Managed types must have exactly one public constructor.", nameof(type));

AddManaged(new TypeLateInstanceGenerator(type));
Expand All @@ -113,13 +117,13 @@ public void AddManaged(IInstanceGenerator generator)
else
{
var inst = CreateInstance(generator, _implementations);
PushInstanceProvides(generator, inst, _implementations);
PushInstanceProvides(generator, inst);
foreach(var prov in generator.Provides)
{
if (!_lateInjectInstances.TryGetValue(prov, out var set)) continue;

foreach (var late in set)
late.GetType().GetMethod(nameof(ILateInject<object>.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst });
late.GetType().GetRuntimeMethod(nameof(ILateInject<object>.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst });
}
}
}
Expand All @@ -135,7 +139,7 @@ public void Execute()

// Create a lookup which maps provided type to the set off all generators that provide it.
var implMap = _instanceGenerators
.SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x)))
.SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x }))
.ToLookup(x => x.Type, x => x.Generator);

var graph = new DirectedGraph<IInstanceGenerator>(_instanceGenerators);
Expand All @@ -147,7 +151,7 @@ public void Execute()
var depType = dep;

if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
depType = dep.GetGenericArguments()[0];
depType = dep.GenericTypeArguments[0];

foreach (var impl in implMap[depType])
graph.Link(impl, gen);
Expand All @@ -159,7 +163,7 @@ public void Execute()
foreach (var gen in graph.TopologicalSort()) // Sort the dependency graph topologically - all dependencies should be satisfied by the time we get to each unit
{
var obj = CreateInstance(gen, _implementations);
PushInstanceProvides(gen, obj, _implementations);
PushInstanceProvides(gen, obj);

if (!(gen is ILateInstanceGenerator late)) continue;

Expand Down Expand Up @@ -187,7 +191,7 @@ public async Task ExecuteAsync()

// Create a lookup which maps provided type to the set off all generators that provide it.
var implMap = _instanceGenerators
.SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x)))
.SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x }))
.ToLookup(x => x.Type, x => x.Generator);

var graph = new DirectedGraph<IInstanceGenerator>(_instanceGenerators);
Expand All @@ -199,7 +203,7 @@ public async Task ExecuteAsync()
var depType = dep;

if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
depType = dep.GetGenericArguments()[0];
depType = dep.GenericTypeArguments[0];

foreach (var impl in implMap[depType])
graph.Link(impl, gen);
Expand All @@ -211,7 +215,7 @@ public async Task ExecuteAsync()
await graph.ParallelTopologicalSort(gen =>
{
var obj = CreateInstance(gen, _implementations);
PushInstanceProvides(gen, obj, _implementations);
PushInstanceProvides(gen, obj);
if (!(gen is ILateInstanceGenerator late)) return;
Expand All @@ -230,20 +234,29 @@ await graph.ParallelTopologicalSort(gen =>
}
}

public void Dispose()
{
while (_disposeHooks.TryTake(out var result))
result.Dispose();
}

/// <summary>
/// Given a generator and an instance, push it into the relevant provider dictionaries
/// </summary>
/// <param name="gen">The instance generator that produced the instance.</param>
/// <param name="instance">The instance that was produced.</param>
/// <param name="instances">A dictionary mapping provided types to a set of instances.</param>
private static void PushInstanceProvides(IInstanceGenerator gen, object instance, ConcurrentDictionary<Type, ConcurrentBag<object>> instances)
private void PushInstanceProvides(IInstanceGenerator gen, object instance)
{
foreach (var prov in gen.Provides)
instances.AddOrUpdate(prov, x => new ConcurrentBag<object>(new[] {instance}), (a, b) =>
_implementations.AddOrUpdate(prov, x => new ConcurrentBag<object>(new[] {instance}), (a, b) =>
{
b.Add(instance);
return b;
});

if (instance is IDisposable disp)
_disposeHooks.Add(disp);

}

/// <summary>
Expand All @@ -260,7 +273,7 @@ private object CreateInstance(IInstanceGenerator gen, IDictionary<Type, Concurre
{
if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var depType = dep.GetGenericArguments()[0];
var depType = dep.GenericTypeArguments[0];

if (instances.TryGetValue(depType, out var set))
{
Expand Down
7 changes: 4 additions & 3 deletions ProjectCeilidh.Cobble/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace ProjectCeilidh.Cobble
{
Expand Down Expand Up @@ -36,16 +37,16 @@ public static IEnumerable<T> Unroll<T>(this T value, Func<T, IEnumerable<T>> tra

public static IEnumerable<Type> GetAssignableFrom(this Type type)
{
foreach (var intf in type.GetInterfaces())
foreach (var intf in type.GetTypeInfo().ImplementedInterfaces)
yield return intf;

yield return type;

var b = type.BaseType;
var b = type.GetTypeInfo().BaseType;

while (b != null && b != typeof(object))
{
b = b.BaseType;
b = b.GetTypeInfo().BaseType;
yield return b;
}
}
Expand Down
3 changes: 2 additions & 1 deletion ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ProjectCeilidh.Cobble.Generator
{
Expand All @@ -21,7 +22,7 @@ public BareLateInstanceGenerator(object instance)
_instance = instance ?? throw new ArgumentNullException(nameof(instance));

var type = _instance.GetType();
LateDependencies = type.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray();
LateDependencies = type.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray();
Provides = type.GetAssignableFrom().ToArray();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace ProjectCeilidh.Cobble.Generator
/// </summary>
public class DictionaryInstanceGenerator : IInstanceGenerator
{
private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create)) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible.");
private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetRuntimeMethod(nameof(DispatchProxy.Create), new Type[0]) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible.");

public IEnumerable<Type> Provides { get; }
public IEnumerable<Type> Dependencies { get; }
Expand All @@ -28,8 +28,8 @@ public class DictionaryInstanceGenerator : IInstanceGenerator
/// <param name="implementations">A dictionary containing all implemented functions as delegates.</param>
public DictionaryInstanceGenerator(Type contractType, Delegate ctor, IReadOnlyDictionary<MethodInfo, Delegate> implementations)
{
Dependencies = ctor == null ? new Type[0] : ctor.Method.GetParameters().Select(x => x.ParameterType).ToArray();
Provides = contractType.GetInterfaces().Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.BaseType == typeof(object) || x.BaseType == null ? new Type[0] : new []{ x.BaseType }));
Dependencies = ctor == null ? new Type[0] : ctor.GetMethodInfo().GetParameters().Select(x => x.ParameterType).ToArray();
Provides = contractType.GetTypeInfo().ImplementedInterfaces.Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.GetTypeInfo().BaseType == typeof(object) || x.GetTypeInfo().BaseType == null ? new Type[0] : new []{ x.GetTypeInfo().BaseType }));
_contractType = contractType;
_ctor = ctor;
_implementations = implementations;
Expand Down
7 changes: 4 additions & 3 deletions ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ProjectCeilidh.Cobble.Generator
{
Expand All @@ -19,14 +20,14 @@ public class TypeLateInstanceGenerator : ILateInstanceGenerator
public TypeLateInstanceGenerator(Type target)
{
_target = target;
Dependencies = target.GetConstructors().Single().GetParameters().Select(x => x.ParameterType).ToArray();
Dependencies = target.GetTypeInfo().DeclaredConstructors.Single().GetParameters().Select(x => x.ParameterType).ToArray();
Provides = target.GetAssignableFrom().ToArray();
LateDependencies = target.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray();
LateDependencies = target.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray();
}

public object GenerateInstance(object[] args)
{
var ctor = _target.GetConstructors().Single();
var ctor = _target.GetTypeInfo().DeclaredConstructors.Single();
return ctor.Invoke(args);
}

Expand Down
4 changes: 2 additions & 2 deletions ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard1.3</TargetFramework>
<LangVersion>latest</LangVersion>
<Authors>Olivia Trewin</Authors>
<Company>Project Ceilidh</Company>
Expand All @@ -12,7 +12,7 @@
<RepositoryUrl>https://github.com/Ceilidh-Team/Cobble.git</RepositoryUrl>
<Copyright>2018 Olivia Trewin</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.DispatchProxy" Version="4.5.1" />
Expand Down

0 comments on commit 929a87f

Please sign in to comment.