Skip to content
Commits on Source (16)
# EditorConfig is awesome: http://EditorConfig.org
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
charset = utf-8
charset=utf-8
end_of_line = lf
indent_brace_style = K&R
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true
curly_bracket_next_line = true
[*.{js,ts}]
quote_type = double
[*.json]
indent_size = 2
tab_width = 2
indent_style = space
[*.yaml]
indent_style = space
insert_final_newline=true
indent_style=space
indent_size=4
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_space_after_cast=false
csharp_style_var_elsewhere=false:hint
csharp_style_var_for_built_in_types=false:hint
csharp_style_var_when_type_is_apparent=true:hint
csharp_preserve_single_line_statements=false
csharp_preserve_single_line_blocks=true
dotnet_style_predefined_type_for_locals_parameters_members=true:hint
dotnet_style_predefined_type_for_member_access=true:hint
dotnet_style_qualification_for_event=true:hint
dotnet_style_qualification_for_field=true:hint
dotnet_style_qualification_for_method=true:hint
dotnet_style_qualification_for_property=true:hint
dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint
# ReSharper properties
resharper_alignment_tab_fill_style=optimal_fill
resharper_apply_on_completion=true
resharper_blank_lines_after_control_transfer_statements=1
resharper_blank_lines_around_single_line_auto_property=1
resharper_blank_lines_around_single_line_property=1
resharper_blank_lines_before_single_line_comment=1
resharper_blank_lines_between_using_groups=1
resharper_braces_for_for=required
resharper_braces_for_foreach=required
resharper_braces_for_ifelse=required
resharper_braces_for_while=required
resharper_can_use_global_alias=false
resharper_csharp_blank_lines_around_single_line_field=1
resharper_csharp_blank_lines_around_single_line_invocable=1
resharper_csharp_indent_style=tab
resharper_csharp_insert_final_newline=true
resharper_csharp_keep_blank_lines_in_code=1
resharper_csharp_keep_blank_lines_in_declarations=1
resharper_csharp_new_line_before_while=true
resharper_csharp_use_indent_from_vs=false
resharper_csharp_wrap_arguments_style=chop_if_long
resharper_csharp_wrap_extends_list_style=chop_if_long
resharper_csharp_wrap_parameters_style=chop_if_long
resharper_css_insert_final_newline=false
resharper_enforce_line_ending_style=true
resharper_html_insert_final_newline=false
resharper_indent_nested_fixed_stmt=true
resharper_js_indent_style=tab
resharper_js_insert_final_newline=true
resharper_js_keep_blank_lines_in_code=1
resharper_js_stick_comment=false
resharper_js_use_indent_from_vs=false
resharper_js_wrap_before_binary_opsign=true
resharper_js_wrap_chained_method_calls=chop_if_long
resharper_keep_blank_lines_between_declarations=1
resharper_min_blank_lines_after_imports=1
resharper_place_attribute_on_same_line=False
resharper_place_constructor_initializer_on_same_line=false
resharper_place_type_constraints_on_same_line=false
resharper_protobuf_insert_final_newline=false
resharper_qualified_using_at_nested_scope=true
resharper_resx_insert_final_newline=false
resharper_space_within_single_line_array_initializer_braces=true
resharper_use_indents_from_main_language_in_file=false
resharper_vb_insert_final_newline=false
resharper_wrap_after_declaration_lpar=true
resharper_wrap_after_invocation_lpar=true
resharper_wrap_before_extends_colon=true
resharper_wrap_before_first_type_parameter_constraint=true
resharper_wrap_before_type_parameter_langle=true
resharper_xmldoc_indent_child_elements=ZeroIndent
resharper_xmldoc_indent_text=ZeroIndent
resharper_xmldoc_insert_final_newline=false
resharper_xml_insert_final_newline=false
# ReSharper inspection severities
resharper_check_namespace_highlighting=none
resharper_convert_to_auto_property_highlighting=none
resharper_localizable_element_highlighting=none
resharper_redundant_comma_in_attribute_list_highlighting=none
resharper_redundant_comma_in_enum_declaration_highlighting=none
resharper_redundant_comma_in_initializer_highlighting=none
resharper_string_compare_to_is_culture_specific_highlighting=none
resharper_string_index_of_is_culture_specific_1_highlighting=none
resharper_use_null_propagation_highlighting=none
resharper_use_object_or_collection_initializer_highlighting=hint
resharper_use_string_interpolation_highlighting=hint
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style=space
indent_size=2
tab_width=2
[*.{cs,js,json,jsx,proto,resjson,ts,tsx}]
indent_style=space
indent_size=space
tab_width=4
[*.{asax,ascx,aspx,cshtml,css,htm,html,master,razor,skin,vb,xaml,xamlx,xoml}]
indent_style=space
indent_size=4
tab_width=4
[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
indent_style=space
indent_size=2
tab_width=2
[*.proto]
indent_style=space
indent_size=2
tab_width=2
use asdf
......@@ -2,8 +2,7 @@ launchSettings.json
*~
*.user
NaNoGenMo.*
Directory.Build.props
obj/
[Bb]in/
......@@ -11,5 +10,4 @@ obj/
.vscode/
.idea/
_ReSharper.Caches/
.author-intrusion
node_modules/
stages:
- test
- tag
- deploy
default:
before_script:
- curl -sL https://deb.nodesource.com/setup_15.x | bash -
- apt-get install -y nodejs
# The test stage runs on every build and every branch.
test:
image: mcr.microsoft.com/dotnet/sdk:5.0
stage: test
script:
# Make sure the commits follow our rules.
- npx yarn install --frozen-lockfile
- if [ $CI_BUILD_BEFORE_SHA == "0000000000000000000000000000000000000000" ]; then npx commitlint --to=HEAD; else npx commitlint --from=$CI_BUILD_BEFORE_SHA; fi
# Build and test the project.
- dotnet restore
- dotnet build
- 'dotnet test --test-adapter-path:. --logger:"junit;LogFilePath=../artifacts/{assembly}-test-result.xml;MethodFormat=Default;FailureBodyFormat=Verbose" --collect:"XPlat Code Coverage"'
# Summarize the output for Gitlab CI reporting.
- dotnet new tool-manifest
- dotnet tool install dotnet-reportgenerator-globaltool
- dotnet tool run reportgenerator -reports:src/*/TestResults/*/coverage.cobertura.xml -targetdir:./coverage "-reporttypes:Cobertura;TextSummary"
- grep "Line coverage" coverage/Summary.txt
rules:
- if: '$CI_COMMIT_TITLE =~ /^(docs|chore\(release\))/'
when: never
- if: '$CI_COMMIT_TAG'
when: never
- when: on_success
artifacts:
when: always
paths:
- ./**/*test-result.xml
- ./coverage/Cobertura.xml
- ./coverage/Summary.*
reports:
junit:
- ./**/*test-result.xml
cobertura:
- ./coverage/Cobertura.xml
# Tagging only runs on the master branches and after testing.
tag:
image: mcr.microsoft.com/dotnet/sdk:5.0
stage: tag
needs: [test]
script:
- npx yarn install --frozen-lockfile
- npx semantic-release
rules:
- if: '$CI_COMMIT_TITLE =~ /^(docs|chore\(release\))/'
when: never
- if: '$CI_COMMIT_TAG'
when: never
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
when: never
- when: on_success
# Publish the packages.
deploy:
image: mcr.microsoft.com/dotnet/sdk:5.0
needs: []
stage: deploy
before_script: []
script:
- scripts/set-build-version.sh
- dotnet restore
- dotnet build
- dotnet pack -c Release
- dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
- dotnet nuget push "bin/*.nupkg" --source gitlab
rules:
- if: '$FORCE_DEPLOY'
- if: '$CI_COMMIT_TITLE =~ /^chore\(release\)/ && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
dotnet-core 5.0.100
yarn 1.22.10
nodejs 15.0.1
This diff is collapsed.
{
"name": "mfgames-locking-cil",
"version": "1.1.0",
"private": true,
"scripts": {
"commitlint": "commitlint "
},
"release": {
"branch": "master",
"message": "chore(release): v${nextRelease.version}\n\n${nextRelease.notes}",
"verifyConditions": [
"@semantic-release/git"
],
"analyzeCommits": [
"@semantic-release/commit-analyzer"
],
"prepare": [
"@semantic-release/npm",
"@semantic-release/git"
],
"publish": [],
"success": [],
"fail": []
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@semantic-release/git": "^9.0.0",
"husky": "^4.3.6",
"semantic-release": "^17.3.0"
}
}
#!/bin/bash
# This sets the build version using a `Directory.Build.props` at the top-level
# of the project.
# Move into the root directory.
cd $(dirname $0)
cd ..
# Get the version from `package.json`.
VERSION=$(grep '"version":' package.json | cut -f 4 -d '"')
cat > src/Directory.Build.props << EOL
<Project>
<PropertyGroup>
<Version>$VERSION</Version>
</PropertyGroup>
</Project>
EOL
namespace MfGames.Locking.Tests
{
/// <summary>
/// The various states for testing lock logic.
/// </summary>
internal enum LockAction
{
BeforeLock,
InLock,
AfterLock,
}
}
// <copyright file="LockTests.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using KellermanSoftware.CompareNetObjects;
using Xunit;
using Xunit.Sdk;
namespace MfGames.Locking.Tests
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class LockTests
{
public static readonly TimeSpan ProcessTime = TimeSpan.FromMilliseconds(50);
private readonly List<int> events;
private readonly ReaderWriterLockSlim locker;
private readonly ITestOutputHelper output;
private readonly List<(int, LockAction)> sequenceRecord;
private readonly DateTime start;
private readonly object syncLock;
public LockTests(ITestOutputHelper output)
public LockTests()
{
this.output = output;
this.start = DateTime.UtcNow;
this.syncLock = new object();
this.sequenceRecord = new List<(int, LockAction)>();
this.locker = new ReaderWriterLockSlim();
this.events = new List<int>();
}
private TimeSpan Elapsed => DateTime.UtcNow - this.start;
[Fact]
public void BasicDataGatheringWorks()
{
this.Report(ProcessTime, 1);
this.Verify();
}
[Fact]
public void ReadBlocksWrite()
{
Task.WaitAll(
Task.Run(() => this.ReportInReadLock(ProcessTime * 0, ProcessTime * 3, 1)),
Task.Run(() => this.ReportInWriteLock(ProcessTime * 1, ProcessTime * 1, 2)));
this.Verify();
Task.Run(() => this.TestRead(1, 5, 1)),
Task.Run(() => this.TestWrite(2, 2, 2)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock));
}
[Fact]
public void ReadsDoNotBlockReads()
{
Task.WaitAll(
Task.Run(() => this.ReportInReadLock(ProcessTime, ProcessTime * 5, 3)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime, 1)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime * 2, 2)));
this.Verify();
}
[Fact]
public void ThreadedDataGatheringWorks()
{
Task.WaitAll(
Task.Run(() => this.ReportInReadLock(ProcessTime, ProcessTime, 1)));
this.Verify();
Task.Run(() => this.TestRead(1, 8, 1)),
Task.Run(() => this.TestRead(2, 1, 2)),
Task.Run(() => this.TestRead(4, 1, 3)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.BeforeLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock),
(1, LockAction.AfterLock));
}
[Fact]
public void UpgradableBlocksUpgradable()
{
Task.WaitAll(
Task.Run(() => this.ReportInUpgradableLock(ProcessTime, ProcessTime * 5, 1)),
Task.Run(() => this.ReportInUpgradableLock(ProcessTime * 3, ProcessTime, 2)),
Task.Run(() => this.ReportInUpgradableLock(ProcessTime * 2, ProcessTime * 2, 3)));
this.Verify();
Task.Run(() => this.TestUpgradable(1, 10, 1)),
Task.Run(() => this.TestUpgradable(2, 2, 2)),
Task.Run(() => this.TestUpgradable(5, 1, 3)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(3, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock));
}
[Fact]
public void UpgradableDoesNotBlockReads()
{
Task.WaitAll(
Task.Run(() => this.ReportInUpgradableLock(ProcessTime, ProcessTime * 5, 3)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime, 1)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime * 2, 2)));
this.Verify();
Task.Run(() => this.TestUpgradable(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2)),
Task.Run(() => this.TestRead(4, 1, 3)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock),
(3, LockAction.BeforeLock),
(3, LockAction.InLock),
(3, LockAction.AfterLock),
(1, LockAction.AfterLock));
}
[Fact]
public void WriteBlockRead()
{
Task.WaitAll(
Task.Run(() => this.ReportInWriteLock(ProcessTime, ProcessTime * 3, 1)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime, 2)));
this.Verify();
Task.Run(() => this.TestWrite(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock));
}
[Fact]
public void WriteBlockReads()
{
Task.WaitAll(
Task.Run(() => this.ReportInWriteLock(ProcessTime, ProcessTime * 5, 1)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime, 2)),
Task.Run(() => this.ReportInReadLock(ProcessTime * 2, ProcessTime * 2, 3)));
this.Verify();
Task.Run(() => this.TestWrite(1, 6, 1)),
Task.Run(() => this.TestRead(2, 1, 2)));
this.VerifySequenceRecord(
(1, LockAction.BeforeLock),
(1, LockAction.InLock),
(2, LockAction.BeforeLock),
(1, LockAction.AfterLock),
(2, LockAction.InLock),
(2, LockAction.AfterLock));
}
private void AddEvent(int sequence)
private void RecordSequenceAction(int sequence, LockAction action)
{
// This is a monitor lock on the slim, just to give us a second lock.
lock (this.locker)
lock (this.syncLock)
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => adding sequence");
this.events.Add(sequence);
this.sequenceRecord.Add((sequence, action));
}
}
/// <summary>
/// Waits a short period of time and then injects the sequence into the event list.
/// </summary>
private void Report(
TimeSpan processTime,
private void Test(
Func<ReaderWriterLockSlim, IDisposable> getLock,
int settleTocks,
int waitTocks,
int sequence)
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => starting process");
Thread.Sleep(processTime);
this.output.WriteLine(this.Elapsed + ": " + sequence + " => finished process");
this.AddEvent(sequence);
}
private void ReportInReadLock(
TimeSpan settleTime,
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => entering read lock: " + sequence);
const int TocksInMilliseconds = 200;
Thread.Sleep(settleTime);
Thread.Sleep(TocksInMilliseconds * settleTocks);
this.output.WriteLine(this.Elapsed + ": " + sequence + " => locking read lock: " + sequence);
this.RecordSequenceAction(sequence, LockAction.BeforeLock);
using (new ReadLock(this.locker))
using (getLock(this.locker))
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => starting read lock: " + sequence);
Thread.Sleep(100);
this.RecordSequenceAction(sequence, LockAction.InLock);
Thread.Sleep(TocksInMilliseconds * waitTocks);
this.Report(processTime, sequence);
this.RecordSequenceAction(sequence, LockAction.AfterLock);
}
}
private void ReportInUpgradableLock(
TimeSpan settleTime,
TimeSpan processTime,
private void TestRead(
int settleTocks,
int waitTocks,
int sequence)
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => entering upgradable lock: " + sequence);
Thread.Sleep(settleTime);
this.output.WriteLine(this.Elapsed + ": " + sequence + " => locking upgradable lock: " + sequence);
using (new UpgradableLock(this.locker))
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => starting upgradable lock: " + sequence);
this.Report(processTime, sequence);
}
this.Test(
x => new ReadLock(x),
settleTocks,
waitTocks,
sequence);
}
private void ReportInWriteLock(
TimeSpan settleTime,
TimeSpan processTime,
private void TestUpgradable(
int settleTocks,
int waitTocks,
int sequence)
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => entering write lock: " + sequence);
Thread.Sleep(settleTime);
this.output.WriteLine(this.Elapsed + ": " + sequence + " => locking write lock: " + sequence);
using (new WriteLock(this.locker))
{
this.output.WriteLine(this.Elapsed + ": " + sequence + " => starting write lock: " + sequence);
this.Report(processTime, sequence);
}
this.Test(
x => new UpgradableLock(x),
settleTocks,
waitTocks,
sequence);
}
/// <summary>
/// Verifies the sequence of events.
/// </summary>
private void Verify()
private void TestWrite(
int settleTocks,
int waitTocks,
int sequence)
{
this.output.WriteLine("Final Sequence: " + string.Join(", ", this.events));
this.Test(
x => new WriteLock(x),
settleTocks,
waitTocks,
sequence);
}
for (int i = 1; i <= this.events.Count; i++)
private void VerifySequenceRecord(
params (int, LockAction BeforeLock)[] values)
{
lock (this.syncLock)
{
Assert.Equal(i, this.events[i - 1]);
var compareLogic = new CompareLogic
{
Config =
{
IgnoreObjectTypes = true,
MaxDifferences = 2,
}
};
ComparisonResult result = compareLogic.Compare(
values,
this.sequenceRecord);
if (!result.AreEqual)
{
throw new XunitException(result.DifferencesString);
}
}
}
}
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>net5</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CompareNETObjects" Version="4.70.0" />
<PackageReference Include="coverlet.collector" Version="3.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JunitXml.TestLogger" Version="2.1.81" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="Roslynator.Analyzers" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFrameworks>netstandard2.0;net45;net5</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<Authors>Dylan Moonfire</Authors>
<Company>Moonfire Games</Company>
<RepositoryUrl>https://gitlab.com/dmoonfire/mfgames-locking-cil</RepositoryUrl>
<RepositoryUrl>https://gitlab.com/mfgames/mfgames-locking-cil</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<PackageTags>locking</PackageTags>
<PackageProjectUrl>https://gitlab.com/dmoonfire/mfgames-locking-cil</PackageProjectUrl>
<PackageLicenseUrl>https://gitlab.com/dmoonfire/mfgames-locking-cil/blob/master/LICENSE.txt</PackageLicenseUrl>
<Description>An IDisposable pattern for using ReaderWriterLockSlim.</Description>
<PackageProjectUrl>https://gitlab.com/mfgames/mfgames-locking-cil</PackageProjectUrl>
<PackageLicense>MIT</PackageLicense>
<Description>Wrappers and patterns for working with ReaderWriterLockSlim.</Description>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\..\bin</OutputPath>
......@@ -28,10 +28,14 @@
<None Remove="Serialization\**" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="../stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007" PrivateAssets="All" />
<PackageReference Include="Roslynator.Analyzers" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
// <copyright file="NestableReadLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Defines a ReaderWriterLockSlim read-only lock.
/// </summary>
......
// <copyright file="NestableUpgradableReadLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Defines a ReaderWriterLockSlim upgradable read lock.
/// </summary>
......@@ -17,10 +13,12 @@ namespace MfGames.Locking
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableUpgradableReadLock" /> class.
/// Initializes a new instance of the <see cref="NestableUpgradableReadLock" />
/// class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableUpgradableReadLock(ReaderWriterLockSlim readerWriterLockSlim)
public NestableUpgradableReadLock(
ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
......
// <copyright file="NestableWriteLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Defines a ReaderWriterLockSlim write lock.
/// </summary>
......
// <copyright file="ReadLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <inheritdoc />
/// <summary>
/// Defines a ReaderWriterLockSlim read-only lock.
......
// <copyright file="TryGetCreate.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Implements the basic pattern for getting an item from a cache using
/// the ReaderWriterLockSlim class. This attempts to get it using a read-only
/// lock. If that fails, it gets an upgradable lock, tries again, and if it
/// still can't find it, upgrades the lock to a write lock to create it.
/// </summary>
public static class TryGetCreate
{
/// <summary>
/// Invokes the try/get/create pattern used a condition to test for it
/// and a constructor function.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
/// <param name="conditionHandler">The condition handler.</param>
/// <param name="createHandler">The create handler.</param>
public static void Invoke(
ReaderWriterLockSlim readerWriterLockSlim,
Func<bool> conditionHandler,
Action createHandler)
{
using (new ReadLock(readerWriterLockSlim))
{
// Verify that the condition for creating it is false.
if (!conditionHandler())
{
return;
}
}
// We failed to get the lock using the read-only. We create an upgradable lock
// and try again since it may have been created with a race condition when the
// last lock was released and this one was acquired.
using (new UpgradableLock(readerWriterLockSlim))
{
// Verify that the condition for creating it is false.
if (!conditionHandler())
{
return;
}
// We failed to get it in the lock. Upgrade the lock to a write and create it.
using (new WriteLock(readerWriterLockSlim))
{
createHandler();
}
}
}
/// <summary>
/// Invokes the try/get/create pattern using a tryget retrieval and a
/// creator handler.
/// </summary>
/// <typeparam name="TInput">The type of the input.</typeparam>
/// <typeparam name="TOutput">The type of the output.</typeparam>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
/// <param name="input">The input.</param>
/// <param name="tryGetHandler">The try get handler.</param>
/// <param name="createHandler">The create handler.</param>
/// <returns>The requested output object.</returns>
public static TOutput Invoke<TInput, TOutput>(
ReaderWriterLockSlim readerWriterLockSlim,
TInput input,
TryGetHandler<TInput, TOutput> tryGetHandler,
Func<TInput, TOutput> createHandler)
{
// First attempt to get the item using a read-only lock.
TOutput output;
using (new ReadLock(readerWriterLockSlim))
{
// Try to get the item using the try/get handler.
if (tryGetHandler(input, out output))
{
// We successful got the item in the read-only cache, so just return it.
return output;
}
}
// We failed to get the lock using the read-only. We create an upgradable lock
// and try again since it may have been created with a race condition when the
// last lock was released and this one was acquired.
using (new UpgradableLock(readerWriterLockSlim))
{
// Try to get the item using the try/get handler.
if (tryGetHandler(input, out output))
{
// We successful got the item in this lock, so return it without
// upgrading the lock.
return output;
}
// We failed to get it in the lock. Upgrade the lock to a write and create it.
using (new WriteLock(readerWriterLockSlim))
{
return createHandler(input);
}
}
}
}
}
// <copyright file="TryGetHandler.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
namespace MfGames.Locking
{
/// <summary>
/// Defines the common try/get handler to retrieve an item of a given type.
/// </summary>
/// <typeparam name="TInput">The type of the input or lookup key.</typeparam>
/// <typeparam name="TOutput">The type of the output.</typeparam>
/// <param name="input">The input value to search for.</param>
/// <param name="output">The resulting output value.</param>
/// <returns>True if the attempt was successful, otherwise false.</returns>
public delegate bool TryGetHandler<in TInput, TOutput>(
TInput input,
out TOutput output);
}
// <copyright file="UpgradableLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Defines a ReaderWriterLockSlim read-only lock.
/// </summary>
......@@ -25,7 +21,8 @@ namespace MfGames.Locking
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
......
// <copyright file="WriteLock.cs" company="Moonfire Games">
// Copyright (c) Moonfire Games. MIT License.
// </copyright>
using System;
using System.Threading;
namespace MfGames.Locking
{
using System;
using System.Threading;
/// <summary>
/// Defines a ReaderWriterLockSlim read-only lock.
/// </summary>
......@@ -25,7 +21,8 @@ namespace MfGames.Locking
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
......