Skip to content

Commit

Permalink
Add auto lifetime
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed Sep 15, 2023
1 parent 3bcbd15 commit 923b6d6
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 23 deletions.
42 changes: 42 additions & 0 deletions src/Lifetime/AutoLifetime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Linq.Expressions;
using Stashbox.Registration;
using Stashbox.Resolution;

namespace Stashbox.Lifetime;

internal class AutoLifetime : LifetimeDescriptor
{
private LifetimeDescriptor selectedLifetime;

internal override bool StoreResultInLocalVariable => this.selectedLifetime.StoreResultInLocalVariable;

protected internal override int LifeSpan => this.selectedLifetime.LifeSpan;

public AutoLifetime(LifetimeDescriptor boundaryLifetime)
{
this.selectedLifetime = boundaryLifetime;
}

private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
{
var tracker = new ResolutionContext.AutoLifetimeTracker();
var context = resolutionContext.BeginAutoLifetimeTrackingContext(tracker);
var expression = GetExpressionForRegistration(serviceRegistration, context, typeInformation);
this.selectedLifetime = tracker.HighestRankingLifetime.LifeSpan <= this.selectedLifetime.LifeSpan
? tracker.HighestRankingLifetime
: this.selectedLifetime;

var func = expression?.CompileDelegate(context, context.CurrentContainerContext.ContainerConfiguration);
if (func == null) return null;

var final = Expression.Invoke(func.AsConstant(), context.CurrentScopeParameter, context.RequestContextParameter);

return this.selectedLifetime.ApplyLifetimeToExpression(final, serviceRegistration, resolutionContext, typeInformation);
}

internal override Expression? ApplyLifetimeToExpression(Expression? expression,
ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation) =>
this.selectedLifetime.ApplyLifetimeToExpression(expression, serviceRegistration, resolutionContext, typeInformation);
}
5 changes: 5 additions & 0 deletions src/Lifetime/EmptyLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ internal class EmptyLifetime : LifetimeDescriptor
private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
=> null;

internal override Expression? ApplyLifetimeToExpression(Expression? expression,
ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
=> null;
}
12 changes: 9 additions & 3 deletions src/Lifetime/ExpressionLifetimeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ public abstract class ExpressionLifetimeDescriptor : LifetimeDescriptor
ResolutionContext resolutionContext, TypeInformation typeInformation)
{
var expression = GetExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation);
return expression == null ? null : this.ApplyLifetime(expression, serviceRegistration, resolutionContext, typeInformation.Type);
return this.ApplyLifetimeToExpression(expression, serviceRegistration, resolutionContext, typeInformation);
}

internal override Expression? ApplyLifetimeToExpression(Expression? expression,
ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation) => expression == null
? null
: this.ApplyLifetime(expression, serviceRegistration, resolutionContext, typeInformation);

/// <summary>
/// Derived types are using this method to apply their lifetime to the instance creation.
/// </summary>
/// <param name="expression">The expression the lifetime should apply to.</param>
/// <param name="serviceRegistration">The service registration.</param>
/// <param name="resolutionContext">The info about the actual resolution.</param>
/// <param name="resolveType">The type of the resolved service.</param>
/// <param name="typeInformation">The type information of the resolved service.</param>
/// <returns>The lifetime managed expression.</returns>
protected abstract Expression ApplyLifetime(Expression expression, ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, Type resolveType);
ResolutionContext resolutionContext, TypeInformation typeInformation);
}
13 changes: 10 additions & 3 deletions src/Lifetime/FactoryLifetimeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ public abstract class FactoryLifetimeDescriptor : LifetimeDescriptor
ResolutionContext resolutionContext, TypeInformation typeInformation)
{
var factory = GetFactoryDelegateForRegistration(serviceRegistration, resolutionContext, typeInformation);
return factory == null ? null : this.ApplyLifetime(factory, serviceRegistration, resolutionContext, typeInformation.Type);
return factory == null ? null : this.ApplyLifetime(factory, serviceRegistration, resolutionContext, typeInformation);
}

internal override Expression? ApplyLifetimeToExpression(Expression? expression,
ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation) => expression == null
? null
: this.ApplyLifetime(expression.CompileDelegate(resolutionContext, resolutionContext.CurrentContainerContext.ContainerConfiguration),
serviceRegistration, resolutionContext, typeInformation);

private static Func<IResolutionScope, IRequestContext, object>? GetFactoryDelegateForRegistration(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
Expand Down Expand Up @@ -46,8 +53,8 @@ public abstract class FactoryLifetimeDescriptor : LifetimeDescriptor
/// <param name="factory">The factory which can be used to instantiate the service.</param>
/// <param name="serviceRegistration">The service registration.</param>
/// <param name="resolutionContext">The info about the actual resolution.</param>
/// <param name="resolveType">The type of the resolved service.</param>
/// <param name="typeInformation">The type information of the resolved service.</param>
/// <returns>The lifetime managed expression.</returns>
protected abstract Expression ApplyLifetime(Func<IResolutionScope, IRequestContext, object> factory, ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, Type resolveType);
ResolutionContext resolutionContext, TypeInformation typeInformation);
}
13 changes: 10 additions & 3 deletions src/Lifetime/LifetimeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ namespace Stashbox.Lifetime;
/// </summary>
public abstract class LifetimeDescriptor
{

private protected virtual bool StoreResultInLocalVariable => false;
internal virtual bool StoreResultInLocalVariable => false;

/// <summary>
/// An indicator used to validate the lifetime configuration of the resolution tree.
/// Services with longer life-span shouldn't contain dependencies with shorter ones.
/// </summary>
protected virtual int LifeSpan => 0;
protected internal virtual int LifeSpan => 0;

/// <summary>
/// The name of the lifetime, used only for diagnostic reasons.
Expand Down Expand Up @@ -50,6 +49,11 @@ protected LifetimeDescriptor()
$"{serviceRegistration.ImplementationType} ({this.Name}|{this.LifeSpan})");
}

if (resolutionContext.AutoLifetimeTracking != null && this.LifeSpan > resolutionContext.AutoLifetimeTracking.HighestRankingLifetime.LifeSpan)
{
resolutionContext.AutoLifetimeTracking.HighestRankingLifetime = this;
}

if (!this.StoreResultInLocalVariable)
return this.BuildLifetimeAppliedExpression(serviceRegistration, resolutionContext, typeInformation);

Expand All @@ -72,6 +76,9 @@ protected LifetimeDescriptor()

private protected abstract Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation);

internal abstract Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation);

private protected static Expression? GetExpressionForRegistration(ServiceRegistration serviceRegistration,
ResolutionContext resolutionContext, TypeInformation typeInformation)
Expand Down
10 changes: 10 additions & 0 deletions src/Lifetime/Lifetimes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ public static class Lifetimes
/// <summary>
/// Produces a NamedScope lifetime.
/// </summary>
/// <param name="name">The name of the scope.</param>
/// <returns>A named-scope lifetime.</returns>
public static LifetimeDescriptor NamedScope(object name) => new NamedScopeLifetime(name);

/// <summary>
/// Produces a lifetime that aligns to the lifetime of the resolved service's dependencies.
/// When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.
/// </summary>
/// <param name="boundaryLifetime">The lifetime that represents a boundary which the derived lifetime must not exceed.</param>
/// <returns>An auto lifetime.</returns>
public static LifetimeDescriptor Auto(LifetimeDescriptor boundaryLifetime) => new AutoLifetime(boundaryLifetime);

internal static readonly LifetimeDescriptor Empty = new EmptyLifetime();
}
4 changes: 2 additions & 2 deletions src/Lifetime/NamedScopeLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class NamedScopeLifetime : FactoryLifetimeDescriptor
public readonly object ScopeName;

/// <inheritdoc />
protected override int LifeSpan => 10;
protected internal override int LifeSpan => 10;

/// <summary>
/// Constructs a <see cref="NamedScopeLifetime"/>.
Expand All @@ -34,7 +34,7 @@ public NamedScopeLifetime(object scopeName)

/// <inheritdoc />
protected override Expression ApplyLifetime(Func<IResolutionScope, IRequestContext, object> factory,
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType) =>
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) =>
GetScopeValueMethod.CallStaticMethod(resolutionContext.CurrentScopeParameter,
resolutionContext.RequestContextParameter,
factory.AsConstant(),
Expand Down
4 changes: 2 additions & 2 deletions src/Lifetime/PerRequestLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ namespace Stashbox.Lifetime;
public class PerRequestLifetime : FactoryLifetimeDescriptor
{
/// <inheritdoc />
private protected override bool StoreResultInLocalVariable => true;
internal override bool StoreResultInLocalVariable => true;

/// <inheritdoc />
protected override Expression ApplyLifetime(Func<IResolutionScope, IRequestContext, object> factory,
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType)
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation)
{
resolutionContext.RequestConfiguration.RequiresRequestContext = true;

Expand Down
2 changes: 1 addition & 1 deletion src/Lifetime/PerScopedRequestLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
public class PerScopedRequestLifetime : TransientLifetime
{
/// <inheritdoc />
private protected override bool StoreResultInLocalVariable => true;
internal override bool StoreResultInLocalVariable => true;
}
6 changes: 3 additions & 3 deletions src/Lifetime/ScopedLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ namespace Stashbox.Lifetime;
public class ScopedLifetime : FactoryLifetimeDescriptor
{
/// <inheritdoc />
protected override int LifeSpan => 10;
protected internal override int LifeSpan => 10;

/// <inheritdoc />
private protected override bool StoreResultInLocalVariable => true;
internal override bool StoreResultInLocalVariable => true;

/// <inheritdoc />
protected override Expression ApplyLifetime(Func<IResolutionScope, IRequestContext, object> factory,
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType)
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation)
{
if (resolutionContext.CurrentContainerContext.ContainerConfiguration.LifetimeValidationEnabled &&
resolutionContext.IsRequestedFromRoot)
Expand Down
6 changes: 3 additions & 3 deletions src/Lifetime/SingletonLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ namespace Stashbox.Lifetime;
public class SingletonLifetime : FactoryLifetimeDescriptor
{
/// <inheritdoc />
protected override int LifeSpan => 20;
protected internal override int LifeSpan => 20;

/// <inheritdoc />
private protected override bool StoreResultInLocalVariable => true;
internal override bool StoreResultInLocalVariable => true;

/// <inheritdoc />
protected override Expression ApplyLifetime(Func<IResolutionScope, IRequestContext, object> factory,
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType)
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation)
{
var rootScope = resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.ReBuildSingletonsInChildContainerEnabled
? resolutionContext.RequestInitiatorContainerContext.RootScope
Expand Down
2 changes: 1 addition & 1 deletion src/Lifetime/TransientLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public class TransientLifetime : ExpressionLifetimeDescriptor
{
/// <inheritdoc />
protected override Expression ApplyLifetime(Expression expression,
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type resolveType) =>
ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) =>
expression;
}
8 changes: 8 additions & 0 deletions src/Registration/Fluent/BaseFluentConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ public TConfigurator WithLifetime(LifetimeDescriptor lifetime)
/// </summary>
/// <returns>The configurator itself.</returns>
public TConfigurator WithPerRequestLifetime() => this.WithLifetime(Lifetimes.PerRequest);

/// <summary>
/// Sets the lifetime to auto lifetime. This lifetime aligns to the lifetime of the resolved service's dependencies.
/// When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.
/// </summary>
/// <param name="boundaryLifetime">The lifetime that represents a boundary which the derived lifetime must not exceed.</param>
/// <returns>The configurator itself.</returns>
public TConfigurator WithAutoLifetime(LifetimeDescriptor boundaryLifetime) => this.WithLifetime(Lifetimes.Auto(boundaryLifetime));

/// <summary>
/// Sets a scope name condition for the registration, it will be used only when a scope with the given name requests it.
Expand Down
22 changes: 20 additions & 2 deletions src/Resolution/ResolutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Stashbox.Lifetime;

namespace Stashbox.Resolution;

Expand All @@ -17,8 +18,13 @@ public class ResolutionContext
{
internal class PerRequestConfiguration
{
public bool RequiresRequestContext { get; set; }
public bool FactoryDelegateCacheEnabled { get; set; }
public bool RequiresRequestContext;
public bool FactoryDelegateCacheEnabled;
}

internal class AutoLifetimeTracker
{
public LifetimeDescriptor HighestRankingLifetime = Lifetimes.Transient;
}

private readonly bool shouldFallBackToRequestInitiatorContext;
Expand All @@ -42,6 +48,7 @@ internal class PerRequestConfiguration
internal readonly bool UnknownTypeCheckDisabled;
internal readonly RequestContext RequestContext;
internal readonly bool IsValidationContext;
internal readonly AutoLifetimeTracker? AutoLifetimeTracking;

/// <summary>
/// True if null result is allowed, otherwise false.
Expand Down Expand Up @@ -101,6 +108,7 @@ private ResolutionContext(IEnumerable<object> initialScopeNames,
this.RequestConfiguration.FactoryDelegateCacheEnabled = this.PerResolutionRequestCacheEnabled = dependencyOverrides == null;
this.RequestContext = dependencyOverrides != null ? RequestContext.FromOverrides(dependencyOverrides) : RequestContext.Begin();
this.IsValidationContext = isValidationContext;
this.AutoLifetimeTracking = null;

this.ExpressionOverrides = dependencyOverrides == null && (knownInstances == null || knownInstances.IsEmpty)
? null
Expand Down Expand Up @@ -128,6 +136,7 @@ private ResolutionContext(PerRequestConfiguration perRequestConfiguration,
HashTree<object, ConstantExpression>? expressionOverrides,
ExpandableArray<Pair<bool, ParameterExpression>[]> parameterExpressions,
RequestContext requestContext,
AutoLifetimeTracker? autoLifetimeTracker,
ResolutionBehavior resolutionBehavior,
ResolutionBehavior requestInitiatorResolutionBehavior,
bool nullResultAllowed,
Expand Down Expand Up @@ -165,6 +174,7 @@ private ResolutionContext(PerRequestConfiguration perRequestConfiguration,
this.IsValidationContext = isValidationContext;
this.ResolutionBehavior = resolutionBehavior;
this.RequestInitiatorResolutionBehavior = requestInitiatorResolutionBehavior;
this.AutoLifetimeTracking = autoLifetimeTracker;
}

/// <summary>
Expand Down Expand Up @@ -262,6 +272,12 @@ internal ResolutionContext BeginDecoratingContext(Type decoratingType, IEnumerab
internal ResolutionContext BeginLifetimeValidationContext(int lifeSpan, string currentlyLifeSpanValidatingService) =>
this.Clone(currentLifeSpan: lifeSpan, nameOfServiceLifeSpanValidatingAgainst: currentlyLifeSpanValidatingService);

internal ResolutionContext BeginAutoLifetimeTrackingContext(AutoLifetimeTracker autoLifetimeTracker) =>
this.Clone(autoLifetimeTracker: autoLifetimeTracker,
definedVariables: new Tree<ParameterExpression>(),
singleInstructions: new ExpandableArray<Expression>(),
cachedExpressions: new Tree<Expression>());

private static HashTree<object, ConstantExpression> ProcessDependencyOverrides(object[]? dependencyOverrides, ImmutableTree<object, object>? knownInstances)
{
var result = new HashTree<object, ConstantExpression>();
Expand Down Expand Up @@ -297,6 +313,7 @@ private ResolutionContext Clone(
IContainerContext? currentContainerContext = null,
ExpandableArray<Pair<bool, ParameterExpression>[]>? parameterExpressions = null,
ResolutionBehavior? resolutionBehavior = null,
AutoLifetimeTracker? autoLifetimeTracker = null,
string? nameOfServiceLifeSpanValidatingAgainst = null,
int? currentLifeSpan = null,
bool? perResolutionRequestCacheEnabled = null,
Expand All @@ -318,6 +335,7 @@ private ResolutionContext Clone(
this.ExpressionOverrides,
parameterExpressions ?? this.ParameterExpressions,
this.RequestContext,
autoLifetimeTracker ?? this.AutoLifetimeTracking,
resolutionBehavior ?? this.ResolutionBehavior,
this.RequestInitiatorResolutionBehavior,
this.NullResultAllowed,
Expand Down
Loading

0 comments on commit 923b6d6

Please sign in to comment.