Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Fix for Memory Leak
  • Loading branch information
ChrisPulman committed May 11, 2024
commit 68743c08fce341e8a0d2f3858f5e3eaef9c5e5e0
1 change: 0 additions & 1 deletion samples/Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.10</AvaloniaVersion>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\src\analyzers.ruleset</CodeAnalysisRuleSet>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
Expand Down
94 changes: 49 additions & 45 deletions src/Directory.build.props
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
<Project>
<PropertyGroup>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<Product>ReactiveUI.Validations ($(TargetFramework))</Product>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/reactiveui/ReactiveUI.Validation/</PackageProjectUrl>
<PackageIconUrl>https://github.com/reactiveui/ReactiveUI.Validation/blob/master/media/logo.png?raw=true</PackageIconUrl>
<Authors>.NET Foundation and Contributors</Authors>
<Description>Validations library for ReactiveUI.</Description>
<Owners>xpaulbettsx;ghuntley</Owners>
<PackageTags>reactiveui;validation library;reactive programming;xamarin forms;netcore;portable;xamarin;xamarin ios;xamarin mac;android;monodroid;uwp;net45</PackageTags>
<PackageReleaseNotes>https://github.com/reactiveui/reactiveui.validation/releases</PackageReleaseNotes>
<RepositoryUrl>https://github.com/reactiveui/reactiveui.validation</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsTestProject>$(MSBuildProjectName.Contains('Tests'))</IsTestProject>
<DebugType>Embedded</DebugType>
<!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Optional: Embed source files that are not tracked by the source control manager in the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Optional: Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)analyzers.ruleset</CodeAnalysisRuleSet>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625</WarningsAsErrors>
<LangVersion>preview</LangVersion>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
<Project>
<PropertyGroup>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<Product>ReactiveUI.Validations ($(TargetFramework))</Product>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/reactiveui/ReactiveUI.Validation/</PackageProjectUrl>
<PackageIconUrl>https://github.com/reactiveui/ReactiveUI.Validation/blob/master/media/logo.png?raw=true</PackageIconUrl>
<Authors>.NET Foundation and Contributors</Authors>
<Description>Validations library for ReactiveUI.</Description>
<Owners>xanaisbettsx;ghuntley</Owners>
<PackageTags>reactiveui;validation library;reactive programming;netcore;netstandard;netframework</PackageTags>
<PackageIcon>logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>https://github.com/reactiveui/reactiveui.validation/releases</PackageReleaseNotes>
<RepositoryUrl>https://github.com/reactiveui/reactiveui.validation</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsTestProject>$(MSBuildProjectName.Contains('Tests'))</IsTestProject>
<DebugType>Embedded</DebugType>
<!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Optional: Embed source files that are not tracked by the source control manager in the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Optional: Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625</WarningsAsErrors>
<LangVersion>preview</LangVersion>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
<NoWarn>$(NoWarn);IDE1006</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="LICENSE" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\media\logo.png" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="LICENSE" />
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="4.12.2" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="4.12.2" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
</ItemGroup>

</Project>

</Project>

18 changes: 5 additions & 13 deletions src/ReactiveUI.Validation.Tests/MemoryLeakTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,20 @@ public void Instance_Released_IsGarbageCollected()
new Action(
() =>
{
var sut = new TestClassMemory();
var memTest = new TestClassMemory();

reference = new WeakReference(sut, true);
sut.Dispose();
reference = new WeakReference(memTest, true);
memTest.Dispose();
})();

// Sut should have gone out of scope about now, so the garbage collector can clean it up
// memTest should have gone out of scope about now, so the garbage collector can clean it up
dotMemory.Check(
memory => memory.GetObjects(
where => where.Type.Is<TestClassMemory>()).ObjectsCount.Should().Be(0, "it is out of scope"));

GC.Collect();
GC.WaitForPendingFinalizers();

if (reference.Target is TestClassMemory sut)
{
// ReactiveObject does not inherit from IDisposable, so we need to check ValidationContext
sut.ValidationContext.Should().BeNull("it is garbage collected");
}
else
{
reference.Target.Should().BeNull("it is garbage collected");
}
reference.Target.Should().BeNull("it is garbage collected");
}
}
2 changes: 1 addition & 1 deletion src/ReactiveUI.Validation.Tests/Models/TestClassMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public TestClassMemory()
.DisposeWith(_disposable);

// commenting out the following statement makes the test green
ValidationContext.ValidationStatusChange
ValidationContext?.ValidationStatusChange
.Subscribe(/* you do something here, but this does not matter for now. */)
.DisposeWith(_disposable);
}
Expand Down
5 changes: 3 additions & 2 deletions src/ReactiveUI.Validation.Tests/ValidationContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,15 @@ public void IsValidShouldNotifyOfValidityChange()
viewModel.ValidationContext.Add(nameValidation);

var latestValidity = false;
viewModel.IsValid().Subscribe(isValid => latestValidity = isValid);
var d = viewModel.IsValid().Subscribe(isValid => latestValidity = isValid);
Assert.False(latestValidity);

viewModel.Name = "Jonathan";
Assert.True(latestValidity);

viewModel.Name = string.Empty;
Assert.False(latestValidity);
d.Dispose();
}

/// <summary>
Expand Down Expand Up @@ -220,4 +221,4 @@ public void ShouldClearAttachedValidationRulesForTheGivenProperty()
Assert.NotEmpty(viewModel.ValidationContext.Text);
Assert.Equal(name2ErrorMessage, viewModel.ValidationContext.Text.ToSingleLine());
}
}
}
25 changes: 12 additions & 13 deletions src/ReactiveUI.Validation/Contexts/ValidationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
Expand All @@ -31,15 +30,17 @@ namespace ReactiveUI.Validation.Contexts;
/// </remarks>
public class ValidationContext : ReactiveObject, IValidationContext
{
private static readonly CompositeDisposable _collectionDisposables = [];
private readonly CompositeDisposable _disposables = [];

private readonly ReplaySubject<IValidationState> _validationStatusChange = new(1);
private readonly ReplaySubject<bool> _validSubject = new(1);

private readonly IConnectableObservable<bool> _validationConnectable;
private readonly IObservable<bool> _validationObservable;
private readonly ObservableAsPropertyHelper<IValidationText> _validationText;
private readonly ObservableAsPropertyHelper<bool> _isValid;

private readonly CompositeDisposable _disposables = [];
private SourceList<IValidationComponent> _validationSource = new();
private readonly SourceList<IValidationComponent> _validationSource = new();
private bool _isActive;

/// <summary>
Expand All @@ -52,15 +53,15 @@ public ValidationContext(IScheduler? scheduler = null)
var changeSets = _validationSource.Connect().ObserveOn(scheduler);
Validations = changeSets.AsObservableList();

_validationConnectable = changeSets
_validationObservable = changeSets
.StartWithEmpty()
.AutoRefreshOnObservable(x => x.ValidationStatusChange)
.QueryWhenChanged(static x =>
{
using ReadOnlyCollectionPooled<IValidationComponent> validationComponents = new(x);
validationComponents.DisposeWith(_collectionDisposables);
return validationComponents.Count is 0 || validationComponents.All(v => v.IsValid);
})
.Multicast(_validSubject);
});

_isValid = _validSubject
.StartWith(true)
Expand Down Expand Up @@ -95,7 +96,7 @@ public IObservable<bool> Valid
/// <summary>
/// Gets get the list of validations.
/// </summary>
public IObservableList<IValidationComponent> Validations { get; private set; }
public IObservableList<IValidationComponent> Validations { get; }

/// <inheritdoc/>
public bool IsValid
Expand Down Expand Up @@ -183,11 +184,9 @@ protected virtual void Dispose(bool disposing)
_validationStatusChange.Dispose();
_validSubject.Dispose();
_validationSource.Clear();
Validations.Dispose();
_validationSource.Dispose();

Validations = null!;
_validationSource = null!;
Validations.Dispose();
_collectionDisposables.Dispose();
}
}

Expand All @@ -199,7 +198,7 @@ private void Activate()
}

_isActive = true;
_disposables.Add(_validationConnectable.Connect());
_disposables.Add(_validationObservable.Subscribe(_validSubject));
}

/// <summary>
Expand Down
13 changes: 5 additions & 8 deletions src/ReactiveUI.Validation/Helpers/ReactiveValidationObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ namespace ReactiveUI.Validation.Helpers;
/// </summary>
public abstract class ReactiveValidationObject : ReactiveObject, IValidatableViewModel, INotifyDataErrorInfo, IDisposable
{
private CompositeDisposable _disposables = [];
private IValidationTextFormatter<string> _formatter;
private HashSet<string> _mentionedPropertyNames = [];
private readonly CompositeDisposable _disposables = [];
private readonly IValidationTextFormatter<string> _formatter;
private readonly HashSet<string> _mentionedPropertyNames = [];
private bool _hasErrors;

/// <summary>
Expand Down Expand Up @@ -77,7 +77,7 @@ public bool HasErrors
}

/// <inheritdoc />
public IValidationContext ValidationContext { get; private set; }
public IValidationContext ValidationContext { get; }

/// <summary>
/// Returns a collection of error messages, required by the INotifyDataErrorInfo interface.
Expand Down Expand Up @@ -121,11 +121,8 @@ protected virtual void Dispose(bool disposing)
if (!_disposables.IsDisposed && disposing)
{
_disposables.Dispose();
ValidationContext.Dispose();
_mentionedPropertyNames.Clear();
_formatter = null!;
_mentionedPropertyNames = null!;
_disposables = null!;
ValidationContext = null!;
}
}

Expand Down