Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ TestResults/*
*.suo
*.user
_ReSharper.*
.vs/
*.nupkg
**/packages/*
*.nuget.props
*.nuget.targets
37 changes: 21 additions & 16 deletions Lifetime.sln
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lifetime", "Lifetime\Lifetime.csproj", "{C5F0A258-0AA4-4644-AD4B-134811518025}"
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2010
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lifetime", "Lifetime\Lifetime.csproj", "{A7112E6A-0643-439F-9F33-B2713FCA9F3A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LifetimeTest", "LifetimeTest\LifetimeTest.csproj", "{B448C282-D446-4624-9A42-7B2CCFA517EB}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LifetimeTest", "LifetimeTest\LifetimeTest.csproj", "{0387C1F5-0D00-4C79-8C47-7DBA35109679}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LifetimeExample", "LifetimeExample\LifetimeExample.csproj", "{9DB14478-561D-4B6F-8919-F5E9D26B4978}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LifetimeExample", "LifetimeExample\LifetimeExample.csproj", "{C00A62C7-7DD3-4068-BB6A-0F2C4B2A9C63}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5F0A258-0AA4-4644-AD4B-134811518025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5F0A258-0AA4-4644-AD4B-134811518025}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5F0A258-0AA4-4644-AD4B-134811518025}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5F0A258-0AA4-4644-AD4B-134811518025}.Release|Any CPU.Build.0 = Release|Any CPU
{B448C282-D446-4624-9A42-7B2CCFA517EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B448C282-D446-4624-9A42-7B2CCFA517EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B448C282-D446-4624-9A42-7B2CCFA517EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B448C282-D446-4624-9A42-7B2CCFA517EB}.Release|Any CPU.Build.0 = Release|Any CPU
{9DB14478-561D-4B6F-8919-F5E9D26B4978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DB14478-561D-4B6F-8919-F5E9D26B4978}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DB14478-561D-4B6F-8919-F5E9D26B4978}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DB14478-561D-4B6F-8919-F5E9D26B4978}.Release|Any CPU.Build.0 = Release|Any CPU
{A7112E6A-0643-439F-9F33-B2713FCA9F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7112E6A-0643-439F-9F33-B2713FCA9F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7112E6A-0643-439F-9F33-B2713FCA9F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7112E6A-0643-439F-9F33-B2713FCA9F3A}.Release|Any CPU.Build.0 = Release|Any CPU
{0387C1F5-0D00-4C79-8C47-7DBA35109679}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0387C1F5-0D00-4C79-8C47-7DBA35109679}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0387C1F5-0D00-4C79-8C47-7DBA35109679}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0387C1F5-0D00-4C79-8C47-7DBA35109679}.Release|Any CPU.Build.0 = Release|Any CPU
{C00A62C7-7DD3-4068-BB6A-0F2C4B2A9C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C00A62C7-7DD3-4068-BB6A-0F2C4B2A9C63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C00A62C7-7DD3-4068-BB6A-0F2C4B2A9C63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C00A62C7-7DD3-4068-BB6A-0F2C4B2A9C63}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6D60882C-2C32-4145-B94F-28FE940DF8B7}
EndGlobalSection
EndGlobal
10 changes: 3 additions & 7 deletions Lifetime/DisposableLifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ namespace TwistedOak.Util {
public sealed class DisposableLifetime : IDisposable {
private readonly LifetimeSource _source = new LifetimeSource();
///<summary>The lifetime that transitions from mortal to dead when the managing DisposableLifetime is disposed.</summary>
public Lifetime Lifetime { get { return _source.Lifetime; } }
public Lifetime Lifetime => _source.Lifetime;
///<summary>Transitions the exposed lifetime from mortal to dead.</summary>
public void Dispose() {
_source.EndLifetime();
}
public void Dispose() => _source.EndLifetime();
///<summary>Returns a text representation of the disposable lifetime's current state.</summary>
public override string ToString() {
return _source.ToString();
}
public override string ToString() => _source.ToString();
}
}
34 changes: 15 additions & 19 deletions Lifetime/Lifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ public struct Lifetime : IEquatable<Lifetime> {
/// </summary>
public static readonly Lifetime Dead = new Lifetime(DeadSoul.Instance);

//Must default to ImmortalSould.Instance in property rather than using auto-prop and setting default
//in contructor so that default(Lifetime).Soul is ImmortalSoul.Instance not null
private readonly ISoul _defSoul;
internal ISoul Soul { get { return _defSoul ?? ImmortalSoul.Instance; } }
internal ISoul Soul => _defSoul ?? ImmortalSoul.Instance;
internal Lifetime(ISoul soul) {
this._defSoul = soul;
_defSoul = soul;
}

/// <summary>Determines if this lifetime is still transiently mortal.</summary>
public bool IsMortal { get { return Soul.Phase == Phase.Mortal; } }
/// <summary>Determines if this lifetime is permanently immortal.</summary>
public bool IsImmortal { get { return Soul.Phase == Phase.Immortal; } }
/// <summary>Determines if this lifetime is permanently dead.</summary>
public bool IsDead { get { return Soul.Phase == Phase.Dead; } }
///<summary>Determines if this lifetime is still transiently mortal.</summary>
public bool IsMortal => Soul.Phase == Phase.Mortal;
///<summary>Determines if this lifetime is permanently immortal.</summary>
public bool IsImmortal => Soul.Phase == Phase.Immortal;
///<summary>Determines if this lifetime is permanently dead.</summary>
public bool IsDead => Soul.Phase == Phase.Dead;

/// <summary>
/// Registers an action to perform when this lifetime is dead.
Expand All @@ -49,7 +51,7 @@ internal Lifetime(ISoul soul) {
/// Defaults to an immortal lifetime.
/// </param>
public void WhenDead(Action action, Lifetime registrationLifetime = default(Lifetime)) {
if (action == null) throw new ArgumentNullException("action");
if (action == null) throw new ArgumentNullException(nameof(action));
var s = Soul;
s.DependentRegister(
() => { if (s.Phase == Phase.Dead) action(); },
Expand Down Expand Up @@ -94,19 +96,13 @@ public static implicit operator Lifetime(CancellationToken token) {
return source.Lifetime;
}

///<summary>Determines if the other lifetime has the same source.</summary>
/// <summary>Determines if the other lifetime has the same source.</summary>
/// <param name="other">The lifetime that this lifetime is being compared to.</param>
public bool Equals(Lifetime other) {
return Equals(Soul, other.Soul);
}
public bool Equals(Lifetime other) => Equals(Soul, other.Soul);
///<summary>Returns the hash code for this lifetime, based on its source.</summary>
public override int GetHashCode() {
return Soul.GetHashCode();
}
public override int GetHashCode() => Soul.GetHashCode();
///<summary>Determines if the other object is a lifetime with the same source.</summary>
public override bool Equals(object obj) {
return obj is Lifetime && Equals((Lifetime)obj);
}
public override bool Equals(object obj) => obj is Lifetime && Equals((Lifetime)obj);
///<summary>Returns a text representation of the lifetime's current state.</summary>
public override string ToString() {
if (Soul.Phase == Phase.Mortal) return "Alive";
Expand Down
92 changes: 30 additions & 62 deletions Lifetime/Lifetime.csproj
Original file line number Diff line number Diff line change
@@ -1,65 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C5F0A258-0AA4-4644-AD4B-134811518025}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TwistedOak.Util</RootNamespace>
<AssemblyName>Lifetime</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile5</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>TwistedOak.Util.Lifetime</PackageId>
<Version>1.0.3</Version>
<Authors>Craig Gidney</Authors>
<Company>Twisted Oak Studios</Company>
<Product></Product>
<Description>Lifetime (improved CancellationToken)

A small library that implements an improved version of System.Threading.CancellationToken with support for removable registrations, to allow for garbage collection in the presence of long-lived tokens, plus some related utilities.

Motivation: cancellation tokens can be the source of memory "leaks", because an unbounded number of completed operations may have registered now-pointless callbacks on a token that can't be released or cancelled. The callbacks may reference all kinds of data, preventing effective garbage collection. Using a Lifetime allows fixing these issues because callback registrations can be given lifetimes of their own.

Additional utilities: LifetimeSource, LifetimeExchanger, DisposableLifetime, Max, Min, CreateDependentSource.</Description>
<Copyright>Twisted Oak Studios, 2012</Copyright>
<PackageLicenseUrl>https://github.com/TwistedOakStudios/Lifetime/blob/master/License.txt</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/TwistedOakStudios/Lifetime</PackageProjectUrl>
<PackageIconUrl>http://i.imgur.com/WP3EF.png</PackageIconUrl>
<PackageTags>lifetime cancellation token</PackageTags>
<PackageReleaseNotes>Using .Net Standard 2.0</PackageReleaseNotes>
<RepositoryType></RepositoryType>
<RepositoryUrl></RepositoryUrl>
<NeutralLanguage></NeutralLanguage>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Lifetime.XML</DocumentationFile>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>bin\Release\netstandard2.0\Lifetime.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Lifetime.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="Soul\AnonymousSoul.cs" />
<Compile Include="Soul\CollapsingSoul.cs" />
<Compile Include="Soul\DeadSoul.cs" />
<Compile Include="Soul\ImmortalSoul.cs" />
<Compile Include="Soul\ISoul.cs" />
<Compile Include="Soul\DoublyLinkedNode.cs" />
<Compile Include="LifetimeSource.cs" />
<Compile Include="DisposableLifetime.cs" />
<Compile Include="LifetimeExchanger.cs" />
<Compile Include="Lifetime.cs" />
<Compile Include="LifetimeUtilities.cs" />
<Compile Include="Soul\Phase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Soul\MortalSoul.cs" />
<Compile Include="Soul\Soul.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

</Project>
6 changes: 3 additions & 3 deletions Lifetime/LifetimeExchanger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ namespace TwistedOak.Util {
///<summary>Creates lifetimes when requested, setting them when the next lifetime is requested.</summary>
public sealed class LifetimeExchanger {
private LifetimeSource _active = new LifetimeSource();

///<summary>Returns the current lifetime, that will be killed or immortalized before the next lifetime is created by the exchanger.</summary>
public Lifetime ActiveLifetime { get { return _active.Lifetime; } }
public Lifetime ActiveLifetime => _active.Lifetime;

///<summary>Returns a newly created mortal lifetime after killing the previously created lifetime (if any).</summary>
public Lifetime StartNextAndEndPreviousLifetime() {
Expand All @@ -20,7 +20,7 @@ public Lifetime StartNextAndEndPreviousLifetime() {
public Lifetime StartNextAndImmortalizePreviousLifetime() {
var next = new LifetimeSource();
var prev = Interlocked.Exchange(ref _active, next);
if (prev != null) prev.ImmortalizeLifetime();
prev?.ImmortalizeLifetime();
return next.Lifetime;
}
}
Expand Down
10 changes: 4 additions & 6 deletions Lifetime/LifetimeSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ namespace TwistedOak.Util {
[DebuggerDisplay("{ToString()}")]
public sealed class LifetimeSource {
private readonly MortalSoul _soul = new MortalSoul();
/// <summary>The lifetime exposed and managed by the lifetime source.</summary>
public Lifetime Lifetime { get; private set; }

///<summary>The lifetime exposed and managed by the lifetime source.</summary>
public Lifetime Lifetime { get; }

///<summary>Creates a new lifetime source managing a new initially mortal lifetime.</summary>
public LifetimeSource() {
Expand Down Expand Up @@ -45,8 +45,6 @@ public void ImmortalizeLifetime() {
}

///<summary>Returns a text representation of the lifetime source's current state.</summary>
public override string ToString() {
return Lifetime.ToString();
}
public override string ToString() => Lifetime.ToString();
}
}
30 changes: 0 additions & 30 deletions Lifetime/Properties/AssemblyInfo.cs

This file was deleted.

14 changes: 5 additions & 9 deletions Lifetime/Soul/AnonymousSoul.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@
using System.Diagnostics;

namespace TwistedOak.Util.Soul {
///<summary>A soul implemented by delegates passed to its constructor.</summary>
/// <summary>A soul implemented by delegates passed to its constructor.</summary>
[DebuggerStepThrough]
internal sealed class AnonymousSoul : ISoul {
private readonly Func<Phase> _phase;
private readonly Func<Action, RegistrationRemover> _register;
public AnonymousSoul(Func<Phase> phase, Func<Action, RegistrationRemover> register) {
if (phase == null) throw new ArgumentNullException("phase");
if (register == null) throw new ArgumentNullException("register");
this._phase = phase;
this._register = register;
_phase = phase ?? throw new ArgumentNullException(nameof(phase));
_register = register ?? throw new ArgumentNullException(nameof(register));
}

public Phase Phase { get { return this._phase(); } }
public RegistrationRemover Register(Action action) {
return this._register(action);
}
public Phase Phase => _phase();
public RegistrationRemover Register(Action action) => _register(action);
}
}
25 changes: 13 additions & 12 deletions Lifetime/Soul/CollapsingSoul.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
using System;

namespace TwistedOak.Util.Soul {
///<summary>Delegates to an underlying soul that is replaced with one of the permanent souls once it is no longer mortal.</summary>
/// <summary>
/// Delegates to an underlying soul that is replaced with one of the permanent
/// souls once it is no longer mortal.
/// </summary>
internal sealed class CollapsingSoul : ISoul {
private bool _collapsed;
private ISoul _subSoul;

public CollapsingSoul(ISoul subSoul) {
this._subSoul = subSoul;
_subSoul = subSoul;

// flatten multiple levels of wrapping
var r = subSoul as CollapsingSoul;
if (r != null) this._subSoul = r._subSoul;

if(subSoul is CollapsingSoul cS) _subSoul = cS._subSoul;

// ensure collapse occurs once the sub soul becomes fixed
Register(() => TryOptimize());
Register(() => tryOptimize());
}

private Phase TryOptimize() {
private Phase tryOptimize() {
var phase = _subSoul.Phase;
if (_collapsed) return phase; // already optimized
if (phase == Phase.Mortal) return phase; // can't optimize yet
Expand All @@ -27,9 +29,8 @@ private Phase TryOptimize() {
_subSoul = Phase.AsPermanentSoul();
return phase;
}
public Phase Phase { get { return TryOptimize(); } }
public RegistrationRemover Register(Action action) {
return _subSoul.Register(action);
}

public Phase Phase => tryOptimize();
public RegistrationRemover Register(Action action) => _subSoul.Register(action);
}
}
2 changes: 1 addition & 1 deletion Lifetime/Soul/DeadSoul.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ internal sealed class DeadSoul : ISoul {
///<summary>The single instance of the permanently dead soul.</summary>
public static readonly ISoul Instance = new DeadSoul();
private DeadSoul() {}
public Phase Phase { get { return Phase.Dead; } }
public Phase Phase => Phase.Dead;
public RegistrationRemover Register(Action action) {
action();
return Soul.EmptyRemover;
Expand Down
Loading