Skip to content

Commit eb7864c

Browse files
authored
lifetime scope for cached IDisposable (#19)
* lifetime scope * scenario test * formatting * fix formatting * scenario test * remove ugly extension methods * formatting * typo * editorconfig, arg validation * seal, fix and run bench
1 parent 841d7ba commit eb7864c

File tree

15 files changed

+659
-317
lines changed

15 files changed

+659
-317
lines changed

.editorconfig

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
2+
###############################
3+
# Core EditorConfig Options #
4+
###############################
5+
# All files
6+
[*]
7+
indent_style = space
8+
# Code files
9+
[*.{cs,csx,vb,vbx}]
10+
indent_size = 4
11+
insert_final_newline = true
12+
charset = utf-8-bom
13+
###############################
14+
# .NET Coding Conventions #
15+
###############################
16+
[*.{cs,vb}]
17+
# Organize usings
18+
dotnet_sort_system_directives_first = true
19+
# this. preferences
20+
dotnet_style_qualification_for_field = false:silent
21+
dotnet_style_qualification_for_property = false:silent
22+
dotnet_style_qualification_for_method = false:silent
23+
dotnet_style_qualification_for_event = false:silent
24+
# Language keywords vs BCL types preferences
25+
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
26+
dotnet_style_predefined_type_for_member_access = true:silent
27+
# Parentheses preferences
28+
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
29+
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
30+
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
31+
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
32+
# Modifier preferences
33+
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
34+
dotnet_style_readonly_field = true:suggestion
35+
# Expression-level preferences
36+
dotnet_style_object_initializer = true:suggestion
37+
dotnet_style_collection_initializer = true:suggestion
38+
dotnet_style_explicit_tuple_names = true:suggestion
39+
dotnet_style_null_propagation = true:suggestion
40+
dotnet_style_coalesce_expression = true:suggestion
41+
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
42+
dotnet_style_prefer_inferred_tuple_names = true:suggestion
43+
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
44+
dotnet_style_prefer_auto_properties = true:silent
45+
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
46+
dotnet_style_prefer_conditional_expression_over_return = true:silent
47+
###############################
48+
# Naming Conventions #
49+
###############################
50+
# Style Definitions
51+
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
52+
# Use PascalCase for constant fields
53+
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
54+
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
55+
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
56+
dotnet_naming_symbols.constant_fields.applicable_kinds = field
57+
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
58+
dotnet_naming_symbols.constant_fields.required_modifiers = const
59+
###############################
60+
# C# Coding Conventions #
61+
###############################
62+
[*.cs]
63+
# var preferences
64+
csharp_style_var_for_built_in_types = true:silent
65+
csharp_style_var_when_type_is_apparent = true:silent
66+
csharp_style_var_elsewhere = true:silent
67+
# Expression-bodied members
68+
csharp_style_expression_bodied_methods = false:silent
69+
csharp_style_expression_bodied_constructors = false:silent
70+
csharp_style_expression_bodied_operators = false:silent
71+
csharp_style_expression_bodied_properties = true:silent
72+
csharp_style_expression_bodied_indexers = true:silent
73+
csharp_style_expression_bodied_accessors = true:silent
74+
# Pattern matching preferences
75+
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
76+
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
77+
# Null-checking preferences
78+
csharp_style_throw_expression = true:suggestion
79+
csharp_style_conditional_delegate_call = true:suggestion
80+
# Modifier preferences
81+
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
82+
# Expression-level preferences
83+
csharp_prefer_braces = true:silent
84+
csharp_style_deconstructed_variable_declaration = true:suggestion
85+
csharp_prefer_simple_default_expression = true:suggestion
86+
csharp_style_pattern_local_over_anonymous_function = true:suggestion
87+
csharp_style_inlined_variable_declaration = true:suggestion
88+
###############################
89+
# C# Formatting Rules #
90+
###############################
91+
# New line preferences
92+
csharp_new_line_before_open_brace = all
93+
csharp_new_line_before_else = true
94+
csharp_new_line_before_catch = true
95+
csharp_new_line_before_finally = true
96+
csharp_new_line_before_members_in_object_initializers = true
97+
csharp_new_line_before_members_in_anonymous_types = true
98+
csharp_new_line_between_query_expression_clauses = true
99+
# Indentation preferences
100+
csharp_indent_case_contents = true
101+
csharp_indent_switch_labels = true
102+
csharp_indent_labels = flush_left
103+
# Space preferences
104+
csharp_space_after_cast = false
105+
csharp_space_after_keywords_in_control_flow_statements = true
106+
csharp_space_between_method_call_parameter_list_parentheses = false
107+
csharp_space_between_method_declaration_parameter_list_parentheses = false
108+
csharp_space_between_parentheses = false
109+
csharp_space_before_colon_in_inheritance_clause = true
110+
csharp_space_after_colon_in_inheritance_clause = true
111+
csharp_space_around_binary_operators = before_and_after
112+
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
113+
csharp_space_between_method_call_name_and_opening_parenthesis = false
114+
csharp_space_between_method_call_empty_parameter_list_parentheses = false
115+
# Wrapping preferences
116+
csharp_preserve_single_line_statements = true
117+
csharp_preserve_single_line_blocks = true
118+
###############################
119+
# VB Coding Conventions #
120+
###############################
121+
[*.vb]
122+
# Modifier preferences
123+
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion

Directory.Build.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33
<PropertyGroup>
4-
<AssemblyOriginatorKeyFile>..\Lightweight.snk</AssemblyOriginatorKeyFile>
4+
<!-- Work around generated csproj can't find the signing key. -->
5+
<!-- https://github.com/dotnet/BenchmarkDotNet/issues/836 -->
6+
<AssemblyOriginatorKeyFile Condition="EXISTS('..\Lightweight.snk')">..\Lightweight.snk</AssemblyOriginatorKeyFile>
57
<SignAssembly>true</SignAssembly>
68
</PropertyGroup>
79
</Project>

Lightweight.Caching.Benchmarks/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Program
1515
static void Main(string[] args)
1616
{
1717
var summary = BenchmarkRunner
18-
.Run<LruGetOrAddTest>(ManualConfig.Create(DefaultConfig.Instance)
18+
.Run<LruCycle>(ManualConfig.Create(DefaultConfig.Instance)
1919
.AddJob(Job.RyuJitX64));
2020
}
2121
}

Lightweight.Caching.UnitTests/ClassicLruTests.cs renamed to Lightweight.Caching.UnitTests/Lru/ClassicLruTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
using FluentAssertions;
2-
using Lightweight.Caching.UnitTests.Lru;
2+
using Lightweight.Caching.Lru;
33
using System;
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Text;
77
using System.Threading.Tasks;
88
using Xunit;
99

10-
namespace Lightweight.Caching.UnitTests
10+
namespace Lightweight.Caching.UnitTests.Lru
1111
{
1212
public class ClassicLruTests
1313
{

Lightweight.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,31 @@ public ConcurrentLruTests(ITestOutputHelper testOutputHelper)
2525
this.testOutputHelper = testOutputHelper;
2626
}
2727

28-
[Fact]
28+
[Fact]
29+
public void WhenConcurrencyIsLessThan1CtorThrows()
30+
{
31+
Action constructor = () => { var x = new ConcurrentLru<int, string>(0, 3, EqualityComparer<int>.Default); };
32+
33+
constructor.Should().Throw<ArgumentOutOfRangeException>();
34+
}
35+
36+
[Fact]
37+
public void WhenCapacityIsLessThan3CtorThrows()
38+
{
39+
Action constructor = () => { var x = new ConcurrentLru<int, string>(1, 2, EqualityComparer<int>.Default); };
40+
41+
constructor.Should().Throw<ArgumentOutOfRangeException>();
42+
}
43+
44+
[Fact]
45+
public void WhenComparerIsNullCtorThrows()
46+
{
47+
Action constructor = () => { var x = new ConcurrentLru<int, string>(1, 3, null); };
48+
49+
constructor.Should().Throw<ArgumentNullException>();
50+
}
51+
52+
[Fact]
2953
public void WhenItemIsAddedCountIsCorrect()
3054
{
3155
lru.Count.Should().Be(0);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using FluentAssertions;
2+
using Lightweight.Caching.Lru;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Text;
6+
using Xunit;
7+
8+
namespace Lightweight.Caching.UnitTests
9+
{
10+
public class ScopedTests
11+
{
12+
[Fact]
13+
public void WhenScopeIsCreatedThenScopeDisposedLifetimeDisposesValue()
14+
{
15+
var disposable = new Disposable();
16+
var scope = new Scoped<Disposable>(disposable);
17+
var lifetime = scope.CreateLifetime();
18+
19+
scope.Dispose();
20+
scope.Dispose(); // validate double dispose is still single ref count
21+
disposable.IsDisposed.Should().BeFalse();
22+
23+
lifetime.Dispose();
24+
disposable.IsDisposed.Should().BeTrue();
25+
}
26+
27+
[Fact]
28+
public void WhenScopeIsCreatedThenLifetimeDisposedScopeDisposesValue()
29+
{
30+
var disposable = new Disposable();
31+
var scope = new Scoped<Disposable>(disposable);
32+
var lifetime = scope.CreateLifetime();
33+
34+
lifetime.Dispose();
35+
lifetime.Dispose(); // validate double dispose is still single ref count
36+
37+
disposable.IsDisposed.Should().BeFalse();
38+
39+
scope.Dispose();
40+
disposable.IsDisposed.Should().BeTrue();
41+
}
42+
43+
[Fact]
44+
public void WhenScopeIsDisposedCreateScopeThrows()
45+
{
46+
var disposable = new Disposable();
47+
var scope = new Scoped<Disposable>(disposable);
48+
scope.Dispose();
49+
50+
scope.Invoking(s => s.CreateLifetime()).Should().Throw<ObjectDisposedException>();
51+
}
52+
53+
[Fact]
54+
public void WhenScopedIsCreatedFromCacheItemHasExpectedLifetime()
55+
{
56+
var lru = new ConcurrentLru<int, Scoped<Disposable>>(2, 9, EqualityComparer<int>.Default);
57+
var valueFactory = new DisposableValueFactory();
58+
59+
using (var lifetime = lru.GetOrAdd(1, valueFactory.Create).CreateLifetime())
60+
{
61+
lifetime.Value.IsDisposed.Should().BeFalse();
62+
}
63+
64+
valueFactory.Disposable.IsDisposed.Should().BeFalse();
65+
66+
lru.TryRemove(1);
67+
68+
valueFactory.Disposable.IsDisposed.Should().BeTrue();
69+
}
70+
71+
private class DisposableValueFactory
72+
{
73+
public Disposable Disposable { get; } = new Disposable();
74+
75+
public Scoped<Disposable> Create(int key)
76+
{
77+
return new Scoped<Disposable>(this.Disposable);
78+
}
79+
}
80+
81+
private class Disposable : IDisposable
82+
{
83+
public bool IsDisposed { get; set; }
84+
85+
public void Dispose()
86+
{
87+
this.IsDisposed.Should().BeFalse();
88+
IsDisposed = true;
89+
}
90+
}
91+
}
92+
}

Lightweight.Caching/ClassicLru.cs renamed to Lightweight.Caching/Lru/ClassicLru.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Text;
66
using System.Threading.Tasks;
77

8-
namespace Lightweight.Caching
8+
namespace Lightweight.Caching.Lru
99
{
1010
/// <summary>
1111
/// LRU implementation where Lookup operations are backed by a ConcurrentDictionary and the LRU list is protected

Lightweight.Caching/Lru/ConcurrentTLru.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Lightweight.Caching.Lru
88
{
9-
public class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, HitCounter>
9+
public sealed class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, HitCounter>
1010
{
1111
public ConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
1212
: base(concurrencyLevel, capacity, comparer, new TLruPolicy<K, V>(timeToLive), new HitCounter())

Lightweight.Caching/Lru/FastConcurrentTLru.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Lightweight.Caching.Lru
66
{
7-
public class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, NullHitCounter>
7+
public sealed class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, NullHitCounter>
88
{
99
public FastConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
1010
: base(concurrencyLevel, capacity, comparer, new TLruPolicy<K, V>(timeToLive), new NullHitCounter())

0 commit comments

Comments
 (0)