From 0b8b836c10c1a099b6b66624cad76ec1736da4df Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 21:58:00 -0800 Subject: [PATCH 01/11] Refactor --- .vscode/settings.json | 3 + .vscode/tasks.json | 25 + src/.editorconfig | 719 ++++++++++++++++++ src/Directory.Build.props | 26 +- src/Directory.Packages.props | 43 ++ src/Interprocess.Benchmark/.editorconfig | 9 + .../Interprocess.Benchmark.csproj | 2 +- src/Interprocess.Benchmark/Program.cs | 14 +- .../Queue/EnqueueBenchmark.cs | 77 +- .../Queue/QueueBenchmark.cs | 91 ++- .../Queue/QueueExtendedBenchmark.cs | 97 ++- src/Interprocess.Tests/.editorconfig | 10 + src/Interprocess.Tests/CircularBufferTests.cs | 252 +++--- .../Interprocess.Tests.csproj | 12 +- .../Properties/AssemblyInfo.cs | 2 +- src/Interprocess.Tests/QueueTests.cs | 499 ++++++------ src/Interprocess.Tests/SemaphoreTests.cs | 181 +++-- src/Interprocess.Tests/Utils/FactAttribute.cs | 59 +- src/Interprocess.Tests/Utils/Platform.cs | 21 +- .../Utils/RepeatAttribute.cs | 29 +- .../Utils/TestBeforeAfterAttribute.cs | 22 +- .../Utils/UniquePathFixture.cs | 50 +- src/Interprocess.Tests/Utils/XunitLogger.cs | 90 +-- .../Utils/XunitLoggerProvider.cs | 24 +- src/Interprocess/Contracts/IPublisher.cs | 15 +- src/Interprocess/Contracts/IQueueFactory.cs | 17 +- src/Interprocess/Contracts/ISubscriber.cs | 132 ++-- src/Interprocess/Contracts/MessageHeader.cs | 37 +- src/Interprocess/Contracts/QueueHeader.cs | 55 +- src/Interprocess/Contracts/QueueOptions.cs | 98 +-- src/Interprocess/DependencyInjection.cs | 22 - src/Interprocess/Log.cs | 13 + src/Interprocess/Memory/CircularBuffer.cs | 162 ++-- src/Interprocess/Memory/IMemoryFile.cs | 14 +- src/Interprocess/Memory/MemoryFileUnix.cs | 200 ++--- src/Interprocess/Memory/MemoryFileWindows.cs | 42 +- src/Interprocess/Memory/MemoryView.cs | 76 +- src/Interprocess/Queue/Publisher.cs | 187 +++-- src/Interprocess/Queue/Queue.cs | 142 ++-- src/Interprocess/Queue/QueueFactory.cs | 48 +- src/Interprocess/Queue/Subscriber.cs | 263 ++++--- .../IInterprocessSemaphoreReleaser.cs | 11 +- .../Semaphore/IInterprocessSemaphoreWaiter.cs | 11 +- .../Semaphore/InterprocessSemaphore.cs | 71 +- src/Interprocess/Semaphore/Linux/Interop.cs | 254 +++---- .../Semaphore/Linux/SemaphoreLinux.cs | 71 +- src/Interprocess/Semaphore/MacOS/Interop.cs | 271 +++---- .../Semaphore/MacOS/SemaphoreMacOS.cs | 71 +- .../Semaphore/Posix/PosixFilePermissions.cs | 57 +- .../Posix/PosixSemaphoreException.cs | 61 +- .../Semaphore/Posix/PosixTimespec.cs | 36 +- .../Semaphore/Windows/SemaphoreWindows.cs | 31 +- .../ServiceCollectionExtensions.cs | 24 + src/Interprocess/Util.cs | 58 +- src/Sample/Publisher/Program.cs | 55 +- src/Sample/Publisher/Publisher.csproj | 2 +- src/Sample/Subscriber/Program.cs | 48 +- 57 files changed, 2866 insertions(+), 2146 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 src/.editorconfig create mode 100644 src/Directory.Packages.props create mode 100644 src/Interprocess.Benchmark/.editorconfig create mode 100644 src/Interprocess.Tests/.editorconfig delete mode 100644 src/Interprocess/DependencyInjection.cs create mode 100644 src/Interprocess/Log.cs create mode 100644 src/Interprocess/ServiceCollectionExtensions.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c44c10..17110cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,9 +4,12 @@ "Diagnoser", "Interprocess", "Kaby", + "Onnx", "Pedram", "Posix", "Rezaei", + "Roslynator", + "Timespec", "Xeon", "Xunit" ] diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..288fbb3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "${workspaceFolder}/src" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "reveal": "always", + "panel": "shared" + } + }, + ] +} \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..9983788 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,719 @@ +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +end_of_line = lf +indent_size = 2 +trim_trailing_whitespace = true + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +end_of_line = lf +indent_size = 2 +trim_trailing_whitespace = true + +[*.{liquid}] +end_of_line = lf +indent_size = 4 + +# Powershell files +[*.ps1] +end_of_line = lf +indent_size = 2 + +# Shell script files +[*.sh] +end_of_line = lf +indent_size = 2 + +# JSON files +[*.json] +end_of_line = lf +indent_size = 2 +trim_trailing_whitespace = true + +[*.{js,jsx,ts,tsx}] +end_of_line = lf +indent_size = 2 +trim_trailing_whitespace = true + +# Code files +[*.{cs,csx,vb,vbx}] +end_of_line = lf +indent_size = 4 +trim_trailing_whitespace = true +tab_width = 4 + +[*.g.cs] +dotnet_diagnostic.CS1591.severity = none + +[*.{cs,vb}] +csharp_indent_labels = one_less_than_current +csharp_prefer_braces = false:error +csharp_prefer_simple_default_expression = true:error +csharp_prefer_simple_using_statement = true:error +csharp_prefer_static_local_function = true:error +csharp_preserve_single_line_blocks = true:error +csharp_space_around_binary_operators = before_and_after +csharp_space_before_semicolon_in_for_statement = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_conditional_delegate_call = true:error +csharp_style_deconstructed_variable_declaration = true:error +csharp_style_expression_bodied_accessors = when_on_single_line:error +csharp_style_expression_bodied_constructors = when_on_single_line:error +csharp_style_expression_bodied_indexers = when_on_single_line:error +csharp_style_expression_bodied_lambdas = when_on_single_line:error +csharp_style_expression_bodied_local_functions = when_on_single_line:error +csharp_style_expression_bodied_methods = when_on_single_line:error +csharp_style_expression_bodied_operators = when_on_single_line:error +csharp_style_expression_bodied_properties = when_on_single_line:error +csharp_style_implicit_object_creation_when_type_is_apparent = true:error +csharp_style_inlined_variable_declaration = true:error +csharp_style_namespace_declarations = file_scoped:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_method_group_conversion = true:error +csharp_style_prefer_not_pattern = true:error +csharp_style_prefer_null_check_over_type_check = true:error +csharp_style_prefer_pattern_matching = true:error +csharp_style_prefer_readonly_struct = true:error +csharp_style_prefer_readonly_struct_member = true:error +csharp_style_prefer_switch_expression = true:error +csharp_style_prefer_tuple_swap = true:error +csharp_style_prefer_utf8_string_literals = true:error +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = unused_local_variable:error +csharp_style_var_elsewhere = true:error +csharp_style_var_for_built_in_types = true:none +csharp_style_var_when_type_is_apparent = true:error +csharp_using_directive_placement = outside_namespace:error +dotnet_code_quality_unused_parameters = all:error +dotnet_naming_rule.interface_should_be_begins_with_i.severity = error +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.types_should_be_pascal_case.severity = error +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_style.begins_with_i.capitalization = pascal_case +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.required_modifiers = +dotnet_style_allow_multiple_blank_lines_experimental = false:error +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_collection_initializer = true:error +dotnet_style_explicit_tuple_names = true:error +dotnet_style_namespace_match_folder = false:silent +dotnet_style_null_propagation = true:error +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:error +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:error +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:error +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:error +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error +dotnet_style_prefer_auto_properties = true:error +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_prefer_simplified_boolean_expressions = true:error +dotnet_style_prefer_simplified_interpolation = true:error +dotnet_style_qualification_for_event = false:error +dotnet_style_qualification_for_field = false:error +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_readonly_field = true:error +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +insert_final_newline = false + +dotnet_diagnostic.ASP0000.severity = error +dotnet_diagnostic.ASP0001.severity = error +dotnet_diagnostic.ASP0002.severity = error +dotnet_diagnostic.ASP0003.severity = error +dotnet_diagnostic.ASP0004.severity = error +dotnet_diagnostic.ASP0005.severity = error +dotnet_diagnostic.ASP0006.severity = error +dotnet_diagnostic.ASP0007.severity = error +dotnet_diagnostic.ASP0008.severity = error +dotnet_diagnostic.ASP0009.severity = error +dotnet_diagnostic.ASP0010.severity = error +dotnet_diagnostic.ASP0011.severity = error +dotnet_diagnostic.ASP0012.severity = error +dotnet_diagnostic.ASP0013.severity = error +dotnet_diagnostic.ASP0014.severity = error +dotnet_diagnostic.ASP0015.severity = error +dotnet_diagnostic.ASP0016.severity = error +dotnet_diagnostic.ASP0017.severity = error +dotnet_diagnostic.ASP0018.severity = error +dotnet_diagnostic.ASP0019.severity = error +dotnet_diagnostic.ASP0020.severity = error +dotnet_diagnostic.ASP0021.severity = error +dotnet_diagnostic.ASP0022.severity = error +dotnet_diagnostic.ASP0023.severity = error +dotnet_diagnostic.ASP0024.severity = error +dotnet_diagnostic.ASP0025.severity = error +dotnet_diagnostic.ASP0026.severity = error +dotnet_diagnostic.CA1000.severity = error +dotnet_diagnostic.CA1001.severity = error +dotnet_diagnostic.CA1002.severity = error +dotnet_diagnostic.CA1005.severity = error +dotnet_diagnostic.CA1008.severity = error +dotnet_diagnostic.CA1010.severity = error +dotnet_diagnostic.CA1012.severity = error +dotnet_diagnostic.CA1014.severity = none +dotnet_diagnostic.CA1016.severity = none +dotnet_diagnostic.CA1017.severity = none +dotnet_diagnostic.CA1018.severity = error +dotnet_diagnostic.CA1021.severity = error +dotnet_diagnostic.CA1028.severity = error +dotnet_diagnostic.CA1031.severity = suggestion +dotnet_diagnostic.CA1032.severity = none +dotnet_diagnostic.CA1034.severity = error +dotnet_diagnostic.CA1040.severity = none +dotnet_diagnostic.CA1041.severity = error +dotnet_diagnostic.CA1044.severity = error +dotnet_diagnostic.CA1047.severity = error +dotnet_diagnostic.CA1050.severity = error +dotnet_diagnostic.CA1051.severity = error +dotnet_diagnostic.CA1052.severity = error +dotnet_diagnostic.CA1054.severity = none +dotnet_diagnostic.CA1055.severity = none +dotnet_diagnostic.CA1056.severity = none +dotnet_diagnostic.CA1058.severity = error +dotnet_diagnostic.CA1060.severity = error +dotnet_diagnostic.CA1061.severity = error +dotnet_diagnostic.CA1062.severity = suggestion +dotnet_diagnostic.CA1063.severity = suggestion +dotnet_diagnostic.CA1064.severity = none +dotnet_diagnostic.CA1065.severity = error +dotnet_diagnostic.CA1066.severity = error +dotnet_diagnostic.CA1068.severity = error +dotnet_diagnostic.CA1069.severity = error +dotnet_diagnostic.CA1070.severity = error +dotnet_diagnostic.CA1200.severity = error +dotnet_diagnostic.CA1303.severity = none +dotnet_diagnostic.CA1307.severity = error +dotnet_diagnostic.CA1308.severity = none +dotnet_diagnostic.CA1309.severity = error +dotnet_diagnostic.CA1310.severity = error +dotnet_diagnostic.CA1311.severity = error +dotnet_diagnostic.CA1401.severity = error +dotnet_diagnostic.CA1416.severity = error +dotnet_diagnostic.CA1501.severity = error +dotnet_diagnostic.CA1502.severity = error +dotnet_diagnostic.CA1505.severity = error +dotnet_diagnostic.CA1506.severity = none +dotnet_diagnostic.CA1507.severity = error +dotnet_diagnostic.CA1508.severity = error +dotnet_diagnostic.CA1509.severity = error +dotnet_diagnostic.CA1510.severity = error +dotnet_diagnostic.CA1511.severity = error +dotnet_diagnostic.CA1512.severity = error +dotnet_diagnostic.CA1513.severity = error +dotnet_diagnostic.CA1514.severity = error +dotnet_diagnostic.CA1515.severity = error +dotnet_diagnostic.CA1700.severity = error +dotnet_code_quality.CA1707.api_surface = all +dotnet_diagnostic.CA1707.severity = error +dotnet_diagnostic.CA1708.severity = error +dotnet_diagnostic.CA1710.severity = error +dotnet_diagnostic.CA1711.severity = error +dotnet_diagnostic.CA1712.severity = error +dotnet_diagnostic.CA1715.severity = error +dotnet_diagnostic.CA1716.severity = none +dotnet_diagnostic.CA1717.severity = error +dotnet_diagnostic.CA1720.severity = error +dotnet_diagnostic.CA1721.severity = error +dotnet_diagnostic.CA1724.severity = error +dotnet_diagnostic.CA1725.severity = error +dotnet_diagnostic.CA1727.severity = error +dotnet_diagnostic.CA1802.severity = error +dotnet_diagnostic.CA1805.severity = error +dotnet_diagnostic.CA1806.severity = none +dotnet_diagnostic.CA1810.severity = error +dotnet_diagnostic.CA1812.severity = error +dotnet_diagnostic.CA1813.severity = error +dotnet_diagnostic.CA1816.severity = none +dotnet_code_quality.CA1819.api_surface = all +dotnet_diagnostic.CA1819.severity = error +dotnet_diagnostic.CA1820.severity = error +dotnet_diagnostic.CA1821.severity = error +dotnet_diagnostic.CA1822.severity = error +dotnet_diagnostic.CA1823.severity = error +dotnet_diagnostic.CA1825.severity = error +dotnet_code_quality.CA1826.exclude_ordefault_methods = true +dotnet_diagnostic.CA1826.severity = error +dotnet_diagnostic.CA1827.severity = error +dotnet_diagnostic.CA1828.severity = error +dotnet_diagnostic.CA1829.severity = error +dotnet_diagnostic.CA1830.severity = error +dotnet_diagnostic.CA1831.severity = error +dotnet_diagnostic.CA1832.severity = error +dotnet_diagnostic.CA1833.severity = error +dotnet_diagnostic.CA1834.severity = error +dotnet_diagnostic.CA1835.severity = error +dotnet_diagnostic.CA1836.severity = error +dotnet_diagnostic.CA1837.severity = error +dotnet_diagnostic.CA1840.severity = error +dotnet_diagnostic.CA1841.severity = error +dotnet_diagnostic.CA1842.severity = error +dotnet_diagnostic.CA1843.severity = error +dotnet_diagnostic.CA1844.severity = error +dotnet_diagnostic.CA1845.severity = error +dotnet_diagnostic.CA1846.severity = error +dotnet_diagnostic.CA1847.severity = error +dotnet_diagnostic.CA1848.severity = error +dotnet_diagnostic.CA1849.severity = error +dotnet_diagnostic.CA1850.severity = error +dotnet_diagnostic.CA1851.severity = error +dotnet_code_quality.CA1852.ignore_internalsvisibleto = true +dotnet_diagnostic.CA1852.severity = error +dotnet_diagnostic.CA1853.severity = error +dotnet_diagnostic.CA1854.severity = error +dotnet_diagnostic.CA1855.severity = error +dotnet_diagnostic.CA1856.severity = error +dotnet_diagnostic.CA1857.severity = error +dotnet_diagnostic.CA1858.severity = error +dotnet_diagnostic.CA1859.severity = error +dotnet_diagnostic.CA1861.severity = error +dotnet_diagnostic.CA1862.severity = error +dotnet_diagnostic.CA1863.severity = error +dotnet_diagnostic.CA1864.severity = error +dotnet_diagnostic.CA1865.severity = error +dotnet_diagnostic.CA1866.severity = error +dotnet_diagnostic.CA1867.severity = error +dotnet_diagnostic.CA1868.severity = error +dotnet_diagnostic.CA1869.severity = error +dotnet_diagnostic.CA1870.severity = error +dotnet_diagnostic.CA1871.severity = error +dotnet_diagnostic.CA1872.severity = error +dotnet_diagnostic.CA2000.severity = error +dotnet_diagnostic.CA2002.severity = error +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2008.severity = none +dotnet_diagnostic.CA2009.severity = error +dotnet_diagnostic.CA2011.severity = error +dotnet_diagnostic.CA2012.severity = error +dotnet_diagnostic.CA2013.severity = error +dotnet_diagnostic.CA2014.severity = error +dotnet_diagnostic.CA2015.severity = error +dotnet_diagnostic.CA2016.severity = error +dotnet_diagnostic.CA2017.severity = error +dotnet_diagnostic.CA2018.severity = error +dotnet_diagnostic.CA2019.severity = error +dotnet_diagnostic.CA2021.severity = error +dotnet_diagnostic.CA2022.severity = error +dotnet_diagnostic.CA2101.severity = error +dotnet_diagnostic.CA2200.severity = error +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA2207.severity = error +dotnet_diagnostic.CA2208.severity = error +dotnet_diagnostic.CA2211.severity = error +dotnet_diagnostic.CA2213.severity = error +dotnet_diagnostic.CA2214.severity = error +dotnet_diagnostic.CA2215.severity = error +dotnet_diagnostic.CA2216.severity = none +dotnet_diagnostic.CA2217.severity = error +dotnet_diagnostic.CA2219.severity = error +dotnet_diagnostic.CA2227.severity = error +dotnet_diagnostic.CA2234.severity = none +dotnet_diagnostic.CA2242.severity = error +dotnet_diagnostic.CA2244.severity = error +dotnet_diagnostic.CA2245.severity = error +dotnet_diagnostic.CA2247.severity = error +dotnet_diagnostic.CA2248.severity = error +dotnet_diagnostic.CA2249.severity = error +dotnet_diagnostic.CA2250.severity = error +dotnet_diagnostic.CA2251.severity = error +dotnet_diagnostic.CA2253.severity = error +dotnet_diagnostic.CA2254.severity = error +dotnet_diagnostic.CA2259.severity = error +dotnet_diagnostic.CA2260.severity = error +dotnet_diagnostic.CA2261.severity = error +dotnet_diagnostic.CA2262.severity = error +dotnet_diagnostic.CA2263.severity = error +dotnet_diagnostic.CA2264.severity = error +dotnet_diagnostic.CA2265.severity = error +dotnet_diagnostic.CA5392.severity = none +dotnet_diagnostic.IDE0001.severity = error +dotnet_diagnostic.IDE0002.severity = error +dotnet_diagnostic.IDE0004.severity = error +dotnet_diagnostic.IDE0005.severity = error +dotnet_diagnostic.IDE0007.severity = error +dotnet_diagnostic.IDE0010.severity = suggestion +dotnet_diagnostic.IDE0020.severity = error +dotnet_diagnostic.IDE0029.severity = error +dotnet_diagnostic.IDE0030.severity = error +dotnet_diagnostic.IDE0031.severity = error +dotnet_diagnostic.IDE0036.severity = error +dotnet_diagnostic.IDE0039.severity = error +dotnet_diagnostic.IDE0041.severity = error +dotnet_diagnostic.IDE0043.severity = error +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_diagnostic.IDE0049.severity = error +dotnet_diagnostic.IDE0054.severity = error +dotnet_diagnostic.IDE0057.severity = suggestion +dotnet_diagnostic.IDE0058.severity = none +dotnet_diagnostic.IDE0059.severity = error +dotnet_diagnostic.IDE0060.severity = error +dotnet_diagnostic.IDE0061.severity = error +dotnet_diagnostic.IDE0062.severity = error +dotnet_diagnostic.IDE0063.severity = error +dotnet_diagnostic.IDE0064.severity = error +dotnet_diagnostic.IDE0065.severity = error +dotnet_diagnostic.IDE0066.severity = error +dotnet_diagnostic.IDE0070.severity = error +dotnet_diagnostic.IDE0071.severity = error +dotnet_diagnostic.IDE0072.severity = none +dotnet_diagnostic.IDE0073.severity = error +dotnet_diagnostic.IDE0074.severity = error +dotnet_diagnostic.IDE0075.severity = error +dotnet_diagnostic.IDE0078.severity = error +dotnet_diagnostic.IDE0079.severity = error +dotnet_diagnostic.IDE0080.severity = error +dotnet_diagnostic.IDE0081.severity = error +dotnet_diagnostic.IDE0082.severity = error +dotnet_diagnostic.IDE0083.severity = error +dotnet_diagnostic.IDE0084.severity = error +dotnet_diagnostic.IDE0090.severity = error +dotnet_diagnostic.IDE0100.severity = error +dotnet_diagnostic.IDE0110.severity = error +dotnet_diagnostic.IDE0120.severity = error +dotnet_diagnostic.IDE0130.severity = error +dotnet_diagnostic.IDE0140.severity = error +dotnet_diagnostic.IDE0150.severity = error +dotnet_diagnostic.IDE0160.severity = error +dotnet_diagnostic.IDE0161.severity = error +dotnet_diagnostic.IDE0170.severity = error +dotnet_diagnostic.IDE0180.severity = error +dotnet_diagnostic.IDE0200.severity = error +dotnet_diagnostic.IDE0210.severity = error +dotnet_diagnostic.IDE0211.severity = error +dotnet_diagnostic.IDE0220.severity = none +dotnet_diagnostic.IDE0230.severity = error +dotnet_diagnostic.IDE0240.severity = error +dotnet_diagnostic.IDE0241.severity = error +dotnet_diagnostic.IDE0250.severity = error +dotnet_diagnostic.IDE0251.severity = error +dotnet_diagnostic.IDE0260.severity = error +dotnet_diagnostic.IDE0270.severity = suggestion +dotnet_diagnostic.IDE0280.severity = error +dotnet_diagnostic.IDE0290.severity = error +dotnet_diagnostic.IDE0300.severity = error +dotnet_diagnostic.IDE0301.severity = error +dotnet_diagnostic.IDE0302.severity = error +dotnet_diagnostic.IDE0303.severity = error +dotnet_diagnostic.IDE0304.severity = error +dotnet_diagnostic.IDE0305.severity = suggestion +dotnet_diagnostic.IDE0320.severity = error +dotnet_diagnostic.IDE0330.severity = error +dotnet_diagnostic.IDE1005.severity = error +dotnet_diagnostic.MVC1000.severity = error +dotnet_diagnostic.MVC1001.severity = error +dotnet_diagnostic.MVC1002.severity = error +dotnet_diagnostic.MVC1003.severity = error +dotnet_diagnostic.MVC1004.severity = error +dotnet_diagnostic.MVC1005.severity = error +dotnet_diagnostic.MVC1006.severity = error +dotnet_diagnostic.SYSLIB1045.severity = error +dotnet_diagnostic.SYSLIB1054.severity = error +dotnet_diagnostic.VSTHRD001.severity = error +dotnet_diagnostic.VSTHRD002.severity = error +dotnet_diagnostic.VSTHRD003.severity = error +dotnet_diagnostic.VSTHRD004.severity = error +dotnet_diagnostic.VSTHRD010.severity = error +dotnet_diagnostic.VSTHRD011.severity = error +dotnet_diagnostic.VSTHRD012.severity = error +dotnet_diagnostic.VSTHRD100.severity = error +dotnet_diagnostic.VSTHRD101.severity = error +dotnet_diagnostic.VSTHRD102.severity = error +dotnet_diagnostic.VSTHRD103.severity = error +dotnet_diagnostic.VSTHRD104.severity = error +dotnet_diagnostic.VSTHRD105.severity = error +dotnet_diagnostic.VSTHRD106.severity = error +dotnet_diagnostic.VSTHRD107.severity = error +dotnet_diagnostic.VSTHRD108.severity = error +dotnet_diagnostic.VSTHRD109.severity = error +dotnet_diagnostic.VSTHRD110.severity = error +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.VSTHRD112.severity = error +dotnet_diagnostic.VSTHRD113.severity = error +dotnet_diagnostic.VSTHRD114.severity = error +dotnet_diagnostic.VSTHRD115.severity = error +dotnet_diagnostic.VSTHRD200.severity = error + +# StyleCop rules +dotnet_diagnostic.SA0001.severity = none +dotnet_diagnostic.SA1005.severity = none +dotnet_diagnostic.SA1101.severity = none +dotnet_diagnostic.SA1105.severity = none +dotnet_diagnostic.SA1118.severity = none +dotnet_diagnostic.SA1128.severity = none +dotnet_diagnostic.SA1129.severity = error +dotnet_diagnostic.SA1133.severity = none +dotnet_diagnostic.SA1134.severity = none +dotnet_diagnostic.SA1204.severity = none +dotnet_diagnostic.SA1208.severity = error +dotnet_diagnostic.SA1210.severity = error +dotnet_diagnostic.SA1309.severity = error +dotnet_diagnostic.SA1313.severity = suggestion +dotnet_diagnostic.SA1402.severity = none +dotnet_diagnostic.SA1413.severity = none +dotnet_diagnostic.SA1502.severity = none +dotnet_diagnostic.SA1503.severity = none +dotnet_diagnostic.SA1512.severity = none +dotnet_diagnostic.SA1513.severity = none +dotnet_diagnostic.SA1514.severity = none +dotnet_diagnostic.SA1515.severity = none +dotnet_diagnostic.SA1516.severity = none +dotnet_diagnostic.SA1600.severity = none +dotnet_diagnostic.SA1601.severity = none +dotnet_diagnostic.SA1602.severity = none +dotnet_diagnostic.SA1604.severity = none +dotnet_diagnostic.SA1611.severity = none +dotnet_diagnostic.SA1612.severity = none +dotnet_diagnostic.SA1614.severity = none +dotnet_diagnostic.SA1615.severity = none +dotnet_diagnostic.SA1618.severity = none +dotnet_diagnostic.SA1623.severity = none +dotnet_diagnostic.SA1629.severity = none +dotnet_diagnostic.SA1633.severity = none +dotnet_diagnostic.SA1642.severity = none +dotnet_diagnostic.SA1649.severity = none + +# Roslynator analyzer +roslynator_accessibility_modifiers = explicit +roslynator_accessor_braces_style = single_line_when_expression_is_on_single_line +roslynator_array_creation_type_style = implicit_when_type_is_obvious +roslynator_arrow_token_new_line = after +roslynator_binary_operator_new_line = before +roslynator_blank_line_between_closing_brace_and_switch_section = true +roslynator_blank_line_between_single_line_accessors = false +roslynator_blank_line_between_switch_sections = omit +roslynator_blank_line_between_using_directives = never +roslynator_body_style = expression +roslynator_conditional_operator_condition_parentheses_style = omit +roslynator_conditional_operator_new_line = before +roslynator_configure_await = false +roslynator_equals_token_new_line = after +roslynator_max_line_length = 120 +roslynator_new_line_before_while_in_do_statement = true +roslynator_null_check_style = pattern_matching +roslynator_null_conditional_operator_new_line = after +roslynator_object_creation_parentheses_style = omit +roslynator_object_creation_type_style = implicit_when_type_is_obvious +roslynator_trailing_comma_style = omit +roslynator_use_block_body_when_declaration_spans_over_multiple_lines = false +roslynator_use_block_body_when_expression_spans_over_multiple_lines = false +roslynator_use_collection_expression = true +roslynator_use_var_instead_of_implicit_object_creation = true +dotnet_diagnostic.RCS0001.severity = error +dotnet_diagnostic.RCS0003.severity = error +dotnet_diagnostic.RCS0011.severity = error +dotnet_diagnostic.RCS0015.severity = error +dotnet_diagnostic.RCS0020.severity = error +dotnet_diagnostic.RCS0027.severity = error +dotnet_diagnostic.RCS0028.severity = error +dotnet_diagnostic.RCS0030.severity = error +dotnet_diagnostic.RCS0032.severity = error +dotnet_diagnostic.RCS0033.severity = error +dotnet_diagnostic.RCS0041.severity = error +dotnet_diagnostic.RCS0042.severity = error +dotnet_diagnostic.RCS0045.severity = error +dotnet_diagnostic.RCS0046.severity = error +dotnet_diagnostic.RCS0050.severity = error +dotnet_diagnostic.RCS0051.severity = error +dotnet_diagnostic.RCS0052.severity = error +dotnet_diagnostic.RCS0053.severity = error +dotnet_diagnostic.RCS0054.severity = error +dotnet_diagnostic.RCS0055.severity = error +dotnet_diagnostic.RCS0056.severity = error +dotnet_diagnostic.RCS0057.severity = error +dotnet_diagnostic.RCS0059.severity = error +dotnet_diagnostic.RCS0060.severity = error +dotnet_diagnostic.RCS0061.severity = error +dotnet_diagnostic.RCS1001.severity = error +dotnet_diagnostic.RCS1002.severity = error +dotnet_diagnostic.RCS1003.severity = error +dotnet_diagnostic.RCS1004.severity = error +dotnet_diagnostic.RCS1005.severity = error +dotnet_diagnostic.RCS1006.severity = error +dotnet_diagnostic.RCS1013.severity = error +dotnet_diagnostic.RCS1015.severity = error +dotnet_diagnostic.RCS1016.severity = error +dotnet_diagnostic.RCS1018.severity = error +dotnet_diagnostic.RCS1020.severity = error +dotnet_diagnostic.RCS1021.severity = error +dotnet_diagnostic.RCS1031.severity = error +dotnet_diagnostic.RCS1032.severity = error +dotnet_diagnostic.RCS1033.severity = error +dotnet_diagnostic.RCS1034.severity = error +dotnet_diagnostic.RCS1036.severity = error +dotnet_diagnostic.RCS1037.severity = error +dotnet_diagnostic.RCS1039.severity = error +dotnet_diagnostic.RCS1042.severity = error +dotnet_diagnostic.RCS1044.severity = error +dotnet_diagnostic.RCS1046.severity = error +dotnet_diagnostic.RCS1047.severity = error +dotnet_diagnostic.RCS1048.severity = error +dotnet_diagnostic.RCS1049.severity = error +dotnet_diagnostic.RCS1050.severity = error +dotnet_diagnostic.RCS1051.severity = error +dotnet_diagnostic.RCS1055.severity = error +dotnet_diagnostic.RCS1058.severity = error +dotnet_diagnostic.RCS1059.severity = error +dotnet_diagnostic.RCS1061.severity = error +dotnet_diagnostic.RCS1068.severity = error +dotnet_diagnostic.RCS1069.severity = error +dotnet_diagnostic.RCS1070.severity = error +dotnet_diagnostic.RCS1071.severity = error +dotnet_diagnostic.RCS1073.severity = error +dotnet_diagnostic.RCS1074.severity = error +dotnet_diagnostic.RCS1077.severity = error +dotnet_diagnostic.RCS1080.severity = error +dotnet_diagnostic.RCS1084.severity = error +dotnet_diagnostic.RCS1085.severity = error +dotnet_diagnostic.RCS1089.severity = error +dotnet_diagnostic.RCS1090.severity = error +dotnet_diagnostic.RCS1093.severity = error +dotnet_diagnostic.RCS1094.severity = error +dotnet_diagnostic.RCS1096.severity = error +dotnet_diagnostic.RCS1097.severity = error +dotnet_diagnostic.RCS1098.severity = error +dotnet_diagnostic.RCS1099.severity = error +dotnet_diagnostic.RCS1102.severity = error +dotnet_diagnostic.RCS1103.severity = error +dotnet_diagnostic.RCS1104.severity = error +dotnet_diagnostic.RCS1105.severity = error +dotnet_diagnostic.RCS1107.severity = error +dotnet_diagnostic.RCS1110.severity = error +dotnet_diagnostic.RCS1112.severity = error +dotnet_diagnostic.RCS1113.severity = error +dotnet_diagnostic.RCS1114.severity = error +dotnet_diagnostic.RCS1118.severity = error +dotnet_diagnostic.RCS1123.severity = error +dotnet_diagnostic.RCS1128.severity = error +dotnet_diagnostic.RCS1129.severity = error +dotnet_diagnostic.RCS1130.severity = error +dotnet_diagnostic.RCS1132.severity = error +dotnet_diagnostic.RCS1133.severity = error +dotnet_diagnostic.RCS1134.severity = error +dotnet_diagnostic.RCS1136.severity = error +dotnet_diagnostic.RCS1143.severity = error +dotnet_diagnostic.RCS1145.severity = error +dotnet_diagnostic.RCS1146.severity = error +dotnet_diagnostic.RCS1151.severity = error +dotnet_diagnostic.RCS1154.severity = error +dotnet_diagnostic.RCS1155.severity = error +dotnet_diagnostic.RCS1156.severity = error +dotnet_diagnostic.RCS1157.severity = error +dotnet_diagnostic.RCS1158.severity = error +dotnet_diagnostic.RCS1159.severity = error +dotnet_diagnostic.RCS1160.severity = error +dotnet_diagnostic.RCS1163.severity = error +dotnet_diagnostic.RCS1164.severity = error +dotnet_diagnostic.RCS1166.severity = error +dotnet_diagnostic.RCS1168.severity = error +dotnet_diagnostic.RCS1169.severity = error +dotnet_diagnostic.RCS1170.severity = error +dotnet_diagnostic.RCS1172.severity = error +dotnet_diagnostic.RCS1173.severity = error +dotnet_diagnostic.RCS1174.severity = error +dotnet_diagnostic.RCS1175.severity = error +dotnet_diagnostic.RCS1179.severity = error +dotnet_diagnostic.RCS1182.severity = error +dotnet_diagnostic.RCS1186.severity = error +dotnet_diagnostic.RCS1187.severity = error +dotnet_diagnostic.RCS1188.severity = error +dotnet_diagnostic.RCS1190.severity = error +dotnet_diagnostic.RCS1191.severity = error +dotnet_diagnostic.RCS1192.severity = error +dotnet_diagnostic.RCS1193.severity = error +dotnet_diagnostic.RCS1194.severity = none +dotnet_diagnostic.RCS1195.severity = error +dotnet_diagnostic.RCS1196.severity = error +dotnet_diagnostic.RCS1197.severity = error +dotnet_diagnostic.RCS1198.severity = error +dotnet_diagnostic.RCS1199.severity = error +dotnet_diagnostic.RCS1200.severity = error +dotnet_diagnostic.RCS1201.severity = error +dotnet_diagnostic.RCS1202.severity = error +dotnet_diagnostic.RCS1203.severity = error +dotnet_diagnostic.RCS1204.severity = error +dotnet_diagnostic.RCS1205.severity = error +dotnet_diagnostic.RCS1206.severity = error +dotnet_diagnostic.RCS1207.severity = error +dotnet_diagnostic.RCS1209.severity = error +dotnet_diagnostic.RCS1210.severity = error +dotnet_diagnostic.RCS1211.severity = error +dotnet_diagnostic.RCS1212.severity = error +dotnet_diagnostic.RCS1213.severity = error +dotnet_diagnostic.RCS1214.severity = error +dotnet_diagnostic.RCS1215.severity = error +dotnet_diagnostic.RCS1216.severity = error +dotnet_diagnostic.RCS1218.severity = error +dotnet_diagnostic.RCS1220.severity = error +dotnet_diagnostic.RCS1221.severity = error +dotnet_diagnostic.RCS1222.severity = none +dotnet_diagnostic.RCS1225.severity = error +dotnet_diagnostic.RCS1228.severity = error +dotnet_diagnostic.RCS1229.severity = error +dotnet_diagnostic.RCS1230.severity = error +dotnet_diagnostic.RCS1231.severity = none # Make parameter ref read-only - should be avoided as it can have perf implications +dotnet_diagnostic.RCS1232.severity = error +dotnet_diagnostic.RCS1233.severity = error +dotnet_diagnostic.RCS1234.severity = error +dotnet_diagnostic.RCS1235.severity = error +dotnet_diagnostic.RCS1236.severity = error +dotnet_diagnostic.RCS1239.severity = error +dotnet_diagnostic.RCS1240.severity = error +dotnet_diagnostic.RCS1242.severity = error +dotnet_diagnostic.RCS1243.severity = error +dotnet_diagnostic.RCS1244.severity = error +dotnet_diagnostic.RCS1246.severity = error +dotnet_diagnostic.RCS1247.severity = error +dotnet_diagnostic.RCS1248.severity = error +dotnet_diagnostic.RCS1249.severity = error +dotnet_diagnostic.RCS1251.severity = error +dotnet_diagnostic.RCS1255.severity = error +dotnet_diagnostic.RCS1256.severity = error +dotnet_diagnostic.RCS1257.severity = error +dotnet_diagnostic.RCS1259.severity = error +dotnet_diagnostic.RCS1260.severity = error +dotnet_diagnostic.RCS1261.severity = error +dotnet_diagnostic.RCS1262.severity = error +dotnet_diagnostic.RCS1263.severity = error +dotnet_diagnostic.RCS1265.severity = error +dotnet_diagnostic.RCS1267.severity = error +dotnet_diagnostic.RCS1268.severity = error \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3ba3a75..fad57d8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,27 +2,19 @@ - net9.0 + net9.0 + latest + enable enable true true true + true + true + false + true + All + false - - - 1.0.* - 1.0.* - 9.0.* - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 0000000..de41659 --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + \ No newline at end of file diff --git a/src/Interprocess.Benchmark/.editorconfig b/src/Interprocess.Benchmark/.editorconfig new file mode 100644 index 0000000..0e7e61e --- /dev/null +++ b/src/Interprocess.Benchmark/.editorconfig @@ -0,0 +1,9 @@ +root = false + +[*.{cs,vb}] +dotnet_diagnostic.CA1305.severity = none # Benchmark doesn't require formatters +dotnet_diagnostic.CA1515.severity = none +dotnet_diagnostic.CS1591.severity = none # Benchmark doesn't require XML comments +dotnet_diagnostic.CA1707.severity = none # Benchmark doesn't require underscores in method names +dotnet_diagnostic.CA1861.severity = none # Benchmark can use constant array arguments +dotnet_diagnostic.VSTHRD003.severity = none # Avoid awaiting foreign Tasks \ No newline at end of file diff --git a/src/Interprocess.Benchmark/Interprocess.Benchmark.csproj b/src/Interprocess.Benchmark/Interprocess.Benchmark.csproj index f576b8e..d96350c 100644 --- a/src/Interprocess.Benchmark/Interprocess.Benchmark.csproj +++ b/src/Interprocess.Benchmark/Interprocess.Benchmark.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Interprocess.Benchmark/Program.cs b/src/Interprocess.Benchmark/Program.cs index 21bb044..b176e0f 100644 --- a/src/Interprocess.Benchmark/Program.cs +++ b/src/Interprocess.Benchmark/Program.cs @@ -1,12 +1,8 @@ using BenchmarkDotNet.Running; -namespace Cloudtoid.Interprocess.Benchmark +namespace Cloudtoid.Interprocess.Benchmark; + +public sealed class Program { - public sealed class Program - { - public static void Main() - { - _ = BenchmarkRunner.Run(typeof(Program).Assembly); - } - } -} + public static void Main() => _ = BenchmarkRunner.Run(typeof(Program).Assembly); +} \ No newline at end of file diff --git a/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs b/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs index 4f6bbef..bf2eb3e 100644 --- a/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs @@ -1,50 +1,47 @@ -using System; -using System.IO; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace Cloudtoid.Interprocess.Benchmark +namespace Cloudtoid.Interprocess.Benchmark; + +[SimpleJob(RuntimeMoniker.Net90)] +[MemoryDiagnoser] +[MarkdownExporterAttribute.GitHub] +public class EnqueueBenchmark { - [SimpleJob(RuntimeMoniker.Net90)] - [MemoryDiagnoser] - [MarkdownExporterAttribute.GitHub] - public class EnqueueBenchmark - { - private static readonly byte[] Message = new byte[] { 100, 110, 120 }; - private static readonly Memory MessageBuffer = new byte[Message.Length]; + private static readonly byte[] Message = [100, 110, 120]; + private static readonly Memory MessageBuffer = new byte[Message.Length]; #pragma warning disable CS8618 - private IPublisher publisher; - private ISubscriber subscriber; + private IPublisher publisher; + private ISubscriber subscriber; #pragma warning restore CS8618 - [GlobalSetup] - public void Setup() - { - var queueFactory = new QueueFactory(); - publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 5120000)); - subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 5120000)); - } + [GlobalSetup] + public void Setup() + { + var queueFactory = new QueueFactory(); + publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 5120000)); + subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 5120000)); + } - [GlobalCleanup] - public void Cleanup() - { - subscriber.Dispose(); - publisher.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + subscriber.Dispose(); + publisher.Dispose(); + } - [IterationCleanup] - public void DrainQueue() - { - for (int i = 8; i < 320000; i++) - subscriber.Dequeue(MessageBuffer, default); - } + [IterationCleanup] + public void DrainQueue() + { + for (int i = 8; i < 320000; i++) + subscriber.Dequeue(MessageBuffer, default); + } - // Expecting that there are NO managed heap allocations. - [Benchmark(Description = "Message enqueue (320,000 times)")] - public void Enqueue() - { - for (int i = 8; i < 320000; i++) - publisher.TryEnqueue(Message); - } + // Expecting that there are NO managed heap allocations. + [Benchmark(Description = "Message enqueue (320,000 times)")] + public void Enqueue() + { + for (int i = 8; i < 320000; i++) + publisher.TryEnqueue(Message); } -} +} \ No newline at end of file diff --git a/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs b/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs index 98c1a74..29b21e0 100644 --- a/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs @@ -1,54 +1,51 @@ -using System; -using System.IO; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace Cloudtoid.Interprocess.Benchmark +namespace Cloudtoid.Interprocess.Benchmark; + +[SimpleJob(RuntimeMoniker.Net90)] +[MemoryDiagnoser] +[MarkdownExporterAttribute.GitHub] +public class QueueBenchmark { - [SimpleJob(RuntimeMoniker.Net90)] - [MemoryDiagnoser] - [MarkdownExporterAttribute.GitHub] - public class QueueBenchmark - { - private static readonly byte[] Message = new byte[] { 100, 110, 120 }; - private static readonly Memory MessageBuffer = new byte[Message.Length]; + private static readonly byte[] Message = [100, 110, 120]; + private static readonly Memory MessageBuffer = new byte[Message.Length]; #pragma warning disable CS8618 - private IPublisher publisher; - private ISubscriber subscriber; + private IPublisher publisher; + private ISubscriber subscriber; #pragma warning restore CS8618 - [GlobalSetup] - public void Setup() - { - var queueFactory = new QueueFactory(); - publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 128)); - subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 128)); - } - - [GlobalCleanup] - public void Cleanup() - { - subscriber.Dispose(); - publisher.Dispose(); - } - - [Benchmark(Description = "Message enqueue and dequeue - no message buffer")] - public ReadOnlyMemory EnqueueDequeue_WithResultArrayAllocation() - { - if (!publisher.TryEnqueue(Message)) - throw new Exception("Failed to enqueue"); - - return subscriber.Dequeue(default); - } - - // Expecting that there are NO managed heap allocations. - [Benchmark(Description = "Message enqueue and dequeue")] - public ReadOnlyMemory EnqueueAndDequeue_WithPooledResultArray() - { - if (!publisher.TryEnqueue(Message)) - throw new Exception("Failed to enqueue"); - - return subscriber.Dequeue(MessageBuffer, default); - } + [GlobalSetup] + public void Setup() + { + var queueFactory = new QueueFactory(); + publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 128)); + subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 128)); + } + + [GlobalCleanup] + public void Cleanup() + { + subscriber.Dispose(); + publisher.Dispose(); + } + + [Benchmark(Description = "Message enqueue and dequeue - no message buffer")] + public ReadOnlyMemory EnqueueDequeue_WithResultArrayAllocation() + { + if (!publisher.TryEnqueue(Message)) + throw new Exception("Failed to enqueue"); + + return subscriber.Dequeue(default); + } + + // Expecting that there are NO managed heap allocations. + [Benchmark(Description = "Message enqueue and dequeue")] + public ReadOnlyMemory EnqueueAndDequeue_WithPooledResultArray() + { + if (!publisher.TryEnqueue(Message)) + throw new Exception("Failed to enqueue"); + + return subscriber.Dequeue(MessageBuffer, default); } -} +} \ No newline at end of file diff --git a/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs b/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs index 0bb603d..1116f8a 100644 --- a/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs @@ -1,57 +1,54 @@ -using System; -using System.IO; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace Cloudtoid.Interprocess.Benchmark +namespace Cloudtoid.Interprocess.Benchmark; + +[SimpleJob(RuntimeMoniker.Net90)] +[MarkdownExporterAttribute.GitHub] +public class QueueExtendedBenchmark { - [SimpleJob(RuntimeMoniker.Net90)] - [MarkdownExporterAttribute.GitHub] - public class QueueExtendedBenchmark - { - private static readonly byte[] Message = new byte[50]; - private static readonly byte[] MessageBuffer = new byte[Message.Length]; + private static readonly byte[] Message = new byte[50]; + private static readonly byte[] MessageBuffer = new byte[Message.Length]; #pragma warning disable CS8618 - private IPublisher publisher; - private ISubscriber subscriber; + private IPublisher publisher; + private ISubscriber subscriber; #pragma warning restore CS8618 - [GlobalSetup] - public void Setup() - { - var queueFactory = new QueueFactory(); - publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 128)); - subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 128)); - } - - [GlobalCleanup] - public void Cleanup() - { - subscriber.Dispose(); - publisher.Dispose(); - } - - [Benchmark(Description = "Message enqueue and dequeue - long message")] - public ReadOnlyMemory EnqueueDequeue_LongMessage() - { - if (!publisher.TryEnqueue(Message)) - throw new Exception("Failed to enqueue"); - - return subscriber.Dequeue(MessageBuffer, default); - } - - [Benchmark(Description = "Message enqueue and dequeue - wrapped message in circular buffer")] - public ReadOnlyMemory EnqueueDequeue_WrappedMessages() - { - if (!publisher.TryEnqueue(Message)) - throw new Exception("Failed to enqueue"); - - subscriber.Dequeue(MessageBuffer, default); - - if (!publisher.TryEnqueue(Message)) - throw new Exception("Failed to enqueue"); - - return subscriber.Dequeue(MessageBuffer, default); - } + [GlobalSetup] + public void Setup() + { + var queueFactory = new QueueFactory(); + publisher = queueFactory.CreatePublisher(new QueueOptions("qn", Path.GetTempPath(), 128)); + subscriber = queueFactory.CreateSubscriber(new QueueOptions("qn", Path.GetTempPath(), 128)); + } + + [GlobalCleanup] + public void Cleanup() + { + subscriber.Dispose(); + publisher.Dispose(); + } + + [Benchmark(Description = "Message enqueue and dequeue - long message")] + public ReadOnlyMemory EnqueueDequeue_LongMessage() + { + if (!publisher.TryEnqueue(Message)) + throw new Exception("Failed to enqueue"); + + return subscriber.Dequeue(MessageBuffer, default); + } + + [Benchmark(Description = "Message enqueue and dequeue - wrapped message in circular buffer")] + public ReadOnlyMemory EnqueueDequeue_WrappedMessages() + { + if (!publisher.TryEnqueue(Message)) + throw new Exception("Failed to enqueue"); + + subscriber.Dequeue(MessageBuffer, default); + + if (!publisher.TryEnqueue(Message)) + throw new Exception("Failed to enqueue"); + + return subscriber.Dequeue(MessageBuffer, default); } -} +} \ No newline at end of file diff --git a/src/Interprocess.Tests/.editorconfig b/src/Interprocess.Tests/.editorconfig new file mode 100644 index 0000000..94f5394 --- /dev/null +++ b/src/Interprocess.Tests/.editorconfig @@ -0,0 +1,10 @@ +root = false + +[*.{cs,vb}] +dotnet_diagnostic.CA1305.severity = none # Tests don't require formatters +dotnet_diagnostic.CA1515.severity = none +dotnet_diagnostic.CS1591.severity = none # Tests don't require XML comments +dotnet_diagnostic.CA1707.severity = none # Tests don't require underscores in method names +dotnet_diagnostic.CA1861.severity = none # Tests can use constant array arguments +dotnet_diagnostic.MSTEST0030.severity = error # Type containing '[TestMethod]' should be marked with '[TestClass]' +dotnet_diagnostic.VSTHRD003.severity = none # Avoid awaiting foreign Tasks \ No newline at end of file diff --git a/src/Interprocess.Tests/CircularBufferTests.cs b/src/Interprocess.Tests/CircularBufferTests.cs index 1e0a8a5..e404e6c 100644 --- a/src/Interprocess.Tests/CircularBufferTests.cs +++ b/src/Interprocess.Tests/CircularBufferTests.cs @@ -1,149 +1,151 @@ -using System.Linq; -using FluentAssertions; +using FluentAssertions; using Xunit; -namespace Cloudtoid.Interprocess.Tests +namespace Cloudtoid.Interprocess.Tests; + +public unsafe class CircularBufferTests { - public unsafe class CircularBufferTests - { - private static readonly byte[] ByteArray1 = new byte[] { 100, }; - private static readonly byte[] ByteArray2 = new byte[] { 100, 110 }; - private static readonly byte[] ByteArray3 = new byte[] { 100, 110, 120 }; + private static readonly byte[] ByteArray = [100, 110, 120]; - [Theory] - [InlineData(new byte[] { 100 }, 0, 0)] - [InlineData(new byte[] { 100 }, 1, 0)] - [InlineData(new byte[] { 100 }, 2, 0)] - [InlineData(new byte[] { 100 }, 3, 0)] - [InlineData(new byte[] { 100, 110 }, 0, 0)] - [InlineData(new byte[] { 100, 110 }, 1, 1)] - [InlineData(new byte[] { 100, 110 }, 2, 0)] - [InlineData(new byte[] { 100, 110 }, 3, 1)] - public void CanAdjustOffset(byte[] bytes, long offset, long adjustedOffset) + [Theory] + [InlineData(new byte[] { 100 }, 0, 0)] + [InlineData(new byte[] { 100 }, 1, 0)] + [InlineData(new byte[] { 100 }, 2, 0)] + [InlineData(new byte[] { 100 }, 3, 0)] + [InlineData(new byte[] { 100, 110 }, 0, 0)] + [InlineData(new byte[] { 100, 110 }, 1, 1)] + [InlineData(new byte[] { 100, 110 }, 2, 0)] + [InlineData(new byte[] { 100, 110 }, 3, 1)] + public void CanAdjustOffset(byte[] bytes, long offset, long adjustedOffset) + { + fixed (byte* bytesPtr = &bytes[0]) { - fixed (byte* bytesPtr = &bytes[0]) - { - var buffer = new CircularBuffer(bytesPtr, bytes.Length); - buffer.Capacity.Should().Be(bytes.Length); - buffer.AdjustedOffset(ref offset); - offset.Should().Be(adjustedOffset); - } + var buffer = new CircularBuffer(bytesPtr, bytes.Length); + buffer.Capacity.Should().Be(bytes.Length); + buffer.AdjustedOffset(ref offset); + offset.Should().Be(adjustedOffset); } + } - [Theory] - [InlineData(new byte[] { 100 }, 0, 100)] - [InlineData(new byte[] { 100 }, 1, 100)] - [InlineData(new byte[] { 100 }, 2, 100)] - [InlineData(new byte[] { 100 }, 3, 100)] - [InlineData(new byte[] { 100, 110 }, 0, 100)] - [InlineData(new byte[] { 100, 110 }, 1, 110)] - [InlineData(new byte[] { 100, 110 }, 2, 100)] - [InlineData(new byte[] { 100, 110 }, 3, 110)] - public void CanGetPointer(byte[] bytes, long offset, byte expectedValue) + [Theory] + [InlineData(new byte[] { 100 }, 0, 100)] + [InlineData(new byte[] { 100 }, 1, 100)] + [InlineData(new byte[] { 100 }, 2, 100)] + [InlineData(new byte[] { 100 }, 3, 100)] + [InlineData(new byte[] { 100, 110 }, 0, 100)] + [InlineData(new byte[] { 100, 110 }, 1, 110)] + [InlineData(new byte[] { 100, 110 }, 2, 100)] + [InlineData(new byte[] { 100, 110 }, 3, 110)] + public void CanGetPointer(byte[] bytes, long offset, byte expectedValue) + { + fixed (byte* bytesPtr = &bytes[0]) { - fixed (byte* bytesPtr = &bytes[0]) - { - var buffer = new CircularBuffer(bytesPtr, bytes.Length); - buffer.Capacity.Should().Be(bytes.Length); - var b = *buffer.GetPointer(offset); - b.Should().Be(expectedValue); - } + var buffer = new CircularBuffer(bytesPtr, bytes.Length); + buffer.Capacity.Should().Be(bytes.Length); + var b = *buffer.GetPointer(offset); + b.Should().Be(expectedValue); } + } - [Theory] - [InlineData(0, 0, new byte[] { })] - [InlineData(0, 1, new byte[] { 100 })] - [InlineData(1, 1, new byte[] { 110 })] - [InlineData(2, 1, new byte[] { 120 })] - [InlineData(3, 1, new byte[] { 100 })] - [InlineData(0, 2, new byte[] { 100, 110 })] - [InlineData(1, 2, new byte[] { 110, 120 })] - [InlineData(2, 2, new byte[] { 120, 100 })] - [InlineData(3, 2, new byte[] { 100, 110 })] - [InlineData(0, 3, new byte[] { 100, 110, 120 })] - [InlineData(1, 3, new byte[] { 110, 120, 100 })] - [InlineData(2, 3, new byte[] { 120, 100, 110 })] - [InlineData(3, 3, new byte[] { 100, 110, 120 })] - [InlineData(0, 4, new byte[] { 100, 110, 120, 100 })] - [InlineData(1, 4, new byte[] { 110, 120, 100, 110 })] - [InlineData(0, 0, new byte[] { }, 1)] - [InlineData(1, 4, new byte[] { 110 }, 1)] - [InlineData(1, 2, new byte[] { 110, 120 }, 6)] - public void CanRead(long offset, int length, byte[] expectedResult, int? bufferLength = null) + [Theory] + [InlineData(0, 0, new byte[] { })] + [InlineData(0, 1, new byte[] { 100 })] + [InlineData(1, 1, new byte[] { 110 })] + [InlineData(2, 1, new byte[] { 120 })] + [InlineData(3, 1, new byte[] { 100 })] + [InlineData(0, 2, new byte[] { 100, 110 })] + [InlineData(1, 2, new byte[] { 110, 120 })] + [InlineData(2, 2, new byte[] { 120, 100 })] + [InlineData(3, 2, new byte[] { 100, 110 })] + [InlineData(0, 3, new byte[] { 100, 110, 120 })] + [InlineData(1, 3, new byte[] { 110, 120, 100 })] + [InlineData(2, 3, new byte[] { 120, 100, 110 })] + [InlineData(3, 3, new byte[] { 100, 110, 120 })] + [InlineData(0, 4, new byte[] { 100, 110, 120, 100 })] + [InlineData(1, 4, new byte[] { 110, 120, 100, 110 })] + [InlineData(0, 0, new byte[] { }, 1)] + [InlineData(1, 4, new byte[] { 110 }, 1)] + [InlineData(1, 2, new byte[] { 110, 120 }, 6)] + public void CanRead(long offset, int length, byte[] expectedResult, int? bufferLength = null) + { + fixed (byte* bytesPtr = &ByteArray[0]) { - fixed (byte* bytesPtr = &ByteArray3[0]) - { - var buffer = new CircularBuffer(bytesPtr, ByteArray3.Length); - if (bufferLength is null) - buffer.Read(offset, length).ToArray().Should().BeEquivalentTo(expectedResult); + var buffer = new CircularBuffer(bytesPtr, ByteArray.Length); + if (bufferLength is null) + buffer.Read(offset, length).ToArray().Should().BeEquivalentTo(expectedResult); - var resultBuffer = new byte[bufferLength ?? length]; - buffer.Read(offset, length, resultBuffer).ToArray().Should().BeEquivalentTo(expectedResult); - } + var resultBuffer = new byte[bufferLength ?? length]; + buffer.Read(offset, length, resultBuffer).ToArray().Should().BeEquivalentTo(expectedResult); } + } - [Theory] - [InlineData(0, 0, new byte[] { })] - [InlineData(0, 1, new byte[] { 100 })] - [InlineData(1, 1, new byte[] { 110 })] - [InlineData(2, 1, new byte[] { 120 })] - [InlineData(3, 1, new byte[] { 100 })] - [InlineData(0, 2, new byte[] { 100, 110 })] - [InlineData(1, 2, new byte[] { 110, 120 })] - [InlineData(2, 2, new byte[] { 120, 100 })] - [InlineData(3, 2, new byte[] { 100, 110 })] - [InlineData(0, 3, new byte[] { 100, 110, 120 })] - [InlineData(1, 3, new byte[] { 110, 120, 100 })] - [InlineData(2, 3, new byte[] { 120, 100, 110 })] - [InlineData(3, 3, new byte[] { 100, 110, 120 })] - public void CanWrite(long offset, long length, byte[] bytes) + [Theory] + [InlineData(0, 0, new byte[] { })] + [InlineData(0, 1, new byte[] { 100 })] + [InlineData(1, 1, new byte[] { 110 })] + [InlineData(2, 1, new byte[] { 120 })] + [InlineData(3, 1, new byte[] { 100 })] + [InlineData(0, 2, new byte[] { 100, 110 })] + [InlineData(1, 2, new byte[] { 110, 120 })] + [InlineData(2, 2, new byte[] { 120, 100 })] + [InlineData(3, 2, new byte[] { 100, 110 })] + [InlineData(0, 3, new byte[] { 100, 110, 120 })] + [InlineData(1, 3, new byte[] { 110, 120, 100 })] + [InlineData(2, 3, new byte[] { 120, 100, 110 })] + [InlineData(3, 3, new byte[] { 100, 110, 120 })] + public void CanWrite(long offset, long length, byte[] bytes) + { + var b = new byte[3]; + fixed (byte* ptr = &b[0]) { - var b = new byte[3]; - fixed (byte* ptr = &b[0]) - { - var buffer = new CircularBuffer(ptr, b.Length); - buffer.Write(bytes, offset); - buffer.Read(offset, length).ToArray().Should().BeEquivalentTo(bytes); - } + var buffer = new CircularBuffer(ptr, b.Length); + buffer.Write(bytes, offset); + buffer.Read(offset, length).ToArray().Should().BeEquivalentTo(bytes); } + } - [Fact] - public void CanWriteStruct() + [Fact] + public void CanWriteStruct() + { + var b = new byte[sizeof(QueueHeader)]; + fixed (byte* ptr = &b[0]) { - var b = new byte[sizeof(QueueHeader)]; - fixed (byte* ptr = &b[0]) + var buffer = new CircularBuffer(ptr, b.Length); + var value = new QueueHeader { - var buffer = new CircularBuffer(ptr, b.Length); - var value = new QueueHeader { ReadOffset = 1, WriteOffset = 2, ReadLockTimestamp = long.MaxValue, Reserved = long.MinValue }; - buffer.Write(value, 0); - value.Should().BeEquivalentTo(*(QueueHeader*)ptr); - } + ReadOffset = 1, + WriteOffset = 2, + ReadLockTimestamp = long.MaxValue, + Reserved = long.MinValue + }; + buffer.Write(value, 0); + value.Should().BeEquivalentTo(*(QueueHeader*)ptr); } + } - [Theory] - [InlineData(0, 0)] - [InlineData(0, 1)] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(3, 1)] - [InlineData(0, 2)] - [InlineData(1, 2)] - [InlineData(2, 2)] - [InlineData(3, 2)] - [InlineData(0, 3)] - [InlineData(1, 3)] - [InlineData(2, 3)] - [InlineData(3, 3)] - public void CanZeroBlock(long offset, long length) + [Theory] + [InlineData(0, 0)] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(3, 1)] + [InlineData(0, 2)] + [InlineData(1, 2)] + [InlineData(2, 2)] + [InlineData(3, 2)] + [InlineData(0, 3)] + [InlineData(1, 3)] + [InlineData(2, 3)] + [InlineData(3, 3)] + public void CanZeroBlock(long offset, long length) + { + var b = new byte[3] { 1, 1, 1 }; + fixed (byte* ptr = &b[0]) { - var b = new byte[3] { 1, 1, 1 }; - fixed (byte* ptr = &b[0]) - { - var buffer = new CircularBuffer(ptr, b.Length); - buffer.Read(offset, length).ToArray().All(i => i == 1).Should().BeTrue(); - buffer.Clear(offset, length); - buffer.Read(offset, length).ToArray().All(i => i == 0).Should().BeTrue(); - } + var buffer = new CircularBuffer(ptr, b.Length); + buffer.Read(offset, length).ToArray().All(i => i == 1).Should().BeTrue(); + buffer.Clear(offset, length); + buffer.Read(offset, length).ToArray().All(i => i == 0).Should().BeTrue(); } } -} +} \ No newline at end of file diff --git a/src/Interprocess.Tests/Interprocess.Tests.csproj b/src/Interprocess.Tests/Interprocess.Tests.csproj index 48e4189..edc2ae5 100644 --- a/src/Interprocess.Tests/Interprocess.Tests.csproj +++ b/src/Interprocess.Tests/Interprocess.Tests.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/src/Interprocess.Tests/Properties/AssemblyInfo.cs b/src/Interprocess.Tests/Properties/AssemblyInfo.cs index 87ebd04..669ff39 100644 --- a/src/Interprocess.Tests/Properties/AssemblyInfo.cs +++ b/src/Interprocess.Tests/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Diagnostics.CodeAnalysis; -[assembly: ExcludeFromCodeCoverage] +[assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/src/Interprocess.Tests/QueueTests.cs b/src/Interprocess.Tests/QueueTests.cs index 1b90b02..9d6ebfb 100644 --- a/src/Interprocess.Tests/QueueTests.cs +++ b/src/Interprocess.Tests/QueueTests.cs @@ -1,251 +1,248 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace Cloudtoid.Interprocess.Tests -{ - public class QueueTests : IClassFixture - { - private static readonly byte[] ByteArray1 = new byte[] { 100, }; - private static readonly byte[] ByteArray2 = new byte[] { 100, 110 }; - private static readonly byte[] ByteArray3 = new byte[] { 100, 110, 120 }; - private static readonly byte[] ByteArray50 = Enumerable.Range(1, 50).Select(i => (byte)i).ToArray(); - private readonly UniquePathFixture fixture; - private readonly QueueFactory queueFactory; - - public QueueTests( - UniquePathFixture fixture, - ITestOutputHelper testOutputHelper) - { - this.fixture = fixture; - var loggerFactory = new LoggerFactory(); - loggerFactory.AddProvider(new XunitLoggerProvider(testOutputHelper)); - queueFactory = new QueueFactory(loggerFactory); - } - - [Fact] - [TestBeforeAfter] - public void Sample() - { - var message = new byte[] { 1, 2, 3 }; - var messageBuffer = new byte[3]; - CancellationToken cancellationToken = default; - - var factory = new QueueFactory(); - var options = new QueueOptions( - queueName: "my-queue", - capacity: 1024 * 1024); - - using var publisher = factory.CreatePublisher(options); - publisher.TryEnqueue(message); - - options = new QueueOptions( - queueName: "my-queue", - capacity: 1024 * 1024); - - using var subscriber = factory.CreateSubscriber(options); - subscriber.TryDequeue(messageBuffer, cancellationToken, out var msg); - - msg.ToArray().Should().BeEquivalentTo(message); - } - - [Fact] - [TestBeforeAfter] - public void DependencyInjectionSample() - { - var message = new byte[] { 1, 2, 3 }; - var messageBuffer = new byte[3]; - CancellationToken cancellationToken = default; - var services = new ServiceCollection(); - - services - .AddInterprocessQueue() // adding the queue related components - .AddLogging(); // optionally, we can enable logging - - var serviceProvider = services.BuildServiceProvider(); - var factory = serviceProvider.GetRequiredService(); - - var options = new QueueOptions( - queueName: "my-queue", - capacity: 1024 * 1024); - - using var publisher = factory.CreatePublisher(options); - publisher.TryEnqueue(message); - - options = new QueueOptions( - queueName: "my-queue", - capacity: 1024 * 1024); - - using var subscriber = factory.CreateSubscriber(options); - subscriber.TryDequeue(messageBuffer, cancellationToken, out var msg); - - msg.ToArray().Should().BeEquivalentTo(message); - } - - [Fact] - [TestBeforeAfter] - public void CanEnqueueAndDequeue() - { - using var p = CreatePublisher(24); - using var s = CreateSubscriber(24); - - p.TryEnqueue(ByteArray3).Should().BeTrue(); - var message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray3); - - p.TryEnqueue(ByteArray3).Should().BeTrue(); - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray3); - - p.TryEnqueue(ByteArray2).Should().BeTrue(); - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray2); - - p.TryEnqueue(ByteArray2).Should().BeTrue(); - message = s.Dequeue(new byte[5], default); - message.ToArray().Should().BeEquivalentTo(ByteArray2); - } - - [Fact] - [TestBeforeAfter] - public void CanEnqueueDequeueWrappedMessage() - { - using var p = CreatePublisher(128); - using var s = CreateSubscriber(128); - - p.TryEnqueue(ByteArray50).Should().BeTrue(); - var message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray50); - - p.TryEnqueue(ByteArray50).Should().BeTrue(); - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray50); - - p.TryEnqueue(ByteArray50).Should().BeTrue(); - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray50); - - p.TryEnqueue(ByteArray50).Should().BeTrue(); - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray50); - } - - [Fact] - [TestBeforeAfter] - public void CannotEnqueuePastCapacity() - { - using var p = CreatePublisher(24); - - p.TryEnqueue(ByteArray3).Should().BeTrue(); - p.TryEnqueue(ByteArray1).Should().BeFalse(); - } - - [Fact] - [TestBeforeAfter] - public void DisposeShouldNotThrow() - { - var p = CreatePublisher(24); - p.TryEnqueue(ByteArray3).Should().BeTrue(); - - using var s = CreateSubscriber(24); - p.Dispose(); - - s.Dequeue(default); - } - - [Fact] - [TestBeforeAfter] - public void CannotReadAfterProducerIsDisposed() - { - var p = CreatePublisher(24); - p.TryEnqueue(ByteArray3).Should().BeTrue(); - using (var s = CreateSubscriber(24)) - p.Dispose(); - - using (CreatePublisher(24)) - using (var s = CreateSubscriber(24)) - { - s.TryDequeue(default, out var message).Should().BeFalse(); - } - } - - [Theory] - [Repeat(10)] - [TestBeforeAfter] - [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The extra argument is needed by the Repeat attribute.")] - [SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters", Justification = "The extra argument is needed by the Repeat attribute.")] - public async Task CanDisposeQueueAsync(int i) - { - using (var s = CreateSubscriber(1024)) - { - _ = Task.Run(() => s.Dequeue(default)); - await Task.Delay(200); - } - } - - [Fact] - [TestBeforeAfter] - public void CanCircleBuffer() - { - using var p = CreatePublisher(1024); - using var s = CreateSubscriber(1024); - - var message = Enumerable.Range(100, 66).Select(i => (byte)i).ToArray(); - - for (var i = 0; i < 20000; i++) - { - p.TryEnqueue(message).Should().BeTrue(); - var result = s.Dequeue(default); - result.ToArray().Should().BeEquivalentTo(message); - } - } - - [Fact] - [TestBeforeAfter] - public void CanRejectLargeMessages() - { - using (var p = CreatePublisher(24)) - using (var s = CreateSubscriber(24)) - { - p.TryEnqueue(ByteArray3).Should().BeTrue(); - var message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray3); - - p.TryEnqueue(ByteArray3).Should().BeTrue(); - - // This should fail because the queue is out of capacity - p.TryEnqueue(ByteArray3).Should().BeFalse(); - - message = s.Dequeue(default); - message.ToArray().Should().BeEquivalentTo(ByteArray3); - - p.TryEnqueue(ByteArray3).Should().BeTrue(); - p.TryEnqueue(ByteArray3).Should().BeFalse(); - } - - using (var p = CreatePublisher(32)) - { - p.TryEnqueue(ByteArray3).Should().BeTrue(); - p.TryEnqueue(ByteArray3).Should().BeTrue(); - p.TryEnqueue(ByteArray3).Should().BeFalse(); - } - - using (var p = CreatePublisher(32)) - p.TryEnqueue(ByteArray50).Should().BeFalse(); // failed here - } - - private IPublisher CreatePublisher(long capacity) - => queueFactory.CreatePublisher( - new QueueOptions("qn", fixture.Path, capacity)); - - private ISubscriber CreateSubscriber(long capacity) - => queueFactory.CreateSubscriber( - new QueueOptions("qn", fixture.Path, capacity)); - } -} +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Cloudtoid.Interprocess.Tests; + +public class QueueTests : IClassFixture +{ + private static readonly byte[] ByteArray1 = [100,]; + private static readonly byte[] ByteArray2 = [100, 110]; + private static readonly byte[] ByteArray3 = [100, 110, 120]; + private static readonly byte[] ByteArray50 = Enumerable.Range(1, 50).Select(i => (byte)i).ToArray(); + private readonly UniquePathFixture fixture; + private readonly QueueFactory queueFactory; + + public QueueTests( + UniquePathFixture fixture, + ITestOutputHelper testOutputHelper) + { + this.fixture = fixture; +#pragma warning disable CA2000 // Dispose objects before losing scope + var loggerFactory = new LoggerFactory(); + loggerFactory.AddProvider(new XunitLoggerProvider(testOutputHelper)); +#pragma warning restore CA2000 // Dispose objects before losing scope + queueFactory = new QueueFactory(loggerFactory); + } + + [Fact] + [TestBeforeAfter] + public void Sample() + { + var message = new byte[] { 1, 2, 3 }; + var messageBuffer = new byte[3]; + CancellationToken cancellationToken = default; + + var factory = new QueueFactory(); + var options = new QueueOptions( + queueName: "my-queue", + capacity: 1024 * 1024); + + using var publisher = factory.CreatePublisher(options); + publisher.TryEnqueue(message); + + options = new QueueOptions( + queueName: "my-queue", + capacity: 1024 * 1024); + + using var subscriber = factory.CreateSubscriber(options); + subscriber.TryDequeue(messageBuffer, cancellationToken, out var msg); + + msg.ToArray().Should().BeEquivalentTo(message); + } + + [Fact] + [TestBeforeAfter] + public void DependencyInjectionSample() + { + var message = new byte[] { 1, 2, 3 }; + var messageBuffer = new byte[3]; + CancellationToken cancellationToken = default; + var services = new ServiceCollection(); + + services + .AddInterprocessQueue() // adding the queue related components + .AddLogging(); // optionally, we can enable logging + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + var options = new QueueOptions( + queueName: "my-queue", + capacity: 1024 * 1024); + + using var publisher = factory.CreatePublisher(options); + publisher.TryEnqueue(message); + + options = new QueueOptions( + queueName: "my-queue", + capacity: 1024 * 1024); + + using var subscriber = factory.CreateSubscriber(options); + subscriber.TryDequeue(messageBuffer, cancellationToken, out var msg); + + msg.ToArray().Should().BeEquivalentTo(message); + } + + [Fact] + [TestBeforeAfter] + public void CanEnqueueAndDequeue() + { + using var p = CreatePublisher(24); + using var s = CreateSubscriber(24); + + p.TryEnqueue(ByteArray3).Should().BeTrue(); + var message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray3); + + p.TryEnqueue(ByteArray3).Should().BeTrue(); + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray3); + + p.TryEnqueue(ByteArray2).Should().BeTrue(); + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray2); + + p.TryEnqueue(ByteArray2).Should().BeTrue(); + message = s.Dequeue(new byte[5], default); + message.ToArray().Should().BeEquivalentTo(ByteArray2); + } + + [Fact] + [TestBeforeAfter] + public void CanEnqueueDequeueWrappedMessage() + { + using var p = CreatePublisher(128); + using var s = CreateSubscriber(128); + + p.TryEnqueue(ByteArray50).Should().BeTrue(); + var message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray50); + + p.TryEnqueue(ByteArray50).Should().BeTrue(); + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray50); + + p.TryEnqueue(ByteArray50).Should().BeTrue(); + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray50); + + p.TryEnqueue(ByteArray50).Should().BeTrue(); + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray50); + } + + [Fact] + [TestBeforeAfter] + public void CannotEnqueuePastCapacity() + { + using var p = CreatePublisher(24); + + p.TryEnqueue(ByteArray3).Should().BeTrue(); + p.TryEnqueue(ByteArray1).Should().BeFalse(); + } + + [Fact] + [TestBeforeAfter] + public void DisposeShouldNotThrow() + { + var p = CreatePublisher(24); + p.TryEnqueue(ByteArray3).Should().BeTrue(); + + using var s = CreateSubscriber(24); + p.Dispose(); + + s.Dequeue(default); + } + + [Fact] + [TestBeforeAfter] + public void CannotReadAfterProducerIsDisposed() + { + var p = CreatePublisher(24); + p.TryEnqueue(ByteArray3).Should().BeTrue(); + using (var s = CreateSubscriber(24)) + p.Dispose(); + + using (CreatePublisher(24)) + using (var s = CreateSubscriber(24)) + s.TryDequeue(default, out var message).Should().BeFalse(); + } + + [Theory] + [Repeat(10)] + [TestBeforeAfter] +#pragma warning disable RCS1163 // Unused parameter +#pragma warning disable IDE0060 // Remove unused parameter +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public async Task CanDisposeQueueAsync(int i) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters +#pragma warning restore IDE0060 // Remove unused parameter +#pragma warning restore RCS1163 // Unused parameter + { + using var s = CreateSubscriber(1024); + _ = Task.Run(() => s.Dequeue(default)); + await Task.Delay(200); + } + + [Fact] + [TestBeforeAfter] + public void CanCircleBuffer() + { + using var p = CreatePublisher(1024); + using var s = CreateSubscriber(1024); + + var message = Enumerable.Range(100, 66).Select(i => (byte)i).ToArray(); + + for (var i = 0; i < 20000; i++) + { + p.TryEnqueue(message).Should().BeTrue(); + var result = s.Dequeue(default); + result.ToArray().Should().BeEquivalentTo(message); + } + } + + [Fact] + [TestBeforeAfter] + public void CanRejectLargeMessages() + { + using (var p = CreatePublisher(24)) + using (var s = CreateSubscriber(24)) + { + p.TryEnqueue(ByteArray3).Should().BeTrue(); + var message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray3); + + p.TryEnqueue(ByteArray3).Should().BeTrue(); + + // This should fail because the queue is out of capacity + p.TryEnqueue(ByteArray3).Should().BeFalse(); + + message = s.Dequeue(default); + message.ToArray().Should().BeEquivalentTo(ByteArray3); + + p.TryEnqueue(ByteArray3).Should().BeTrue(); + p.TryEnqueue(ByteArray3).Should().BeFalse(); + } + + using (var p = CreatePublisher(32)) + { + p.TryEnqueue(ByteArray3).Should().BeTrue(); + p.TryEnqueue(ByteArray3).Should().BeTrue(); + p.TryEnqueue(ByteArray3).Should().BeFalse(); + } + + using (var p = CreatePublisher(32)) + p.TryEnqueue(ByteArray50).Should().BeFalse(); // failed here + } + + private IPublisher CreatePublisher(long capacity) => + queueFactory.CreatePublisher( + new QueueOptions("qn", fixture.Path, capacity)); + + private ISubscriber CreateSubscriber(long capacity) => + queueFactory.CreateSubscriber( + new QueueOptions("qn", fixture.Path, capacity)); +} \ No newline at end of file diff --git a/src/Interprocess.Tests/SemaphoreTests.cs b/src/Interprocess.Tests/SemaphoreTests.cs index b1426ad..4c6ecbe 100644 --- a/src/Interprocess.Tests/SemaphoreTests.cs +++ b/src/Interprocess.Tests/SemaphoreTests.cs @@ -2,122 +2,121 @@ using Cloudtoid.Interprocess.Semaphore.MacOS; using FluentAssertions; -namespace Cloudtoid.Interprocess.Tests +namespace Cloudtoid.Interprocess.Tests; + +public class SemaphoreTests { - public class SemaphoreTests + [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] + [TestBeforeAfter] + public void CanReleaseAndWaitLinux() + { + using var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true); + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Wait(10).Should().BeTrue(); + sem.Wait(0).Should().BeFalse(); + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Wait(10).Should().BeTrue(); + } + + [Fact(Platforms = Platform.OSX)] + [TestBeforeAfter] + public void CanReleaseAndWaitMacOS() + { + using var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true); + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Wait(10).Should().BeTrue(); + sem.Wait(0).Should().BeFalse(); + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Wait(10).Should().BeTrue(); + } + + [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] + [TestBeforeAfter] + public void CanCreateMultipleSemaphoresWithSameNameLinux() + { + using var sem1 = new SemaphoreLinux("my-sem", deleteOnDispose: true); + using var sem2 = new SemaphoreLinux("my-sem", deleteOnDispose: false); + sem2.Release(); + sem1.Wait(10).Should().BeTrue(); + sem1.Wait(10).Should().BeFalse(); + sem2.Wait(10).Should().BeFalse(); + } + + [Fact(Platforms = Platform.OSX)] + [TestBeforeAfter] + public void CanCreateMultipleSemaphoresWithSameNameMacOS() + { + using var sem1 = new SemaphoreMacOS("my-sem", deleteOnDispose: true); + using var sem2 = new SemaphoreMacOS("my-sem", deleteOnDispose: false); + sem2.Release(); + sem1.Wait(10).Should().BeTrue(); + sem1.Wait(10).Should().BeFalse(); + sem2.Wait(10).Should().BeFalse(); + } + + [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] + [TestBeforeAfter] + public void CanReuseSameSemaphoreNameLinux() { - [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] - [TestBeforeAfter] - public void CanReleaseAndWaitLinux() + using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true)) { - using var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true); sem.Wait(10).Should().BeFalse(); sem.Release(); - sem.Release(); sem.Wait(-1).Should().BeTrue(); - sem.Wait(10).Should().BeTrue(); - sem.Wait(0).Should().BeFalse(); - sem.Wait(10).Should().BeFalse(); sem.Release(); - sem.Wait(10).Should().BeTrue(); } - [Fact(Platforms = Platform.OSX)] - [TestBeforeAfter] - public void CanReleaseAndWaitMacOS() + using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: false)) { - using var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true); sem.Wait(10).Should().BeFalse(); sem.Release(); - sem.Release(); sem.Wait(-1).Should().BeTrue(); - sem.Wait(10).Should().BeTrue(); - sem.Wait(0).Should().BeFalse(); - sem.Wait(10).Should().BeFalse(); sem.Release(); - sem.Wait(10).Should().BeTrue(); } - [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] - [TestBeforeAfter] - public void CanCreateMultipleSemaphoresWithSameNameLinux() + using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true)) { - using var sem1 = new SemaphoreLinux("my-sem", deleteOnDispose: true); - using var sem2 = new SemaphoreLinux("my-sem", deleteOnDispose: false); - sem2.Release(); - sem1.Wait(10).Should().BeTrue(); - sem1.Wait(10).Should().BeFalse(); - sem2.Wait(10).Should().BeFalse(); + sem.Wait(10).Should().BeTrue(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Release(); } + } - [Fact(Platforms = Platform.OSX)] - [TestBeforeAfter] - public void CanCreateMultipleSemaphoresWithSameNameMacOS() + [Fact(Platforms = Platform.OSX)] + [TestBeforeAfter] + public void CanReuseSameSemaphoreNameMacOS() + { + using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true)) { - using var sem1 = new SemaphoreMacOS("my-sem", deleteOnDispose: true); - using var sem2 = new SemaphoreMacOS("my-sem", deleteOnDispose: false); - sem2.Release(); - sem1.Wait(10).Should().BeTrue(); - sem1.Wait(10).Should().BeFalse(); - sem2.Wait(10).Should().BeFalse(); + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Release(); } - [Fact(Platforms = Platform.Linux | Platform.FreeBSD)] - [TestBeforeAfter] - public void CanReuseSameSemaphoreNameLinux() + using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: false)) { - using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true)) - { - sem.Wait(10).Should().BeFalse(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } - - using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: false)) - { - sem.Wait(10).Should().BeFalse(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } - - using (var sem = new SemaphoreLinux("my-sem", deleteOnDispose: true)) - { - sem.Wait(10).Should().BeTrue(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } + sem.Wait(10).Should().BeFalse(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Release(); } - [Fact(Platforms = Platform.OSX)] - [TestBeforeAfter] - public void CanReuseSameSemaphoreNameMacOS() + using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true)) { - using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true)) - { - sem.Wait(10).Should().BeFalse(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } - - using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: false)) - { - sem.Wait(10).Should().BeFalse(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } - - using (var sem = new SemaphoreMacOS("my-sem", deleteOnDispose: true)) - { - sem.Wait(10).Should().BeTrue(); - sem.Release(); - sem.Wait(-1).Should().BeTrue(); - sem.Release(); - } + sem.Wait(10).Should().BeTrue(); + sem.Release(); + sem.Wait(-1).Should().BeTrue(); + sem.Release(); } } -} +} \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/FactAttribute.cs b/src/Interprocess.Tests/Utils/FactAttribute.cs index 46d6db7..3f602fc 100644 --- a/src/Interprocess.Tests/Utils/FactAttribute.cs +++ b/src/Interprocess.Tests/Utils/FactAttribute.cs @@ -1,46 +1,45 @@ using System.Runtime.InteropServices; -namespace Cloudtoid.Interprocess.Tests +namespace Cloudtoid.Interprocess.Tests; + +public sealed class FactAttribute : Xunit.FactAttribute { - public class FactAttribute : Xunit.FactAttribute - { - private static readonly Platform? CurrentPlatform = GetPlatform(); + private static readonly Platform? CurrentPlatform = GetPlatform(); - /// - /// Gets or sets the supported OS Platforms - /// - public Platform Platforms { get; set; } = Platform.All; + /// + /// Gets or sets the supported OS Platforms + /// + public Platform Platforms { get; set; } = Platform.All; - public override string? Skip + public override string? Skip + { + get { - get - { - if (base.Skip != null || CurrentPlatform is null) - return base.Skip; + if (base.Skip is not null || CurrentPlatform is null) + return base.Skip; - if ((Platforms & CurrentPlatform) == 0) - return $"Skipped on {CurrentPlatform}"; + if ((Platforms & CurrentPlatform) == 0) + return $"Skipped on {CurrentPlatform}"; - return null; - } - set => base.Skip = value; + return null; } + set => base.Skip = value; + } - private static Platform? GetPlatform() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return Platform.Windows; + private static Platform? GetPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Platform.Windows; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return Platform.Linux; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return Platform.Linux; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return Platform.OSX; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return Platform.OSX; - if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) - return Platform.FreeBSD; + if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + return Platform.FreeBSD; - return null; - } + return null; } } \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/Platform.cs b/src/Interprocess.Tests/Utils/Platform.cs index 2978c47..753d3db 100644 --- a/src/Interprocess.Tests/Utils/Platform.cs +++ b/src/Interprocess.Tests/Utils/Platform.cs @@ -1,16 +1,13 @@ -using System; +namespace Cloudtoid.Interprocess.Tests; -namespace Cloudtoid.Interprocess.Tests +[Flags] +public enum Platform { - [Flags] - public enum Platform - { - Windows = 0x01, - Linux = 0x02, - OSX = 0x04, - FreeBSD = 0x08, + Windows = 0x01, + Linux = 0x02, + OSX = 0x04, + FreeBSD = 0x08, - UnixBased = Linux | OSX | FreeBSD, - All = Windows | Linux | OSX | FreeBSD - } + UnixBased = Linux | OSX | FreeBSD, + All = Windows | Linux | OSX | FreeBSD } \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/RepeatAttribute.cs b/src/Interprocess.Tests/Utils/RepeatAttribute.cs index a36f63d..99383ce 100644 --- a/src/Interprocess.Tests/Utils/RepeatAttribute.cs +++ b/src/Interprocess.Tests/Utils/RepeatAttribute.cs @@ -1,24 +1,13 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; using Xunit.Sdk; -namespace Cloudtoid.Interprocess.Tests -{ - public sealed class RepeatAttribute : DataAttribute - { - private readonly int count; +namespace Cloudtoid.Interprocess.Tests; - public RepeatAttribute(int count) - { - this.count = count; - } +public sealed class RepeatAttribute(int count) : DataAttribute +{ + public int Count { get; } = count; - public override IEnumerable GetData(MethodInfo testMethod) - { - return Enumerable - .Range(1, count) - .Select(i => new object[] { i }); - } - } -} + public override IEnumerable GetData(MethodInfo testMethod) => Enumerable + .Range(1, Count) + .Select(i => new object[] { i }); +} \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs b/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs index 29240e6..2220f7a 100644 --- a/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs +++ b/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs @@ -1,19 +1,13 @@ -using System; -using System.Reflection; +using System.Reflection; using Xunit.Sdk; -namespace Cloudtoid.Interprocess.Tests +namespace Cloudtoid.Interprocess.Tests; + +public sealed class TestBeforeAfterAttribute : BeforeAfterTestAttribute { - public class TestBeforeAfterAttribute : BeforeAfterTestAttribute - { - public override void Before(MethodInfo methodUnderTest) - { - Console.WriteLine("Before - " + methodUnderTest.Name); - } + public override void Before(MethodInfo methodUnderTest) => + Console.WriteLine($"Before - {methodUnderTest.Name}"); - public override void After(MethodInfo methodUnderTest) - { - Console.WriteLine("After - " + methodUnderTest.Name); - } - } + public override void After(MethodInfo methodUnderTest) => + Console.WriteLine($"After - {methodUnderTest.Name}"); } \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/UniquePathFixture.cs b/src/Interprocess.Tests/Utils/UniquePathFixture.cs index 3841e2c..9657d8f 100644 --- a/src/Interprocess.Tests/Utils/UniquePathFixture.cs +++ b/src/Interprocess.Tests/Utils/UniquePathFixture.cs @@ -1,38 +1,36 @@ -using System; -using System.IO; +namespace Cloudtoid.Interprocess.Tests; -namespace Cloudtoid.Interprocess.Tests +public class UniquePathFixture : IDisposable { - public class UniquePathFixture : IDisposable - { - private static readonly string Root = System.IO.Path.GetTempPath(); + private static readonly string Root = System.IO.Path.GetTempPath(); - public UniquePathFixture() + public UniquePathFixture() + { + while (true) { - while (true) + var folder = (DateTime.UtcNow.Ticks % 0xFFFFF).ToStringInvariant("X5"); + Path = System.IO.Path.Combine(Root, folder); + if (!Directory.Exists(Path)) { - var folder = (DateTime.UtcNow.Ticks % 0xFFFFF).ToStringInvariant("X5"); - Path = System.IO.Path.Combine(Root, folder); - if (!Directory.Exists(Path)) - { - Directory.CreateDirectory(Path); - break; - } + Directory.CreateDirectory(Path); + break; } } + } - internal string Path { get; } + internal string Path { get; } - public void Dispose() - { - foreach (var file in Directory.EnumerateFiles(Path)) - PathUtil.TryDeleteFile(file); + public void Dispose() + { + foreach (var file in Directory.EnumerateFiles(Path)) + PathUtil.TryDeleteFile(file); - try - { - Directory.Delete(Path); - } - catch { } + try + { + Directory.Delete(Path); + } + catch + { } } -} +} \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/XunitLogger.cs b/src/Interprocess.Tests/Utils/XunitLogger.cs index 2efaf9c..f197306 100644 --- a/src/Interprocess.Tests/Utils/XunitLogger.cs +++ b/src/Interprocess.Tests/Utils/XunitLogger.cs @@ -1,62 +1,56 @@ -using System; -using System.IO; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; -namespace Cloudtoid.Interprocess.Tests +namespace Cloudtoid.Interprocess.Tests; + +public class XunitLogger(ITestOutputHelper testOutputHelper, string categoryName, string? fileName) : ILogger { - public class XunitLogger : ILogger + public IDisposable BeginScope(TState state) + where TState : notnull => + NoopDisposable.Instance; + + public bool IsEnabled(LogLevel logLevel) => + true; + + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) { - private readonly ITestOutputHelper testOutputHelper; - private readonly string categoryName; - private readonly string? fileName; - - public XunitLogger(ITestOutputHelper testOutputHelper, string categoryName, string? fileName) - { - this.testOutputHelper = testOutputHelper; - this.categoryName = categoryName; - this.fileName = fileName; - } - - public IDisposable BeginScope(TState state) where TState : notnull - => NoopDisposable.Instance; + var message = $"{categoryName} [{eventId}] {formatter(state, exception)}"; + testOutputHelper.WriteLine(message); + if (exception is not null) + testOutputHelper.WriteLine(exception.ToString()); - public bool IsEnabled(LogLevel logLevel) - => true; + LogToFile(message, exception); + } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - var message = $"{categoryName} [{eventId}] {formatter(state, exception)}"; - testOutputHelper.WriteLine(message); - if (exception != null) - testOutputHelper.WriteLine(exception.ToString()); + private void LogToFile(string message, Exception? exception) + { + if (fileName is null) + return; - LogToFile(message, exception); - } + if (exception is not null) + message += Environment.NewLine + exception; - private void LogToFile(string message, Exception? exception) + while (true) { - if (fileName is null) - return; - - if (exception != null) - message += Environment.NewLine + exception.ToString(); - - while (true) + try + { + File.AppendAllText(fileName, message); + break; + } + catch { - try - { - File.AppendAllText(fileName, message); - break; - } - catch { } } } + } - private sealed class NoopDisposable : IDisposable - { - public static readonly NoopDisposable Instance = new(); - public void Dispose() { } - } + private sealed class NoopDisposable : IDisposable + { + public static readonly NoopDisposable Instance = new(); + public void Dispose() { } } -} +} \ No newline at end of file diff --git a/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs b/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs index 566d5f0..7c28b84 100644 --- a/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs +++ b/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs @@ -1,22 +1,12 @@ using Microsoft.Extensions.Logging; using Xunit.Abstractions; -namespace Cloudtoid.Interprocess.Tests -{ - public class XunitLoggerProvider : ILoggerProvider - { - private readonly ITestOutputHelper testOutputHelper; - private readonly string? fileName; - - public XunitLoggerProvider(ITestOutputHelper testOutputHelper, string? fileName = null) - { - this.testOutputHelper = testOutputHelper; - this.fileName = fileName; - } +namespace Cloudtoid.Interprocess.Tests; - public ILogger CreateLogger(string categoryName) - => new XunitLogger(testOutputHelper, fileName ?? categoryName, fileName); +public class XunitLoggerProvider(ITestOutputHelper testOutputHelper, string? fileName = null) : ILoggerProvider +{ + public ILogger CreateLogger(string categoryName) => + new XunitLogger(testOutputHelper, fileName ?? categoryName, fileName); - public void Dispose() { } - } -} + public void Dispose() { } +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/IPublisher.cs b/src/Interprocess/Contracts/IPublisher.cs index 9709412..1b5da59 100644 --- a/src/Interprocess/Contracts/IPublisher.cs +++ b/src/Interprocess/Contracts/IPublisher.cs @@ -1,9 +1,10 @@ -using System; +namespace Cloudtoid.Interprocess; -namespace Cloudtoid.Interprocess +/// +/// Message publisher that publishes messages to the subscribers. +/// +public interface IPublisher : IDisposable { - public interface IPublisher : IDisposable - { - bool TryEnqueue(ReadOnlySpan message); - } -} + /// Enqueues the message to be published to the subscribers. + bool TryEnqueue(ReadOnlySpan message); +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/IQueueFactory.cs b/src/Interprocess/Contracts/IQueueFactory.cs index 91cfe6c..3b37007 100644 --- a/src/Interprocess/Contracts/IQueueFactory.cs +++ b/src/Interprocess/Contracts/IQueueFactory.cs @@ -1,8 +1,11 @@ -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +/// Factory to create queue publishers and subscribers. +public interface IQueueFactory { - public interface IQueueFactory - { - IPublisher CreatePublisher(QueueOptions options); - ISubscriber CreateSubscriber(QueueOptions options); - } -} + /// Creates a queue message publisher. + IPublisher CreatePublisher(QueueOptions options); + + /// Creates a queue message subscriber. + ISubscriber CreateSubscriber(QueueOptions options); +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/ISubscriber.cs b/src/Interprocess/Contracts/ISubscriber.cs index 9ea25ca..b06f013 100644 --- a/src/Interprocess/Contracts/ISubscriber.cs +++ b/src/Interprocess/Contracts/ISubscriber.cs @@ -1,71 +1,71 @@ -using System; -using System.Buffers; -using System.Threading; +using System.Buffers; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +/// +/// Message subscriber that subscribes to the messages published by the publisher. +/// +public interface ISubscriber : IDisposable { - public interface ISubscriber : IDisposable - { - /// - /// Dequeues a message from the queue if the queue is not empty. This is a non-blocking - /// call and returns immediately. - /// This overload allocates a array the size of the message in the - /// queue and copies the message from the shared memory to it. To avoid this memory - /// allocation, consider reusing a previously allocated array with - /// . - /// can be a good way of pooling and - /// reusing byte arrays. - /// - /// A cancellation token to observe while waiting for the task to complete. - /// The dequeued message. - /// Returns if the queue is empty. - bool TryDequeue( - CancellationToken cancellation, - out ReadOnlyMemory message); + /// + /// Dequeues a message from the queue if the queue is not empty. This is a non-blocking + /// call and returns immediately. + /// This overload allocates a array the size of the message in the + /// queue and copies the message from the shared memory to it. To avoid this memory + /// allocation, consider reusing a previously allocated array with + /// . + /// can be a good way of pooling and + /// reusing byte arrays. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// The dequeued message. + /// Returns if the queue is empty. + bool TryDequeue( + CancellationToken cancellation, + out ReadOnlyMemory message); - /// - /// Dequeues a message from the queue if the queue is not empty. This is a non-blocking - /// call and returns immediately. This method does not allocated memory and only populates - /// the that is passed in. Make sure that the buffer is large - /// enough to receive the entire message, or the message is truncated to fit the buffer. - /// - /// The memory buffer that is populated with the message. Make sure - /// that the buffer is large enough to receive the entire message, or the message is - /// truncated to fit the buffer. - /// A cancellation token to observe while waiting for the task to complete. - /// The dequeued message. - /// Returns if the queue is empty. - bool TryDequeue( - Memory buffer, - CancellationToken cancellation, - out ReadOnlyMemory message); + /// + /// Dequeues a message from the queue if the queue is not empty. This is a non-blocking + /// call and returns immediately. This method does not allocated memory and only populates + /// the that is passed in. Make sure that the buffer is large + /// enough to receive the entire message, or the message is truncated to fit the buffer. + /// + /// The memory buffer that is populated with the message. Make sure + /// that the buffer is large enough to receive the entire message, or the message is + /// truncated to fit the buffer. + /// A cancellation token to observe while waiting for the task to complete. + /// The dequeued message. + /// Returns if the queue is empty. + bool TryDequeue( + Memory buffer, + CancellationToken cancellation, + out ReadOnlyMemory message); - /// - /// Dequeues a message from the queue. If the queue is empty, it *waits* for the - /// arrival of a new message. This call is blocking until a message is received. - /// This overload allocates a array the size of the message in the - /// queue and copies the message from the shared memory to it. To avoid this memory - /// allocation, consider reusing a previously allocated array with - /// . - /// can be a good way of pooling and - /// reusing byte arrays. - /// - /// A cancellation token to observe while waiting for the task to complete. - ReadOnlyMemory Dequeue(CancellationToken cancellation); + /// + /// Dequeues a message from the queue. If the queue is empty, it *waits* for the + /// arrival of a new message. This call is blocking until a message is received. + /// This overload allocates a array the size of the message in the + /// queue and copies the message from the shared memory to it. To avoid this memory + /// allocation, consider reusing a previously allocated array with + /// . + /// can be a good way of pooling and + /// reusing byte arrays. + /// + /// A cancellation token to observe while waiting for the task to complete. + ReadOnlyMemory Dequeue(CancellationToken cancellation); - /// - /// Dequeues a message from the queue. If the queue is empty, it *waits* for the - /// arrival of a new message. This call is blocking until a message is received. - /// This method does not allocated memory and only populates - /// the that is passed in. Make sure that the buffer is large - /// enough to receive the entire message, or the message is truncated to fit the buffer. - /// - /// The memory buffer that is populated with the message. Make sure - /// that the buffer is large enough to receive the entire message, or the message is - /// truncated to fit the buffer. - /// A cancellation token to observe while waiting for the task to complete. - ReadOnlyMemory Dequeue( - Memory buffer, - CancellationToken cancellation); - } -} + /// + /// Dequeues a message from the queue. If the queue is empty, it *waits* for the + /// arrival of a new message. This call is blocking until a message is received. + /// This method does not allocated memory and only populates + /// the that is passed in. Make sure that the buffer is large + /// enough to receive the entire message, or the message is truncated to fit the buffer. + /// + /// The memory buffer that is populated with the message. Make sure + /// that the buffer is large enough to receive the entire message, or the message is + /// truncated to fit the buffer. + /// A cancellation token to observe while waiting for the task to complete. + ReadOnlyMemory Dequeue( + Memory buffer, + CancellationToken cancellation); +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/MessageHeader.cs b/src/Interprocess/Contracts/MessageHeader.cs index 57b7f33..c692cba 100644 --- a/src/Interprocess/Contracts/MessageHeader.cs +++ b/src/Interprocess/Contracts/MessageHeader.cs @@ -1,26 +1,25 @@ using System.Runtime.InteropServices; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +// We rely on this structure to fit in 64 bits. +// If you change the size of this, no longer many of the assumptions +// taken in this code are going to be valid. +[StructLayout(LayoutKind.Explicit, Size = 8)] +internal struct MessageHeader { - // We rely on this structure to fit in 64 bits. - // If you change the size of this, no longer many of the assumptions - // taken in this code are going to be valid. - [StructLayout(LayoutKind.Explicit, Size = 8)] - internal struct MessageHeader - { - internal const int LockedToBeConsumedState = 1; - internal const int ReadyToBeConsumedState = 2; + internal const int LockedToBeConsumedState = 1; + internal const int ReadyToBeConsumedState = 2; - [FieldOffset(0)] - internal int State; + [FieldOffset(0)] + internal int State; - [FieldOffset(4)] - internal int BodyLength; + [FieldOffset(4)] + internal int BodyLength; - internal MessageHeader(int state, int bodyLength) - { - State = state; - BodyLength = bodyLength; - } + internal MessageHeader(int state, int bodyLength) + { + State = state; + BodyLength = bodyLength; } -} +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/QueueHeader.cs b/src/Interprocess/Contracts/QueueHeader.cs index a2911e5..2d64f38 100644 --- a/src/Interprocess/Contracts/QueueHeader.cs +++ b/src/Interprocess/Contracts/QueueHeader.cs @@ -1,35 +1,34 @@ using System.Runtime.InteropServices; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +[StructLayout(LayoutKind.Explicit, Size = 32)] +internal struct QueueHeader { - [StructLayout(LayoutKind.Explicit, Size = 32)] - internal struct QueueHeader - { - /// - /// Where the next message could potentially be read - /// - [FieldOffset(0)] - internal long ReadOffset; + /// + /// Where the next message could potentially be read + /// + [FieldOffset(0)] + internal long ReadOffset; - /// - /// Where the next message could potentially be written - /// - [FieldOffset(8)] - internal long WriteOffset; + /// + /// Where the next message could potentially be written + /// + [FieldOffset(8)] + internal long WriteOffset; - /// - /// Time (tiks) at which the read lock was taken. It is set to zero if not lock - /// - [FieldOffset(16)] - internal long ReadLockTimestamp; + /// + /// Time (ticks) at which the read lock was taken. It is set to zero if not lock + /// + [FieldOffset(16)] + internal long ReadLockTimestamp; - /// - /// Not used and might be used in the future - /// - [FieldOffset(24)] - internal long Reserved; + /// + /// Not used and might be used in the future + /// + [FieldOffset(24)] + internal long Reserved; - internal bool IsEmpty() - => ReadOffset == WriteOffset; - } -} + internal readonly bool IsEmpty() => + ReadOffset == WriteOffset; +} \ No newline at end of file diff --git a/src/Interprocess/Contracts/QueueOptions.cs b/src/Interprocess/Contracts/QueueOptions.cs index cb12cdc..4f0d143 100644 --- a/src/Interprocess/Contracts/QueueOptions.cs +++ b/src/Interprocess/Contracts/QueueOptions.cs @@ -1,54 +1,56 @@ using static Cloudtoid.Contract; using SysPath = System.IO.Path; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +/// The options to create a queue. +public sealed class QueueOptions { - public sealed class QueueOptions + /// + /// Initializes a new instance of the class. + /// + /// The unique name of the queue. + /// The maximum capacity of the queue in bytes. This should be at least 16 bytes long and in the multiples of 8 + public QueueOptions(string queueName, long capacity) + : this(queueName, SysPath.GetTempPath(), capacity) { - /// - /// Initializes a new instance of the class. - /// - /// The unique name of the queue. - /// The maximum capacity of the queue in bytes. This should be at least 16 bytes long and in the multiples of 8 - public QueueOptions(string queueName, long capacity) - : this(queueName, SysPath.GetTempPath(), capacity) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The unique name of the queue. - /// The path to the directory/folder in which the memory mapped and other files are stored in - /// The maximum capacity of the queue in bytes. This should be at least 16 bytes long and in the multiples of 8 - public unsafe QueueOptions(string queueName, string path, long capacity) - { - QueueName = CheckNonEmpty(queueName, nameof(queueName)); - Path = CheckValue(path, nameof(path)); - - Capacity = CheckGreaterThan(capacity, 16, nameof(capacity)); - CheckParam((capacity % 8) == 0, nameof(queueName), "messageCapacityInBytes should be a multiple of 8 (8 bytes = 64 bits)."); - } - - /// - /// Gets the unique name of the queue. - /// - public string QueueName { get; } - - /// - /// Gets the path to the directory/folder in which the memory mapped and other files are stored in. - /// - public string Path { get; } - - /// - /// Gets the size of the queue in bytes. This does NOT include the space needed for the queue header. - /// - public long Capacity { get; } - - /// - /// Gets the full size of the queue that includes both the header and message sections - /// - internal unsafe long GetQueueStorageSize() - => sizeof(QueueHeader) + Capacity; } -} + + /// + /// Initializes a new instance of the class. + /// + /// The unique name of the queue. + /// The path to the directory/folder in which the memory mapped and other files are stored in + /// The maximum capacity of the queue in bytes. This should be at least 16 bytes long and in the multiples of 8 + public unsafe QueueOptions(string queueName, string path, long capacity) + { + QueueName = CheckNonEmpty(queueName, nameof(queueName)); + Path = CheckValue(path, nameof(path)); + + Capacity = CheckGreaterThan(capacity, 16, nameof(capacity)); + CheckParam( + (capacity % 8) == 0, + nameof(queueName), + "messageCapacityInBytes should be a multiple of 8 (8 bytes = 64 bits)."); + } + + /// + /// Gets the unique name of the queue. + /// + public string QueueName { get; } + + /// + /// Gets the path to the directory/folder in which the memory mapped and other files are stored in. + /// + public string Path { get; } + + /// + /// Gets the size of the queue in bytes. This does NOT include the space needed for the queue header. + /// + public long Capacity { get; } + + /// + /// Gets the full size of the queue that includes both the header and message sections + /// + internal unsafe long GetQueueStorageSize() => sizeof(QueueHeader) + Capacity; +} \ No newline at end of file diff --git a/src/Interprocess/DependencyInjection.cs b/src/Interprocess/DependencyInjection.cs deleted file mode 100644 index 5c8810c..0000000 --- a/src/Interprocess/DependencyInjection.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using static Cloudtoid.Contract; - -namespace Cloudtoid.Interprocess -{ - public static class DependencyInjection - { - /// - /// Registers what is needed to create and consume shared-memory queues that are - /// cross-process accessible. - /// Use to access the queue. - /// - public static IServiceCollection AddInterprocessQueue(this IServiceCollection services) - { - CheckValue(services, nameof(services)); - - Util.Ensure64Bit(); - services.TryAddSingleton(); - return services; - } - } -} diff --git a/src/Interprocess/Log.cs b/src/Interprocess/Log.cs new file mode 100644 index 0000000..6f8bed4 --- /dev/null +++ b/src/Interprocess/Log.cs @@ -0,0 +1,13 @@ +using Cloudtoid.Interprocess.Memory.Unix; +using Microsoft.Extensions.Logging; + +namespace Cloudtoid.Interprocess; + +internal static partial class Log +{ + [LoggerMessage( + Level = LogLevel.Error, + Message = "Failed to delete queue's shared memory backing file even though it is not in use by any process.")] + internal static partial void FailedToDeleteSharedMemoryFile( + this ILogger logger); +} \ No newline at end of file diff --git a/src/Interprocess/Memory/CircularBuffer.cs b/src/Interprocess/Memory/CircularBuffer.cs index adda2dc..5dded88 100644 --- a/src/Interprocess/Memory/CircularBuffer.cs +++ b/src/Interprocess/Memory/CircularBuffer.cs @@ -1,106 +1,102 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Cloudtoid.Interprocess -{ - internal unsafe sealed class CircularBuffer - { - private readonly byte* buffer; +namespace Cloudtoid.Interprocess; - internal CircularBuffer(byte* buffer, long capacity) - { - this.buffer = buffer; - Capacity = capacity; - } +internal sealed unsafe class CircularBuffer +{ + private readonly byte* buffer; - internal long Capacity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + internal CircularBuffer(byte* buffer, long capacity) + { + this.buffer = buffer; + Capacity = capacity; + } + internal long Capacity + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal byte* GetPointer(long offset) - { - AdjustedOffset(ref offset); - return buffer + offset; - } + get; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlyMemory Read(long offset, long length, Memory? resultBuffer = null) - { - if (length == 0) - return ReadOnlyMemory.Empty; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal byte* GetPointer(long offset) + { + AdjustedOffset(ref offset); + return buffer + offset; + } - var result = resultBuffer ?? new byte[length]; - if (length > result.Length) - length = result.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyMemory Read(long offset, long length, Memory? resultBuffer = null) + { + if (length == 0) + return ReadOnlyMemory.Empty; - AdjustedOffset(ref offset); - using (var pinnedResultBuffer = result.Pin()) - { - var resultBuffeerPtr = (byte*)pinnedResultBuffer.Pointer; - var sourcePtr = buffer + offset; + var result = resultBuffer ?? new byte[length]; + if (length > result.Length) + length = result.Length; - var rightLength = Math.Min(Capacity - offset, length); - if (rightLength > 0) - Buffer.MemoryCopy(sourcePtr, resultBuffeerPtr, rightLength, rightLength); + AdjustedOffset(ref offset); + using (var pinnedResultBuffer = result.Pin()) + { + var resultBufferPtr = (byte*)pinnedResultBuffer.Pointer; + var sourcePtr = buffer + offset; - var leftLength = length - rightLength; - if (leftLength > 0) - Buffer.MemoryCopy(buffer, resultBuffeerPtr + rightLength, leftLength, leftLength); - } + var rightLength = Math.Min(Capacity - offset, length); + if (rightLength > 0) + Buffer.MemoryCopy(sourcePtr, resultBufferPtr, rightLength, rightLength); - return result.Slice(0, (int)length); + var leftLength = length - rightLength; + if (leftLength > 0) + Buffer.MemoryCopy(buffer, resultBufferPtr + rightLength, leftLength, leftLength); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Write(ReadOnlySpan source, long offset) - { - fixed (byte* sourcePtr = &MemoryMarshal.GetReference(source)) - Write(sourcePtr, source.Length, offset); - } + return result.Slice(0, (int)length); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Write(T source, long offset) where T : struct - { - Write((byte*)Unsafe.AsPointer(ref source), Unsafe.SizeOf(), offset); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Write(ReadOnlySpan source, long offset) + { + fixed (byte* sourcePtr = &MemoryMarshal.GetReference(source)) + Write(sourcePtr, source.Length, offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Write(byte* sourcePtr, long sourceLength, long offset) - { - if (sourceLength == 0) - return; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Write(T source, long offset) + where T : struct => + Write((byte*)Unsafe.AsPointer(ref source), Unsafe.SizeOf(), offset); - AdjustedOffset(ref offset); - var rightLength = Math.Min(Capacity - offset, sourceLength); - Buffer.MemoryCopy(sourcePtr, buffer + offset, rightLength, rightLength); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Clear(long offset, long length) + { + if (length == 0) + return; - var leftLength = sourceLength - rightLength; - if (leftLength > 0) - Buffer.MemoryCopy(sourcePtr + rightLength, buffer, leftLength, leftLength); - } + AdjustedOffset(ref offset); + var rightLength = Math.Min(Capacity - offset, length); + Unsafe.InitBlock(buffer + offset, 0, (uint)rightLength); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Clear(long offset, long length) - { - if (length == 0) - return; + var leftLength = length - rightLength; + if (leftLength > 0) + Unsafe.InitBlock(buffer, 0, (uint)leftLength); + } - AdjustedOffset(ref offset); - var rightLength = Math.Min(Capacity - offset, length); - Unsafe.InitBlock(buffer + offset, 0, (uint)rightLength); + // internal for testing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdjustedOffset(ref long offset) => offset %= Capacity; - var leftLength = length - rightLength; - if (leftLength > 0) - Unsafe.InitBlock(buffer, 0, (uint)leftLength); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Write(byte* sourcePtr, long sourceLength, long offset) + { + if (sourceLength == 0) + return; - // internal for testing - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AdjustedOffset(ref long offset) - => offset %= Capacity; + AdjustedOffset(ref offset); + var rightLength = Math.Min(Capacity - offset, sourceLength); + Buffer.MemoryCopy(sourcePtr, buffer + offset, rightLength, rightLength); + + var leftLength = sourceLength - rightLength; + if (leftLength > 0) + Buffer.MemoryCopy(sourcePtr + rightLength, buffer, leftLength, leftLength); } -} +} \ No newline at end of file diff --git a/src/Interprocess/Memory/IMemoryFile.cs b/src/Interprocess/Memory/IMemoryFile.cs index 3ae90f2..49e6fc0 100644 --- a/src/Interprocess/Memory/IMemoryFile.cs +++ b/src/Interprocess/Memory/IMemoryFile.cs @@ -1,10 +1,8 @@ -using System; -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +internal interface IMemoryFile : IDisposable { - internal interface IMemoryFile : IDisposable - { - MemoryMappedFile MappedFile { get; } - } -} + MemoryMappedFile MappedFile { get; } +} \ No newline at end of file diff --git a/src/Interprocess/Memory/MemoryFileUnix.cs b/src/Interprocess/Memory/MemoryFileUnix.cs index 9be4cf1..b16ba62 100644 --- a/src/Interprocess/Memory/MemoryFileUnix.cs +++ b/src/Interprocess/Memory/MemoryFileUnix.cs @@ -1,129 +1,131 @@ -using System; -using System.IO; -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; using Microsoft.Extensions.Logging; -namespace Cloudtoid.Interprocess.Memory.Unix +namespace Cloudtoid.Interprocess.Memory.Unix; + +internal sealed class MemoryFileUnix : IMemoryFile { - internal sealed class MemoryFileUnix : IMemoryFile + private const FileAccess FileAccessOption = FileAccess.ReadWrite; + private const FileShare FileShareOption = FileShare.ReadWrite | FileShare.Delete; + private const string Folder = ".cloudtoid/interprocess/mmf"; + private const string FileExtension = ".qu"; + private const int BufferSize = 0x1000; + private readonly string file; + private readonly ILogger logger; + + internal MemoryFileUnix(QueueOptions options, ILoggerFactory loggerFactory) { - private const FileAccess FileAccessOption = FileAccess.ReadWrite; - private const FileShare FileShareOption = FileShare.ReadWrite | FileShare.Delete; - private const string Folder = ".cloudtoid/interprocess/mmf"; - private const string FileExtension = ".qu"; - private const int BufferSize = 0x1000; - private readonly string file; - private readonly ILogger logger; - - internal MemoryFileUnix(QueueOptions options, ILoggerFactory loggerFactory) - { - logger = loggerFactory.CreateLogger(); - file = Path.Combine(options.Path, Folder); - Directory.CreateDirectory(file); - file = Path.Combine(file, options.QueueName + FileExtension); + logger = loggerFactory.CreateLogger(); + file = Path.Combine(options.Path, Folder); + Directory.CreateDirectory(file); + file = Path.Combine(file, options.QueueName + FileExtension); - FileStream stream; + FileStream stream; - if (IsFileInUse(file)) - { - // just open the file - - stream = new FileStream( - file, - FileMode.Open, // just open it - FileAccessOption, - FileShareOption, - BufferSize); - } - else - { - // override (or create if no longer exist) as it is not being used - - stream = new FileStream( - file, - FileMode.Create, - FileAccessOption, - FileShareOption, - BufferSize); - } + if (IsFileInUse(file)) + { + // just open the file + +#pragma warning disable CA2000 + stream = new FileStream( + file, + FileMode.Open, // just open it + FileAccessOption, + FileShareOption, + BufferSize); + } + else + { + // override (or create if no longer exist) as it is not being used + + stream = new FileStream( + file, + FileMode.Create, + FileAccessOption, + FileShareOption, + BufferSize); +#pragma warning restore CA2000 + } + + try + { + MappedFile = MemoryMappedFile.CreateFromFile( + stream, + mapName: null, // do not set this or it will not work on Linux/Unix/MacOS + options.GetQueueStorageSize(), + MemoryMappedFileAccess.ReadWrite, + HandleInheritability.None, + false); + } + catch + { + // do not leave any resources hanging try { - MappedFile = MemoryMappedFile.CreateFromFile( - stream, - mapName: null, // do not set this or it will not work on Linux/Unix/MacOS - options.GetQueueStorageSize(), - MemoryMappedFileAccess.ReadWrite, - HandleInheritability.None, - false); + stream.Dispose(); } catch { - // do not leave any resources hanging - - try - { - stream.Dispose(); - } - catch - { - ResetBackingFile(); - } - - throw; + ResetBackingFile(); } + + throw; } + } - ~MemoryFileUnix() - => Dispose(false); + ~MemoryFileUnix() => + Dispose(false); - public MemoryMappedFile MappedFile { get; } + public MemoryMappedFile MappedFile { get; } - public void Dispose() + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + try { - Dispose(true); - GC.SuppressFinalize(this); + if (disposing) + MappedFile.Dispose(); } - - private void Dispose(bool disposing) + finally { - try - { - if (disposing) - MappedFile.Dispose(); - } - finally - { - ResetBackingFile(); - } + ResetBackingFile(); } + } - private void ResetBackingFile() - { - // Deletes the backing file if it is not used by any other process + private void ResetBackingFile() + { + // Deletes the backing file if it is not used by any other process - if (IsFileInUse(file)) - return; + if (IsFileInUse(file)) + return; - if (!PathUtil.TryDeleteFile(file)) - logger.LogError("Failed to delete queue's shared memory backing file even though it is not in use by any process."); - } + if (!PathUtil.TryDeleteFile(file)) + logger.FailedToDeleteSharedMemoryFile(); + } - private static bool IsFileInUse(string file) + private static bool IsFileInUse(string file) + { + try { - try - { - using (new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None)) { } - return false; - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) + using (new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None)) { - return true; } + + return false; + } + catch (FileNotFoundException) + { + return false; + } + catch (IOException) + { + return true; } } -} +} \ No newline at end of file diff --git a/src/Interprocess/Memory/MemoryFileWindows.cs b/src/Interprocess/Memory/MemoryFileWindows.cs index d29397d..82e13fd 100644 --- a/src/Interprocess/Memory/MemoryFileWindows.cs +++ b/src/Interprocess/Memory/MemoryFileWindows.cs @@ -1,29 +1,27 @@ -using System.IO; -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; -namespace Cloudtoid.Interprocess.Memory.Windows +namespace Cloudtoid.Interprocess.Memory.Windows; + +internal sealed class MemoryFileWindows : IMemoryFile { - internal sealed class MemoryFileWindows : IMemoryFile - { - private const string MapNamePrefix = "CT_IP_"; + private const string MapNamePrefix = "CT_IP_"; - internal MemoryFileWindows(QueueOptions options) - { + internal MemoryFileWindows(QueueOptions options) + { #if NET5_0_OR_GREATER - if (!System.OperatingSystem.IsWindows()) - throw new System.PlatformNotSupportedException(); + if (!OperatingSystem.IsWindows()) + throw new PlatformNotSupportedException(); #endif - MappedFile = MemoryMappedFile.CreateOrOpen( - mapName: MapNamePrefix + options.QueueName, - options.GetQueueStorageSize(), - MemoryMappedFileAccess.ReadWrite, - MemoryMappedFileOptions.None, - HandleInheritability.None); - } + MappedFile = MemoryMappedFile.CreateOrOpen( + mapName: MapNamePrefix + options.QueueName, + options.GetQueueStorageSize(), + MemoryMappedFileAccess.ReadWrite, + MemoryMappedFileOptions.None, + HandleInheritability.None); + } - public MemoryMappedFile MappedFile { get; } + public MemoryMappedFile MappedFile { get; } - public void Dispose() - => MappedFile.Dispose(); - } -} + public void Dispose() => + MappedFile.Dispose(); +} \ No newline at end of file diff --git a/src/Interprocess/Memory/MemoryView.cs b/src/Interprocess/Memory/MemoryView.cs index 6652146..aac68dd 100644 --- a/src/Interprocess/Memory/MemoryView.cs +++ b/src/Interprocess/Memory/MemoryView.cs @@ -1,63 +1,61 @@ -using System; -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Memory.Unix; using Cloudtoid.Interprocess.Memory.Windows; using Microsoft.Extensions.Logging; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +// This class manages the underlying Memory Mapped File +internal sealed class MemoryView : IDisposable { - // This class manages the underlying Memory Mapped File - internal class MemoryView : IDisposable + private readonly IMemoryFile file; + private readonly MemoryMappedViewAccessor view; + + internal unsafe MemoryView(QueueOptions options, ILoggerFactory loggerFactory) { - private readonly IMemoryFile file; - private readonly MemoryMappedViewAccessor view; + file = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? new MemoryFileWindows(options) + : new MemoryFileUnix(options, loggerFactory); - internal unsafe MemoryView(QueueOptions options, ILoggerFactory loggerFactory) + try { - file = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? new MemoryFileWindows(options) - : new MemoryFileUnix(options, loggerFactory); + view = file.MappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.ReadWrite); try { - view = file.MappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.ReadWrite); - - try - { - Pointer = AcquirePointer(); - } - catch - { - view.Dispose(); - throw; - } + Pointer = AcquirePointer(); } catch { - file.Dispose(); + view.Dispose(); throw; } } - - public unsafe byte* Pointer { get; } - - public void Dispose() + catch { - view.SafeMemoryMappedViewHandle.ReleasePointer(); - view.Flush(); - view.Dispose(); file.Dispose(); + throw; } + } - private unsafe byte* AcquirePointer() - { - byte* ptr = null; - view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); - if (ptr == null) - throw new InvalidOperationException("Failed to acquire a pointer to the memory mapped file view."); + public unsafe byte* Pointer { get; } - return ptr; - } + public void Dispose() + { + view.SafeMemoryMappedViewHandle.ReleasePointer(); + view.Flush(); + view.Dispose(); + file.Dispose(); + } + + private unsafe byte* AcquirePointer() + { + byte* ptr = null; + view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); + if (ptr is null) + throw new InvalidOperationException("Failed to acquire a pointer to the memory mapped file view."); + + return ptr; } -} +} \ No newline at end of file diff --git a/src/Interprocess/Queue/Publisher.cs b/src/Interprocess/Queue/Publisher.cs index 7b068af..49ad859 100644 --- a/src/Interprocess/Queue/Publisher.cs +++ b/src/Interprocess/Queue/Publisher.cs @@ -1,98 +1,89 @@ -using System; -using System.Threading; -using Microsoft.Extensions.Logging; - -namespace Cloudtoid.Interprocess -{ - internal sealed class Publisher : Queue, IPublisher - { - private readonly IInterprocessSemaphoreReleaser signal; - - internal Publisher(QueueOptions options, ILoggerFactory loggerFactory) - : base(options, loggerFactory) - { - signal = InterprocessSemaphore.CreateReleaser(options.QueueName); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - signal.Dispose(); - - base.Dispose(disposing); - } - - public unsafe bool TryEnqueue(ReadOnlySpan message) - { - var bodyLength = message.Length; - var messageLength = GetPaddedMessageLength(bodyLength); - - while (true) - { - var header = *Header; - - if (!CheckCapacity(header, messageLength)) - return false; - - var writeOffset = header.WriteOffset; - var newWriteOffset = SafeIncrementMessageOffset(writeOffset, messageLength); - - // try to atomically update the write-offset that is stored in the queue header - if (Interlocked.CompareExchange(ref Header->WriteOffset, newWriteOffset, writeOffset) == writeOffset) - { - try - { - // write the message body - Buffer.Write(message, GetMessageBodyOffset(writeOffset)); - - // write the message header - Buffer.Write( - new MessageHeader(MessageHeader.ReadyToBeConsumedState, bodyLength), - writeOffset); - } - catch (Exception ex) - { - // if there is an error here, we are in a bad state. - // treat this as a fatal exception and crash the process - Logger.FailFast( - "Publishing to the shared memory queue failed leaving the queue in a bad state. " + - "The only option is to crash the application.", - ex); - } - - // signal the next receiver that there is a new message in the queue - signal.Release(); - return true; - } - } - } - - private bool CheckCapacity(QueueHeader header, long messageLength) - { - if (messageLength > Buffer.Capacity) - return false; - - if (header.IsEmpty()) - return true; // it is an empty queue - - var readOffset = header.ReadOffset % Buffer.Capacity; - var writeOffset = header.WriteOffset % Buffer.Capacity; - - if (readOffset == writeOffset) - return false; // queue is full - - if (readOffset < writeOffset) - { - if (messageLength > Buffer.Capacity + readOffset - writeOffset) - return false; - } - else - { - if (messageLength > readOffset - writeOffset) - return false; - } - - return true; - } - } -} +using Microsoft.Extensions.Logging; + +namespace Cloudtoid.Interprocess; + +internal sealed class Publisher : Queue, IPublisher +{ + private readonly IInterprocessSemaphoreReleaser signal; + + internal Publisher(QueueOptions options, ILoggerFactory loggerFactory) + : base(options, loggerFactory) => signal = InterprocessSemaphore.CreateReleaser(options.QueueName); + + public unsafe bool TryEnqueue(ReadOnlySpan message) + { + var bodyLength = message.Length; + var messageLength = GetPaddedMessageLength(bodyLength); + + while (true) + { + var header = *Header; + + if (!CheckCapacity(header, messageLength)) + return false; + + var writeOffset = header.WriteOffset; + var newWriteOffset = SafeIncrementMessageOffset(writeOffset, messageLength); + + // try to atomically update the write-offset that is stored in the queue header + if (Interlocked.CompareExchange(ref Header->WriteOffset, newWriteOffset, writeOffset) == writeOffset) + { + try + { + // write the message body + Buffer.Write(message, GetMessageBodyOffset(writeOffset)); + + // write the message header + Buffer.Write( + new MessageHeader(MessageHeader.ReadyToBeConsumedState, bodyLength), + writeOffset); + } + catch + { + // if there is an error here, we are in a bad state. + // treat this as a fatal exception and crash the process + Environment.FailFast( + "Publishing to the shared memory queue failed leaving the queue in a bad state. The only option is to crash the application."); + } + + // signal the next receiver that there is a new message in the queue + signal.Release(); + return true; + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + signal.Dispose(); + + base.Dispose(disposing); + } + + private bool CheckCapacity(QueueHeader header, long messageLength) + { + if (messageLength > Buffer.Capacity) + return false; + + if (header.IsEmpty()) + return true; // it is an empty queue + + var readOffset = header.ReadOffset % Buffer.Capacity; + var writeOffset = header.WriteOffset % Buffer.Capacity; + + if (readOffset == writeOffset) + return false; // queue is full + + if (readOffset < writeOffset) + { + if (messageLength > Buffer.Capacity + readOffset - writeOffset) + return false; + } + else if (messageLength > readOffset - writeOffset) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/Interprocess/Queue/Queue.cs b/src/Interprocess/Queue/Queue.cs index 40ed9e3..fd030d9 100644 --- a/src/Interprocess/Queue/Queue.cs +++ b/src/Interprocess/Queue/Queue.cs @@ -1,89 +1,89 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +internal abstract class Queue : IDisposable { - internal abstract class Queue : IDisposable - { - private readonly MemoryView view; + private readonly MemoryView view; - protected unsafe Queue(QueueOptions options, ILoggerFactory loggerFactory) + protected unsafe Queue(QueueOptions options, ILoggerFactory loggerFactory) + { + Logger = loggerFactory.CreateLogger(); + view = new MemoryView(options, loggerFactory); + try { - Logger = loggerFactory.CreateLogger(); - view = new MemoryView(options, loggerFactory); - try - { - Buffer = new CircularBuffer(sizeof(QueueHeader) + view.Pointer, options.Capacity); - } - catch - { - view.Dispose(); - throw; - } - - // must clean up if the application is being closed but finalizer is not called. - // this happens in cases such as closing a console app by pressing the X button. - AppDomain.CurrentDomain.ProcessExit += OnAppExit; - Console.CancelKeyPress += OnAppExit; + Buffer = new CircularBuffer(sizeof(QueueHeader) + view.Pointer, options.Capacity); } - - ~Queue() - => Dispose(false); - - public unsafe QueueHeader* Header + catch { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (QueueHeader*)view.Pointer; + view.Dispose(); + throw; } - protected CircularBuffer Buffer { get; } - protected ILogger Logger { get; } + // must clean up if the application is being closed but finalizer is not called. + // this happens in cases such as closing a console app by pressing the X button. + AppDomain.CurrentDomain.ProcessExit += OnAppExit; + Console.CancelKeyPress += OnAppExit; + } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + ~Queue() => + Dispose(false); - protected virtual void Dispose(bool disposing) - { - if (disposing) - view.Dispose(); - } + public unsafe QueueHeader* Header + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (QueueHeader*)view.Pointer; + } - private void OnAppExit(object? sender, EventArgs e) - { - try - { - Dispose(false); - } - catch { } - } + protected CircularBuffer Buffer { get; } + protected ILogger Logger { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static unsafe long GetMessageBodyOffset(long startOffset) - => startOffset + sizeof(MessageHeader); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Calculates the total length of a message which consists of [header][body][padding]. - /// - /// headerAn instance of - /// bodyA collection of bytes provided by the user - /// paddingA possible padding is added to round up the length to the closest multiple of 8 bytes - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static unsafe long GetPaddedMessageLength(long bodyLength) - { - var length = sizeof(MessageHeader) + bodyLength; + protected virtual void Dispose(bool disposing) + { + if (disposing) + view.Dispose(); + } - // Round up to the closest integer divisible by 8. This will add the [padding] if one is needed. - return 8 * (long)Math.Ceiling(length / 8.0); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static unsafe long GetMessageBodyOffset(long startOffset) => + startOffset + sizeof(MessageHeader); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected long SafeIncrementMessageOffset(long offset, long increment) - => (offset + increment) % (Buffer.Capacity * 2); + /// + /// Calculates the total length of a message which consists of [header][body][padding]. + /// + /// headerAn instance of + /// bodyA collection of bytes provided by the user + /// paddingA possible padding is added to round up the length to the closest multiple of 8 bytes + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static unsafe long GetPaddedMessageLength(long bodyLength) + { + var length = sizeof(MessageHeader) + bodyLength; + + // Round up to the closest integer divisible by 8. This will add the [padding] if one is needed. + return 8 * (long)Math.Ceiling(length / 8.0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected long SafeIncrementMessageOffset(long offset, long increment) => + (offset + increment) % (Buffer.Capacity * 2); + + private void OnAppExit(object? sender, EventArgs e) + { + try + { + Dispose(false); + } + catch + { + } } } \ No newline at end of file diff --git a/src/Interprocess/Queue/QueueFactory.cs b/src/Interprocess/Queue/QueueFactory.cs index 0926783..e6d3e29 100644 --- a/src/Interprocess/Queue/QueueFactory.cs +++ b/src/Interprocess/Queue/QueueFactory.cs @@ -2,33 +2,31 @@ using Microsoft.Extensions.Logging.Abstractions; using static Cloudtoid.Contract; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +/// +public sealed class QueueFactory : IQueueFactory { - public sealed class QueueFactory : IQueueFactory - { - private readonly ILoggerFactory loggerFactory; + private readonly ILoggerFactory loggerFactory; - public QueueFactory() - : this(NullLoggerFactory.Instance) - { - } + /// + public QueueFactory() + : this(NullLoggerFactory.Instance) + { + } - public QueueFactory(ILoggerFactory loggerFactory) - { - Util.Ensure64Bit(); - this.loggerFactory = CheckValue(loggerFactory, nameof(loggerFactory)); - } + /// Initializes a new instance of the class. + public QueueFactory(ILoggerFactory loggerFactory) + { + Util.Ensure64Bit(); + this.loggerFactory = CheckValue(loggerFactory, nameof(loggerFactory)); + } - /// - /// Creates a queue message publisher. - /// - public IPublisher CreatePublisher(QueueOptions options) - => new Publisher(CheckValue(options, nameof(options)), loggerFactory); + /// + public IPublisher CreatePublisher(QueueOptions options) => + new Publisher(CheckValue(options, nameof(options)), loggerFactory); - /// - /// Creates a queue message subscriber. - /// - public ISubscriber CreateSubscriber(QueueOptions options) - => new Subscriber(CheckValue(options, nameof(options)), loggerFactory); - } -} + /// + public ISubscriber CreateSubscriber(QueueOptions options) => + new Subscriber(CheckValue(options, nameof(options)), loggerFactory); +} \ No newline at end of file diff --git a/src/Interprocess/Queue/Subscriber.cs b/src/Interprocess/Queue/Subscriber.cs index 90bd873..b4c15fb 100644 --- a/src/Interprocess/Queue/Subscriber.cs +++ b/src/Interprocess/Queue/Subscriber.cs @@ -1,165 +1,162 @@ -using System; -using System.Threading; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +internal sealed class Subscriber : Queue, ISubscriber { - internal sealed class Subscriber : Queue, ISubscriber - { - private static readonly long TicksForTenSeconds = TimeSpan.FromSeconds(10).Ticks; - private readonly CancellationTokenSource cancellationSource = new(); - private readonly CountdownEvent countdownEvent = new(1); - private readonly IInterprocessSemaphoreWaiter signal; + private static readonly long TicksForTenSeconds = TimeSpan.FromSeconds(10).Ticks; + private readonly CancellationTokenSource cancellationSource = new(); + private readonly CountdownEvent countdownEvent = new(1); + private readonly IInterprocessSemaphoreWaiter signal; - internal Subscriber(QueueOptions options, ILoggerFactory loggerFactory) - : base(options, loggerFactory) - { - signal = InterprocessSemaphore.CreateWaiter(options.QueueName); - } + internal Subscriber(QueueOptions options, ILoggerFactory loggerFactory) + : base(options, loggerFactory) => signal = InterprocessSemaphore.CreateWaiter(options.QueueName); - protected override void Dispose(bool disposing) - { - // drain the Dequeue/TryDequeue requests - cancellationSource.Cancel(); - countdownEvent.Signal(); - countdownEvent.Wait(); + public bool TryDequeue(CancellationToken cancellation, out ReadOnlyMemory message) => + TryDequeueCore(default, cancellation, out message); - // There is a potential for a race condition in DequeueCore if the cancellationSource - // was not cancelled before AddEvent is called. The sleep here will prevent that. - Thread.Sleep(millisecondsTimeout: 10); + public bool TryDequeue(Memory buffer, CancellationToken cancellation, out ReadOnlyMemory message) => + TryDequeueCore(buffer, cancellation, out message); - if (disposing) - { - countdownEvent.Dispose(); - signal.Dispose(); - cancellationSource.Dispose(); - } + public ReadOnlyMemory Dequeue(CancellationToken cancellation) => + DequeueCore(default, cancellation); - base.Dispose(disposing); - } + public ReadOnlyMemory Dequeue(Memory buffer, CancellationToken cancellation) => + DequeueCore(buffer, cancellation); - public bool TryDequeue(CancellationToken cancellation, out ReadOnlyMemory message) - => TryDequeueCore(default, cancellation, out message); + protected override void Dispose(bool disposing) + { + // drain the Dequeue/TryDequeue requests + cancellationSource.Cancel(); + countdownEvent.Signal(); + countdownEvent.Wait(); - public bool TryDequeue(Memory resultBuffer, CancellationToken cancellation, out ReadOnlyMemory message) - => TryDequeueCore(resultBuffer, cancellation, out message); + // There is a potential for a race condition in DequeueCore if the cancellationSource + // was not cancelled before AddEvent is called. The sleep here will prevent that. + Thread.Sleep(millisecondsTimeout: 10); - public ReadOnlyMemory Dequeue(CancellationToken cancellation) - => DequeueCore(default, cancellation); + if (disposing) + { + countdownEvent.Dispose(); + signal.Dispose(); + cancellationSource.Dispose(); + } - public ReadOnlyMemory Dequeue(Memory resultBuffer, CancellationToken cancellation) - => DequeueCore(resultBuffer, cancellation); + base.Dispose(disposing); + } - private bool TryDequeueCore(Memory? resultBuffer, CancellationToken cancellation, out ReadOnlyMemory message) - { - // do NOT reorder the cancellation and the AddCount operation below. See Dispose for more information. - cancellationSource.ThrowIfCancellationRequested(cancellation); - countdownEvent.AddCount(); + private bool TryDequeueCore( + Memory? resultBuffer, + CancellationToken cancellation, + out ReadOnlyMemory message) + { + // do NOT reorder the cancellation and the AddCount operation below. See Dispose for more information. + cancellationSource.ThrowIfCancellationRequested(cancellation); + countdownEvent.AddCount(); - try - { - return TryDequeueImpl(resultBuffer, cancellation, out message); - } - finally - { - countdownEvent.Signal(); - } + try + { + return TryDequeueImpl(resultBuffer, cancellation, out message); } - - private ReadOnlyMemory DequeueCore(Memory? resultBuffer, CancellationToken cancellation) + finally { - // do NOT reorder the cancellation and the AddCount operation below. See Dispose for more information. - cancellationSource.ThrowIfCancellationRequested(cancellation); - countdownEvent.AddCount(); + countdownEvent.Signal(); + } + } - try - { - int i = -5; - while (true) - { - if (TryDequeueImpl(resultBuffer, cancellation, out var message)) - return message; - - if (i > 10) - signal.Wait(millisecondsTimeout: 10); - else if (i++ > 0) - signal.Wait(millisecondsTimeout: i); - else - Thread.Yield(); - } - } - finally + private ReadOnlyMemory DequeueCore(Memory? resultBuffer, CancellationToken cancellation) + { + // do NOT reorder the cancellation and the AddCount operation below. See Dispose for more information. + cancellationSource.ThrowIfCancellationRequested(cancellation); + countdownEvent.AddCount(); + + try + { + int i = -5; + while (true) { - countdownEvent.Signal(); + if (TryDequeueImpl(resultBuffer, cancellation, out var message)) + return message; + + if (i > 10) + signal.Wait(millisecondsTimeout: 10); + else if (i++ > 0) + signal.Wait(millisecondsTimeout: i); + else + Thread.Yield(); } } - - private unsafe bool TryDequeueImpl( - Memory? resultBuffer, - CancellationToken cancellation, - out ReadOnlyMemory message) + finally { - cancellationSource.ThrowIfCancellationRequested(cancellation); + countdownEvent.Signal(); + } + } - message = ReadOnlyMemory.Empty; - var header = *Header; + private unsafe bool TryDequeueImpl( + Memory? resultBuffer, + CancellationToken cancellation, + out ReadOnlyMemory message) + { + cancellationSource.ThrowIfCancellationRequested(cancellation); - // is this an empty queue? - if (header.IsEmpty()) - return false; + message = ReadOnlyMemory.Empty; + var header = *Header; - var readLockTimestamp = header.ReadLockTimestamp; - var now = DateTime.UtcNow.Ticks; + // is this an empty queue? + if (header.IsEmpty()) + return false; - // is there already a read-lock or has the previous lock timed out meaning that a subscriber crashed? - if (now - readLockTimestamp < TicksForTenSeconds) - return false; + var readLockTimestamp = header.ReadLockTimestamp; + var now = DateTime.UtcNow.Ticks; - // take a read-lock so no other thread can read a message - if (Interlocked.CompareExchange(ref Header->ReadLockTimestamp, now, readLockTimestamp) != readLockTimestamp) + // is there already a read-lock or has the previous lock timed out meaning that a subscriber crashed? + if (now - readLockTimestamp < TicksForTenSeconds) + return false; + + // take a read-lock so no other thread can read a message + if (Interlocked.CompareExchange(ref Header->ReadLockTimestamp, now, readLockTimestamp) != readLockTimestamp) + return false; + + try + { + // is the queue empty now that we were able to get a read-lock? + if (Header->IsEmpty()) return false; - try - { - // is the queue empty now that we were able to get a read-lock? - if (Header->IsEmpty()) - return false; - - // now finally have a read-lock and the queue is not empty - var readOffset = Header->ReadOffset; - var messageHeader = (MessageHeader*)Buffer.GetPointer(readOffset); - - // was this message fully written by the publisher? if not, wait for the publisher to finish writting it - while (Interlocked.CompareExchange( - ref messageHeader->State, - MessageHeader.LockedToBeConsumedState, - MessageHeader.ReadyToBeConsumedState) != MessageHeader.ReadyToBeConsumedState) - { - Thread.Yield(); - } - - // read the message body from the queue - var bodyLength = messageHeader->BodyLength; - message = Buffer.Read( - GetMessageBodyOffset(readOffset), - bodyLength, - resultBuffer); - - // zero out the message, including the message header - var messageLength = GetPaddedMessageLength(bodyLength); - Buffer.Clear(readOffset, messageLength); - - // update the read offset of the queue - var newReadOffset = SafeIncrementMessageOffset(readOffset, messageLength); - Interlocked.Exchange(ref Header->ReadOffset, newReadOffset); - } - finally + // now finally have a read-lock and the queue is not empty + var readOffset = Header->ReadOffset; + var messageHeader = (MessageHeader*)Buffer.GetPointer(readOffset); + + // was this message fully written by the publisher? if not, wait for the publisher to finish writting it + while (Interlocked.CompareExchange( + ref messageHeader->State, + MessageHeader.LockedToBeConsumedState, + MessageHeader.ReadyToBeConsumedState) != MessageHeader.ReadyToBeConsumedState) { - // release the read-lock - Interlocked.Exchange(ref Header->ReadLockTimestamp, 0L); + Thread.Yield(); } - return true; + // read the message body from the queue + var bodyLength = messageHeader->BodyLength; + message = Buffer.Read( + GetMessageBodyOffset(readOffset), + bodyLength, + resultBuffer); + + // zero out the message, including the message header + var messageLength = GetPaddedMessageLength(bodyLength); + Buffer.Clear(readOffset, messageLength); + + // update the read offset of the queue + var newReadOffset = SafeIncrementMessageOffset(readOffset, messageLength); + Interlocked.Exchange(ref Header->ReadOffset, newReadOffset); } + finally + { + // release the read-lock + Interlocked.Exchange(ref Header->ReadLockTimestamp, 0L); + } + + return true; } -} +} \ No newline at end of file diff --git a/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs b/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs index 4513959..76693a4 100644 --- a/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs +++ b/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs @@ -1,9 +1,6 @@ -using System; +namespace Cloudtoid.Interprocess; -namespace Cloudtoid.Interprocess +internal interface IInterprocessSemaphoreReleaser : IDisposable { - internal interface IInterprocessSemaphoreReleaser : IDisposable - { - void Release(); - } -} + void Release(); +} \ No newline at end of file diff --git a/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs b/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs index d1461c0..241f573 100644 --- a/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs +++ b/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs @@ -1,9 +1,6 @@ -using System; +namespace Cloudtoid.Interprocess; -namespace Cloudtoid.Interprocess +internal interface IInterprocessSemaphoreWaiter : IDisposable { - internal interface IInterprocessSemaphoreWaiter : IDisposable - { - bool Wait(int millisecondsTimeout); - } -} + bool Wait(int millisecondsTimeout); +} \ No newline at end of file diff --git a/src/Interprocess/Semaphore/InterprocessSemaphore.cs b/src/Interprocess/Semaphore/InterprocessSemaphore.cs index 173049b..f0a8ac3 100644 --- a/src/Interprocess/Semaphore/InterprocessSemaphore.cs +++ b/src/Interprocess/Semaphore/InterprocessSemaphore.cs @@ -1,36 +1,35 @@ -using System.Runtime.InteropServices; -using Cloudtoid.Interprocess.Semaphore.Linux; -using Cloudtoid.Interprocess.Semaphore.MacOS; -using Cloudtoid.Interprocess.Semaphore.Windows; - -namespace Cloudtoid.Interprocess -{ - /// - /// This class opens or creates platform agnostic named semaphore. Named - /// semaphores are synchronization constructs accessible across processes. - /// - internal static class InterprocessSemaphore - { - internal static IInterprocessSemaphoreWaiter CreateWaiter(string name) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return new SemaphoreWindows(name); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return new SemaphoreMacOS(name); - - return new SemaphoreLinux(name); - } - - internal static IInterprocessSemaphoreReleaser CreateReleaser(string name) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return new SemaphoreWindows(name); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return new SemaphoreMacOS(name); - - return new SemaphoreLinux(name); - } - } -} +using System.Runtime.InteropServices; +using Cloudtoid.Interprocess.Semaphore.Linux; +using Cloudtoid.Interprocess.Semaphore.MacOS; +using Cloudtoid.Interprocess.Semaphore.Windows; + +namespace Cloudtoid.Interprocess; + +/// +/// This class opens or creates platform agnostic named semaphore. Named +/// semaphores are synchronization constructs accessible across processes. +/// +internal static class InterprocessSemaphore +{ + internal static IInterprocessSemaphoreWaiter CreateWaiter(string name) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return new SemaphoreWindows(name); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return new SemaphoreMacOS(name); + + return new SemaphoreLinux(name); + } + + internal static IInterprocessSemaphoreReleaser CreateReleaser(string name) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return new SemaphoreWindows(name); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return new SemaphoreMacOS(name); + + return new SemaphoreLinux(name); + } +} \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Linux/Interop.cs b/src/Interprocess/Semaphore/Linux/Interop.cs index aa8ec7e..2347418 100644 --- a/src/Interprocess/Semaphore/Linux/Interop.cs +++ b/src/Interprocess/Semaphore/Linux/Interop.cs @@ -1,163 +1,159 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Threading; +using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Semaphore.Posix; -namespace Cloudtoid.Interprocess.Semaphore.Linux +namespace Cloudtoid.Interprocess.Semaphore.Linux; + +internal static partial class Interop { - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Matching the exact names in Linux/MacOS")] - internal static partial class Interop + private const string Lib = "librt.so.1"; + private const uint SEMVALUEMAX = 32767; + private const int OCREAT = 0x040; // Create the semaphore if it does not exist + + private const int ENOENT = 2; // The named semaphore does not exist. + private const int EINTR = 4; // Semaphore operation was interrupted by a signal. + private const int EAGAIN = 11; // Couldn't be acquired (sem_trywait) + private const int ENOMEM = 12; // Out of memory + private const int EACCES = 13; // Semaphore exists, but the caller does not have permission to open it. + private const int EEXIST = 17; // O_CREAT and O_EXCL were specified and the semaphore exists. + private const int EINVAL = 22; // Invalid semaphore or operation on a semaphore + private const int ENFILE = 23; // Too many semaphores or file descriptors are open on the system. + private const int EMFILE = 24; // The process has already reached its limit for semaphores or file descriptors in use. + private const int ENAMETOOLONG = 36; // The specified semaphore name is too long + private const int EOVERFLOW = 75; // The maximum allowable value for a semaphore would be exceeded. + private const int ETIMEDOUT = 110; // The call timed out before the semaphore could be locked. + + private static unsafe int Error => Marshal.GetLastWin32Error(); + + internal static IntPtr CreateOrOpenSemaphore(string name, uint initialCount) { - private const string Lib = "librt.so.1"; - private const uint SEMVALUEMAX = 32767; - private const int OCREAT = 0x040; // Create the semaphore if it does not exist - - private const int ENOENT = 2; // The named semaphore does not exist. - private const int EINTR = 4; // Semaphore operation was interrupted by a signal. - private const int EAGAIN = 11; // Couldn't be acquired (sem_trywait) - private const int ENOMEM = 12; // Out of memory - private const int EACCES = 13; // Semaphore exists, but the caller does not have permission to open it. - private const int EEXIST = 17; // O_CREAT and O_EXCL were specified and the semaphore exists. - private const int EINVAL = 22; // Invalid semaphore or operation on a semaphore - private const int ENFILE = 23; // Too many semaphores or file descriptors are open on the system. - private const int EMFILE = 24; // The process has already reached its limit for semaphores or file descriptors in use. - private const int ENAMETOOLONG = 36; // The specified semaphore name is too long - private const int EOVERFLOW = 75; // The maximum allowable value for a semaphore would be exceeded. - private const int ETIMEDOUT = 110; // The call timed out before the semaphore could be locked. - - private static unsafe int Error => Marshal.GetLastWin32Error(); - - [LibraryImport(Lib, EntryPoint = "sem_open", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - private static partial IntPtr SemaphoreOpen(string name, int oflag, uint mode, uint value); - - [LibraryImport(Lib, EntryPoint = "sem_post", SetLastError = true)] - private static partial int SemaphorePost(IntPtr handle); - - [LibraryImport(Lib, EntryPoint = "sem_wait", SetLastError = true)] - private static partial int SemaphoreWait(IntPtr handle); - - [LibraryImport(Lib, EntryPoint = "sem_trywait", SetLastError = true)] - private static partial int SemaphoreTryWait(IntPtr handle); + var handle = SemaphoreOpen(name, OCREAT, (uint)PosixFilePermissions.ACCESSPERMS, initialCount); + if (handle != IntPtr.Zero) + return handle; - [LibraryImport(Lib, EntryPoint = "sem_timedwait", SetLastError = true)] - private static partial int SemaphoreTimedWait(IntPtr handle, ref PosixTimespec abs_timeout); - - [LibraryImport(Lib, EntryPoint = "sem_unlink", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - private static partial int SemaphoreUnlink(string name); + throw Error switch + { + EINVAL => new ArgumentException( + $"One of the arguments passed to sem_open is invalid. Please also ensure {nameof(initialCount)} is less than {SEMVALUEMAX}."), + ENAMETOOLONG => new ArgumentException("The specified semaphore name is too long.", nameof(name)), + EACCES => new PosixSemaphoreUnauthorizedAccessException(), + EEXIST => new PosixSemaphoreExistsException(), + EINTR => new OperationCanceledException(), + ENFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open on the system."), + EMFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open by this process."), + ENOMEM => new InsufficientMemoryException(), + _ => new PosixSemaphoreException(Error), + }; + } - [LibraryImport(Lib, EntryPoint = "sem_close", SetLastError = true)] - private static partial int SemaphoreClose(IntPtr handle); + internal static void Release(IntPtr handle) + { + if (SemaphorePost(handle) == 0) + return; - internal static IntPtr CreateOrOpenSemaphore(string name, uint initialCount) + throw Error switch { - var handle = SemaphoreOpen(name, OCREAT, (uint)PosixFilePermissions.ACCESSPERMS, initialCount); - if (handle != IntPtr.Zero) - return handle; + EINVAL => new InvalidPosixSemaphoreException(), + EOVERFLOW => new SemaphoreFullException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - EINVAL => new ArgumentException($"The initial count cannot be greater than {SEMVALUEMAX}.", nameof(initialCount)), - ENAMETOOLONG => new ArgumentException($"The specified semaphore name is too long.", nameof(name)), - EACCES => new PosixSemaphoreUnauthorizedAccessException(), - EEXIST => new PosixSemaphoreExistsException(), - EINTR => new OperationCanceledException(), - ENFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open on the system."), - EMFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open by this process."), - ENOMEM => new InsufficientMemoryException(), - _ => new PosixSemaphoreException(Error), - }; - } + internal static void Close(IntPtr handle) + { + if (SemaphoreClose(handle) == 0) + return; - internal static void Release(IntPtr handle) + throw Error switch { - if (SemaphorePost(handle) == 0) - return; + EINVAL => new InvalidPosixSemaphoreException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - EINVAL => new InvalidPosixSemaphoreException(), - EOVERFLOW => new SemaphoreFullException(), - _ => new PosixSemaphoreException(Error), - }; - } + internal static void Unlink(string name) + { + if (SemaphoreUnlink(name) == 0) + return; - internal static bool Wait(IntPtr handle, int millisecondsTimeout) + throw Error switch { - if (millisecondsTimeout == Timeout.Infinite) - { - Wait(handle); - return true; - } - else if (millisecondsTimeout == 0) - { - if (SemaphoreTryWait(handle) == 0) - return true; - - return Error switch - { - EAGAIN => false, - EINVAL => throw new InvalidPosixSemaphoreException(), - EINTR => throw new OperationCanceledException(), - _ => throw new PosixSemaphoreException(Error), - }; - } - - var timeout = DateTimeOffset.UtcNow.AddMilliseconds(millisecondsTimeout); - return Wait(handle, timeout); - } + ENAMETOOLONG => new ArgumentException("The specified semaphore name is too long.", nameof(name)), + EACCES => new PosixSemaphoreUnauthorizedAccessException(), + ENOENT => new PosixSemaphoreNotExistsException(), + _ => new PosixSemaphoreException(Error), + }; + } - private static void Wait(IntPtr handle) + internal static bool Wait(IntPtr handle, int millisecondsTimeout) + { + if (millisecondsTimeout == Timeout.Infinite) { - if (SemaphoreWait(handle) == 0) - return; - - throw Error switch - { - EINVAL => new InvalidPosixSemaphoreException(), - EINTR => new OperationCanceledException(), - _ => new PosixSemaphoreException(Error), - }; + Wait(handle); + return true; } - - private static bool Wait(IntPtr handle, PosixTimespec timeout) + else if (millisecondsTimeout == 0) { - if (SemaphoreTimedWait(handle, ref timeout) == 0) + if (SemaphoreTryWait(handle) == 0) return true; return Error switch { - ETIMEDOUT => false, + EAGAIN => false, EINVAL => throw new InvalidPosixSemaphoreException(), EINTR => throw new OperationCanceledException(), _ => throw new PosixSemaphoreException(Error), }; } - internal static void Close(IntPtr handle) - { - if (SemaphoreClose(handle) == 0) - return; + var timeout = DateTimeOffset.UtcNow.AddMilliseconds(millisecondsTimeout); + return Wait(handle, timeout); + } - throw Error switch - { - EINVAL => new InvalidPosixSemaphoreException(), - _ => new PosixSemaphoreException(Error), - }; - } + private static void Wait(IntPtr handle) + { + if (SemaphoreWait(handle) == 0) + return; - internal static void Unlink(string name) + throw Error switch { - if (SemaphoreUnlink(name) == 0) - return; + EINVAL => new InvalidPosixSemaphoreException(), + EINTR => new OperationCanceledException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - ENAMETOOLONG => new ArgumentException($"The specified semaphore name is too long.", nameof(name)), - EACCES => new PosixSemaphoreUnauthorizedAccessException(), - ENOENT => new PosixSemaphoreNotExistsException(), - _ => new PosixSemaphoreException(Error), - }; - } + private static bool Wait(IntPtr handle, PosixTimespec timeout) + { + if (SemaphoreTimedWait(handle, ref timeout) == 0) + return true; + + return Error switch + { + ETIMEDOUT => false, + EINVAL => throw new InvalidPosixSemaphoreException(), + EINTR => throw new OperationCanceledException(), + _ => throw new PosixSemaphoreException(Error), + }; } + + [LibraryImport(Lib, EntryPoint = "sem_open", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr SemaphoreOpen(string name, int oflag, uint mode, uint value); + + [LibraryImport(Lib, EntryPoint = "sem_post", SetLastError = true)] + private static partial int SemaphorePost(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_wait", SetLastError = true)] + private static partial int SemaphoreWait(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_trywait", SetLastError = true)] + private static partial int SemaphoreTryWait(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_timedwait", SetLastError = true)] + private static partial int SemaphoreTimedWait(IntPtr handle, ref PosixTimespec abs_timeout); + + [LibraryImport(Lib, EntryPoint = "sem_unlink", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] + private static partial int SemaphoreUnlink(string name); + + [LibraryImport(Lib, EntryPoint = "sem_close", SetLastError = true)] + private static partial int SemaphoreClose(IntPtr handle); } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs b/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs index 6e37ddc..cdc06bf 100644 --- a/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs +++ b/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs @@ -1,42 +1,39 @@ -using System; +namespace Cloudtoid.Interprocess.Semaphore.Linux; -namespace Cloudtoid.Interprocess.Semaphore.Linux +internal sealed class SemaphoreLinux : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser { - internal class SemaphoreLinux : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser + private const string HandleNamePrefix = "/ct.ip."; + private readonly string name; + private readonly bool deleteOnDispose; + private readonly IntPtr handle; + + internal SemaphoreLinux(string name, bool deleteOnDispose = false) { - private const string HandleNamePrefix = "/ct.ip."; - private readonly string name; - private readonly bool deleteOnDispose; - private readonly IntPtr handle; - - internal SemaphoreLinux(string name, bool deleteOnDispose = false) - { - this.name = name = HandleNamePrefix + name; - this.deleteOnDispose = deleteOnDispose; - handle = Interop.CreateOrOpenSemaphore(name, 0); - } - - ~SemaphoreLinux() - => Dispose(false); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Interop.Close(handle); - - if (deleteOnDispose) - Interop.Unlink(name); - } - - public void Release() - => Interop.Release(handle); - - public bool Wait(int millisecondsTimeout) - => Interop.Wait(handle, millisecondsTimeout); + this.name = name = HandleNamePrefix + name; + this.deleteOnDispose = deleteOnDispose; + handle = Interop.CreateOrOpenSemaphore(name, 0); + } + + ~SemaphoreLinux() => + DisposeCore(); + + public void Release() => + Interop.Release(handle); + + public bool Wait(int millisecondsTimeout) => + Interop.Wait(handle, millisecondsTimeout); + + public void Dispose() + { + DisposeCore(); + GC.SuppressFinalize(this); + } + + private void DisposeCore() + { + Interop.Close(handle); + + if (deleteOnDispose) + Interop.Unlink(name); } } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/MacOS/Interop.cs b/src/Interprocess/Semaphore/MacOS/Interop.cs index 73d590f..d62a905 100644 --- a/src/Interprocess/Semaphore/MacOS/Interop.cs +++ b/src/Interprocess/Semaphore/MacOS/Interop.cs @@ -1,160 +1,169 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Threading; +using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Semaphore.Posix; -namespace Cloudtoid.Interprocess.Semaphore.MacOS +namespace Cloudtoid.Interprocess.Semaphore.MacOS; + +internal static partial class Interop { - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Matching the exact names in Linux/MacOS")] - internal static partial class Interop + private const string Lib = "libSystem.dylib"; + private const int SEMVALUEMAX = 32767; + private const int OCREAT = 0x0200; // create the semaphore if it does not exist + + private const int ENOENT = 2; // The named semaphore does not exist. + private const int EINTR = 4; // Semaphore operation was interrupted by a signal. + private const int EDEADLK = 11; // A deadlock was detected. + private const int ENOMEM = 12; // Out of memory + private const int EACCES = 13; // Semaphore exists, but the caller does not have permission to open it. + private const int EEXIST = 17; // O_CREAT and O_EXCL were specified and the semaphore exists. + private const int EINVAL = 22; // Invalid semaphore or operation on a semaphore + private const int ENFILE = 23; // Too many semaphores or file descriptors are open on the system. + private const int EMFILE = 24; // The process has already reached its limit for semaphores or file descriptors in use. + private const int EAGAIN = 35; // The semaphore is already locked. + private const int ENAMETOOLONG = 63; // The specified semaphore name is too long + private const int EOVERFLOW = 84; // The maximum allowable value for a semaphore would be exceeded. + + private static readonly IntPtr SemFailed = new(-1); + + private static unsafe int Error => Marshal.GetLastWin32Error(); + + internal static IntPtr CreateOrOpenSemaphore(string name, uint initialCount) { - private const string Lib = "libSystem.dylib"; - private const int SEMVALUEMAX = 32767; - private const int OCREAT = 0x0200; // create the semaphore if it does not exist - - private const int ENOENT = 2; // The named semaphore does not exist. - private const int EINTR = 4; // Semaphore operation was interrupted by a signal. - private const int EDEADLK = 11; // A deadlock was detected. - private const int ENOMEM = 12; // Out of memory - private const int EACCES = 13; // Semaphore exists, but the caller does not have permission to open it. - private const int EEXIST = 17; // O_CREAT and O_EXCL were specified and the semaphore exists. - private const int EINVAL = 22; // Invalid semaphore or operation on a semaphore - private const int ENFILE = 23; // Too many semaphores or file descriptors are open on the system. - private const int EMFILE = 24; // The process has already reached its limit for semaphores or file descriptors in use. - private const int EAGAIN = 35; // The semaphore is already locked. - private const int ENAMETOOLONG = 63; // The specified semaphore name is too long - private const int EOVERFLOW = 84; // The maximum allowable value for a semaphore would be exceeded. - - private static readonly IntPtr SemFailed = new(-1); - - private static unsafe int Error => Marshal.GetLastWin32Error(); - - [LibraryImport(Lib, EntryPoint = "sem_open", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - private static partial IntPtr SemaphoreOpen(string name, int oflag, ulong __x2, ulong __x3, ulong __x4, ulong __x5, ulong __x6, ulong __x7, ulong mode, uint value); - - [LibraryImport(Lib, EntryPoint = "sem_post", SetLastError = true)] - private static partial int SemaphorePost(IntPtr handle); + var handle = SemaphoreOpen( + name, OCREAT, 0, 0, 0, 0, 0, 0, (uint)PosixFilePermissions.ACCESSPERMS, initialCount); - [LibraryImport(Lib, EntryPoint = "sem_wait", SetLastError = true)] - private static partial int SemaphoreWait(IntPtr handle); + if (handle != SemFailed) + return handle; - [LibraryImport(Lib, EntryPoint = "sem_trywait", SetLastError = true)] - private static partial int SemaphoreTryWait(IntPtr handle); - - [LibraryImport(Lib, EntryPoint = "sem_unlink", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - private static partial int SemaphoreUnlink(string name); + throw Error switch + { + EINVAL => new ArgumentException( + $"The initial count cannot be greater than {SEMVALUEMAX}.", + nameof(initialCount)), + ENAMETOOLONG => new ArgumentException("The specified semaphore name is too long.", nameof(name)), + EACCES => new PosixSemaphoreUnauthorizedAccessException(), + EEXIST => new PosixSemaphoreExistsException(), + EINTR => new OperationCanceledException(), + ENFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open on the system."), + EMFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open by this process."), + ENOMEM => new InsufficientMemoryException(), + _ => new PosixSemaphoreException(Error), + }; + } - [LibraryImport(Lib, EntryPoint = "sem_close", SetLastError = true)] - private static partial int SemaphoreClose(IntPtr handle); + internal static void Release(IntPtr handle) + { + if (SemaphorePost(handle) == 0) + return; - internal static IntPtr CreateOrOpenSemaphore(string name, uint initialCount) + throw Error switch { - var handle = SemaphoreOpen(name, OCREAT, 0, 0, 0, 0, 0, 0, (uint)PosixFilePermissions.ACCESSPERMS, initialCount); - if (handle != SemFailed) - return handle; + EINVAL => new InvalidPosixSemaphoreException(), + EOVERFLOW => new SemaphoreFullException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - EINVAL => new ArgumentException($"The initial count cannot be greater than {SEMVALUEMAX}.", nameof(initialCount)), - ENAMETOOLONG => new ArgumentException($"The specified semaphore name is too long.", nameof(name)), - EACCES => new PosixSemaphoreUnauthorizedAccessException(), - EEXIST => new PosixSemaphoreExistsException(), - EINTR => new OperationCanceledException(), - ENFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open on the system."), - EMFILE => new PosixSemaphoreException("Too many semaphores or file descriptors are open by this process."), - ENOMEM => new InsufficientMemoryException(), - _ => new PosixSemaphoreException(Error), - }; - } + internal static void Close(IntPtr handle) + { + if (SemaphoreClose(handle) == 0) + return; - internal static void Release(IntPtr handle) + throw Error switch { - if (SemaphorePost(handle) == 0) - return; + EINVAL => new InvalidPosixSemaphoreException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - EINVAL => new InvalidPosixSemaphoreException(), - EOVERFLOW => new SemaphoreFullException(), - _ => new PosixSemaphoreException(Error), - }; - } + internal static void Unlink(string name) + { + if (SemaphoreUnlink(name) == 0) + return; - internal static bool Wait(IntPtr handle, int millisecondsTimeout) + throw Error switch { - if (millisecondsTimeout == Timeout.Infinite) - { - Wait(handle); - } - else - { - var start = DateTime.Now; - while (!TryWait(handle)) - { - if ((DateTime.Now - start).Milliseconds > millisecondsTimeout) - return false; - - Thread.Yield(); - } - } + ENAMETOOLONG => new ArgumentException("The specified semaphore name is too long.", nameof(name)), + EACCES => new PosixSemaphoreUnauthorizedAccessException(), + ENOENT => new PosixSemaphoreNotExistsException(), + _ => new PosixSemaphoreException(Error), + }; + } - return true; + internal static bool Wait(IntPtr handle, int millisecondsTimeout) + { + if (millisecondsTimeout == Timeout.Infinite) + { + Wait(handle); } - - private static void Wait(IntPtr handle) + else { - if (SemaphoreWait(handle) == 0) - return; - - throw Error switch + var start = DateTime.Now; + while (!TryWait(handle)) { - EINVAL => new InvalidPosixSemaphoreException(), - EDEADLK => new PosixSemaphoreException($"A deadlock was detected attempting to wait on a semaphore."), - EINTR => new OperationCanceledException(), - _ => new PosixSemaphoreException(Error), - }; + if ((DateTime.Now - start).Milliseconds > millisecondsTimeout) + return false; + + Thread.Yield(); + } } - private static bool TryWait(IntPtr handle) - { - if (SemaphoreTryWait(handle) == 0) - return true; + return true; + } - return Error switch - { - EAGAIN => false, - EINVAL => throw new InvalidPosixSemaphoreException(), - EDEADLK => throw new PosixSemaphoreException($"A deadlock was detected attempting to wait on a semaphore."), - EINTR => throw new OperationCanceledException(), - _ => throw new PosixSemaphoreException(Error), - }; - } + private static void Wait(IntPtr handle) + { + if (SemaphoreWait(handle) == 0) + return; - internal static void Close(IntPtr handle) + throw Error switch { - if (SemaphoreClose(handle) == 0) - return; + EINVAL => new InvalidPosixSemaphoreException(), + EDEADLK => new PosixSemaphoreException("A deadlock was detected attempting to wait on a semaphore."), + EINTR => new OperationCanceledException(), + _ => new PosixSemaphoreException(Error), + }; + } - throw Error switch - { - EINVAL => new InvalidPosixSemaphoreException(), - _ => new PosixSemaphoreException(Error), - }; - } + private static bool TryWait(IntPtr handle) + { + if (SemaphoreTryWait(handle) == 0) + return true; - internal static void Unlink(string name) + return Error switch { - if (SemaphoreUnlink(name) == 0) - return; - - throw Error switch - { - ENAMETOOLONG => new ArgumentException($"The specified semaphore name is too long.", nameof(name)), - EACCES => new PosixSemaphoreUnauthorizedAccessException(), - ENOENT => new PosixSemaphoreNotExistsException(), - _ => new PosixSemaphoreException(Error), - }; - } + EAGAIN => false, + EINVAL => throw new InvalidPosixSemaphoreException(), + EDEADLK => throw new PosixSemaphoreException("A deadlock was detected attempting to wait on a semaphore."), + EINTR => throw new OperationCanceledException(), + _ => throw new PosixSemaphoreException(Error), + }; } + + [LibraryImport(Lib, EntryPoint = "sem_open", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr SemaphoreOpen( + string name, + int oflag, + ulong __x2, + ulong __x3, + ulong __x4, + ulong __x5, + ulong __x6, + ulong __x7, + ulong mode, + uint value); + + [LibraryImport(Lib, EntryPoint = "sem_post", SetLastError = true)] + private static partial int SemaphorePost(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_wait", SetLastError = true)] + private static partial int SemaphoreWait(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_trywait", SetLastError = true)] + private static partial int SemaphoreTryWait(IntPtr handle); + + [LibraryImport(Lib, EntryPoint = "sem_unlink", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] + private static partial int SemaphoreUnlink(string name); + + [LibraryImport(Lib, EntryPoint = "sem_close", SetLastError = true)] + private static partial int SemaphoreClose(IntPtr handle); } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs b/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs index bb1e39c..03ae975 100644 --- a/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs +++ b/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs @@ -1,42 +1,39 @@ -using System; +namespace Cloudtoid.Interprocess.Semaphore.MacOS; -namespace Cloudtoid.Interprocess.Semaphore.MacOS +internal sealed class SemaphoreMacOS : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser { - internal class SemaphoreMacOS : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser + private const string HandleNamePrefix = "/ct.ip."; + private readonly string name; + private readonly bool deleteOnDispose; + private readonly IntPtr handle; + + internal SemaphoreMacOS(string name, bool deleteOnDispose = false) { - private const string HandleNamePrefix = "/ct.ip."; - private readonly string name; - private readonly bool deleteOnDispose; - private readonly IntPtr handle; - - internal SemaphoreMacOS(string name, bool deleteOnDispose = false) - { - this.name = name = HandleNamePrefix + name; - this.deleteOnDispose = deleteOnDispose; - handle = Interop.CreateOrOpenSemaphore(name, 0); - } - - ~SemaphoreMacOS() - => Dispose(false); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Interop.Close(handle); - - if (deleteOnDispose) - Interop.Unlink(name); - } - - public void Release() - => Interop.Release(handle); - - public bool Wait(int millisecondsTimeout) - => Interop.Wait(handle, millisecondsTimeout); + this.name = name = HandleNamePrefix + name; + this.deleteOnDispose = deleteOnDispose; + handle = Interop.CreateOrOpenSemaphore(name, 0); + } + + ~SemaphoreMacOS() => + DisposeCore(); + + public void Release() => + Interop.Release(handle); + + public bool Wait(int millisecondsTimeout) => + Interop.Wait(handle, millisecondsTimeout); + + public void Dispose() + { + DisposeCore(); + GC.SuppressFinalize(this); + } + + private void DisposeCore() + { + Interop.Close(handle); + + if (deleteOnDispose) + Interop.Unlink(name); } } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs b/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs index a277d53..0e2f529 100644 --- a/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs +++ b/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs @@ -1,28 +1,35 @@ -using System; +namespace Cloudtoid.Interprocess.Semaphore.Posix; -namespace Cloudtoid.Interprocess.Semaphore.Posix +#pragma warning disable CA1707 // Identifiers should not contain underscores + +[Flags] +internal enum PosixFilePermissions : uint { - [Flags] - internal enum PosixFilePermissions : uint - { - S_ISUID = 0x0800, // Set user ID on execution - S_ISGID = 0x0400, // Set group ID on execution - S_ISVTX = 0x0200, // Save swapped text after use (sticky). - S_IRUSR = 0x0100, // Read by owner - S_IWUSR = 0x0080, // Write by owner - S_IXUSR = 0x0040, // Execute by owner - S_IRGRP = 0x0020, // Read by group - S_IWGRP = 0x0010, // Write by group - S_IXGRP = 0x0008, // Execute by group - S_IROTH = 0x0004, // Read by other - S_IWOTH = 0x0002, // Write by other - S_IXOTH = 0x0001, // Execute by other - - S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, - S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, - S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, - ACCESSPERMS = S_IRWXU | S_IRWXG | S_IRWXO, // 0777 - ALLPERMS = S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO, // 07777 - DEFFILEMODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, // 0666 - } + S_IXOTH = 0x0001, // Execute by other + S_IWOTH = 0x0002, // Write by other + S_IROTH = 0x0004, // Read by other + + S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, + + S_IXGRP = 0x0008, // Execute by group + S_IWGRP = 0x0010, // Write by group + S_IRGRP = 0x0020, // Read by group + + S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, + + S_IXUSR = 0x0040, // Execute by owner + S_IWUSR = 0x0080, // Write by owner + S_IRUSR = 0x0100, // Read by owner + + DEFFILEMODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, // 0666 + + S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, + + ACCESSPERMS = S_IRWXU | S_IRWXG | S_IRWXO, // 0777 + + S_ISVTX = 0x0200, // Save swapped text after use (sticky). + S_ISGID = 0x0400, // Set group ID on execution + S_ISUID = 0x0800, // Set user ID on execution + + ALLPERMS = S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO // 07777 } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs b/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs index 169c5a5..b190569 100644 --- a/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs +++ b/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs @@ -1,50 +1,47 @@ -using System; +namespace Cloudtoid.Interprocess.Semaphore.Posix; -namespace Cloudtoid.Interprocess.Semaphore.Posix +internal class PosixSemaphoreException + : Exception { - internal class PosixSemaphoreException - : Exception + public PosixSemaphoreException(string message) + : base(message) { - public PosixSemaphoreException(string message) - : base(message) - { - } + } - public PosixSemaphoreException(int errorCode) - : base($"Semaphore exception with inner code = {errorCode}") - { - } + public PosixSemaphoreException(int errorCode) + : base($"Semaphore exception with inner code = {errorCode}") + { } +} - internal class InvalidPosixSemaphoreException : PosixSemaphoreException +internal sealed class InvalidPosixSemaphoreException : PosixSemaphoreException +{ + public InvalidPosixSemaphoreException() + : base("The specified semaphore does not exist or it is invalid.") { - public InvalidPosixSemaphoreException() - : base($"The specified semaphore does not exist or it is invalid.") - { - } } +} - internal class PosixSemaphoreNotExistsException : PosixSemaphoreException +internal sealed class PosixSemaphoreNotExistsException : PosixSemaphoreException +{ + public PosixSemaphoreNotExistsException() + : base("The specified semaphore does not exist.") { - public PosixSemaphoreNotExistsException() - : base($"The specified semaphore does not exist.") - { - } } +} - internal class PosixSemaphoreExistsException : PosixSemaphoreException +internal sealed class PosixSemaphoreExistsException : PosixSemaphoreException +{ + public PosixSemaphoreExistsException() + : base("A Semaphore with this name already exists") { - public PosixSemaphoreExistsException() - : base("A Semaphore with this name already exists") - { - } } +} - internal class PosixSemaphoreUnauthorizedAccessException : PosixSemaphoreException +internal sealed class PosixSemaphoreUnauthorizedAccessException : PosixSemaphoreException +{ + public PosixSemaphoreUnauthorizedAccessException() + : base("The semaphore exists, but the caller does not have permission to open it.") { - public PosixSemaphoreUnauthorizedAccessException() - : base("The semaphore exists, but the caller does not have permission to open it.") - { - } } } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Posix/PosixTimespec.cs b/src/Interprocess/Semaphore/Posix/PosixTimespec.cs index fd593ea..8e2c0f6 100644 --- a/src/Interprocess/Semaphore/Posix/PosixTimespec.cs +++ b/src/Interprocess/Semaphore/Posix/PosixTimespec.cs @@ -1,24 +1,20 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; -namespace Cloudtoid.Interprocess.Semaphore.Posix +namespace Cloudtoid.Interprocess.Semaphore.Posix; + +[StructLayout(LayoutKind.Sequential)] +internal struct PosixTimespec { - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching the exact names in Linux/MacOS")] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Matching the exact names in Linux/MacOS")] - [StructLayout(LayoutKind.Sequential)] - internal struct PosixTimespec - { - public long tv_sec; // seconds - public long tv_nsec; // nanoseconds +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter +#pragma warning disable SA1310 // Field names should not contain underscore + public long tv_sec; // seconds + public long tv_nsec; // nanoseconds +#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter +#pragma warning restore SA1310 // Field names should not contain underscore - public static implicit operator PosixTimespec(DateTimeOffset dateTime) - { - return new PosixTimespec - { - tv_sec = dateTime.ToUnixTimeSeconds(), - tv_nsec = 1000_000 * (dateTime.ToUnixTimeMilliseconds() % 1000) - }; - } - } + public static implicit operator PosixTimespec(DateTimeOffset dateTime) => new() + { + tv_sec = dateTime.ToUnixTimeSeconds(), + tv_nsec = 1000_000 * (dateTime.ToUnixTimeMilliseconds() % 1000) + }; } \ No newline at end of file diff --git a/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs b/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs index 4d11536..134b64d 100644 --- a/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs +++ b/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs @@ -1,25 +1,22 @@ using SysSemaphore = System.Threading.Semaphore; -namespace Cloudtoid.Interprocess.Semaphore.Windows +namespace Cloudtoid.Interprocess.Semaphore.Windows; + +// just a wrapper over the Windows named semaphore +internal sealed class SemaphoreWindows : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser { - // just a wrapper over the Windows named semaphore - internal sealed class SemaphoreWindows : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser - { - private const string HandleNamePrefix = @"Global\CT.IP."; - private readonly SysSemaphore handle; + private const string HandleNamePrefix = @"Global\CT.IP."; + private readonly SysSemaphore handle; - internal SemaphoreWindows(string name) - { - handle = new SysSemaphore(0, int.MaxValue, HandleNamePrefix + name); - } + internal SemaphoreWindows(string name) => + handle = new SysSemaphore(0, int.MaxValue, HandleNamePrefix + name); - public void Dispose() - => handle.Dispose(); + public void Dispose() => + handle.Dispose(); - public void Release() - => handle.Release(); + public void Release() => + handle.Release(); - public bool Wait(int millisecondsTimeout) - => handle.WaitOne(millisecondsTimeout); - } + public bool Wait(int millisecondsTimeout) => + handle.WaitOne(millisecondsTimeout); } \ No newline at end of file diff --git a/src/Interprocess/ServiceCollectionExtensions.cs b/src/Interprocess/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..9c2f4b0 --- /dev/null +++ b/src/Interprocess/ServiceCollectionExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; +using static Cloudtoid.Contract; + +namespace Cloudtoid.Interprocess; + +/// +/// Extensions to the to register the shared-memory queue. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers what is needed to create and consume shared-memory queues that are + /// cross-process accessible. + /// Use to access the queue. + /// + public static IServiceCollection AddInterprocessQueue(this IServiceCollection services) + { + CheckValue(services, nameof(services)); + + Util.Ensure64Bit(); + services.TryAddSingleton(); + return services; + } +} \ No newline at end of file diff --git a/src/Interprocess/Util.cs b/src/Interprocess/Util.cs index bca10f7..bae8b26 100644 --- a/src/Interprocess/Util.cs +++ b/src/Interprocess/Util.cs @@ -1,45 +1,29 @@ -using System; -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; -using System.Threading; -using Microsoft.Extensions.Logging; -namespace Cloudtoid.Interprocess +namespace Cloudtoid.Interprocess; + +internal static class Util { - internal static class Util + internal static void Ensure64Bit() { - internal static void Ensure64Bit() - { - if (Environment.Is64BitProcess && Environment.Is64BitOperatingSystem) - return; - - throw new NotSupportedException( - $"{Assembly.GetExecutingAssembly().GetName().Name} only supports 64-bit processor architectures."); - } + if (Environment.Is64BitProcess && Environment.Is64BitOperatingSystem) + return; - /// - /// Logs a critical error and then crashes the process - /// - internal static void FailFast( - this ILogger logger, - string message, - Exception exception) - { - logger.LogCritical(exception, message); - Environment.FailFast(message); - } + throw new NotSupportedException( + $"{Assembly.GetExecutingAssembly().GetName().Name} only supports 64-bit processor architectures."); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ThrowIfCancellationRequested( - this CancellationTokenSource source, - CancellationToken token = default) - { - // NOTE: The source could have been disposed. We can still access the IsCancellationRequested - // property BUT we cannot access its Token property. Do NOT change this code. - if (source.IsCancellationRequested) - throw new OperationCanceledException(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ThrowIfCancellationRequested( + this CancellationTokenSource source, + CancellationToken token = default) + { + // NOTE: The source could have been disposed. We can still access the IsCancellationRequested + // property BUT we cannot access its Token property. Do NOT change this code. + if (source.IsCancellationRequested) + throw new OperationCanceledException(); - token.ThrowIfCancellationRequested(); - } + token.ThrowIfCancellationRequested(); } -} +} \ No newline at end of file diff --git a/src/Sample/Publisher/Program.cs b/src/Sample/Publisher/Program.cs index 417ec66..52a8fe1 100644 --- a/src/Sample/Publisher/Program.cs +++ b/src/Sample/Publisher/Program.cs @@ -1,43 +1,44 @@ -using System.Threading; -using Cloudtoid.Interprocess; +using Cloudtoid.Interprocess; using Microsoft.Extensions.Logging; -namespace Publisher +namespace Publisher; + +internal static partial class Program { - internal class Program + internal static void Main() { - internal static void Main() - { - // Set up an optional logger factory to redirect the traces to he console + // Set up an optional logger factory to redirect the traces to he console - using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger("Publisher"); + using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger("Publisher"); - // Create the queue factory. If you are not interested in tracing the internals of - // the queue then don't pass in a loggerFactory + // Create the queue factory. If you are not interested in tracing the internals of + // the queue then don't pass in a loggerFactory - var factory = new QueueFactory(loggerFactory); + var factory = new QueueFactory(loggerFactory); - // Create a message queue publisher + // Create a message queue publisher - var options = new QueueOptions( - queueName: "sample-queue", - capacity: 1024 * 1024); + var options = new QueueOptions( + queueName: "sample-queue", + capacity: 1024 * 1024); - using var publisher = factory.CreatePublisher(options); + using var publisher = factory.CreatePublisher(options); - // Enqueue messages + // Enqueue messages - byte i = 0; - while (true) - { - logger.LogInformation("Enqueue #" + i); + byte i = 0; + while (true) + { + LogEnqueue(logger, i); - if (publisher.TryEnqueue(new byte[] { i })) - i++; + if (publisher.TryEnqueue([i])) + i++; - Thread.Yield(); - } + Thread.Yield(); } } -} + + [LoggerMessage(Level = LogLevel.Information, Message = "Enqueue #{i}")] + private static partial void LogEnqueue(ILogger logger, int i); +} \ No newline at end of file diff --git a/src/Sample/Publisher/Publisher.csproj b/src/Sample/Publisher/Publisher.csproj index 03f81bc..db337d9 100644 --- a/src/Sample/Publisher/Publisher.csproj +++ b/src/Sample/Publisher/Publisher.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Sample/Subscriber/Program.cs b/src/Sample/Subscriber/Program.cs index c8eb15c..ea2c90d 100644 --- a/src/Sample/Subscriber/Program.cs +++ b/src/Sample/Subscriber/Program.cs @@ -1,38 +1,40 @@ using Cloudtoid.Interprocess; using Microsoft.Extensions.Logging; -namespace Subscriber +namespace Subscriber; + +internal static partial class Program { - internal class Program + internal static void Main() { - internal static void Main() - { - // Set up an optional logger factory to redirect the traces to he console + // Set up an optional logger factory to redirect the traces to he console - using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger("Subscriber"); + using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger("Subscriber"); - // Create the queue factory. If you are not interested in tracing the internals of - // the queue then don't pass in a loggerFactory + // Create the queue factory. If you are not interested in tracing the internals of + // the queue then don't pass in a loggerFactory - var factory = new QueueFactory(loggerFactory); + var factory = new QueueFactory(loggerFactory); - // Create a message queue publisher + // Create a message queue publisher - var options = new QueueOptions( - queueName: "sample-queue", - capacity: 1024 * 1024); + var options = new QueueOptions( + queueName: "sample-queue", + capacity: 1024 * 1024); - using var subscriber = factory.CreateSubscriber(options); + using var subscriber = factory.CreateSubscriber(options); - // Dequeue messages - var messageBuffer = new byte[1]; + // Dequeue messages + var messageBuffer = new byte[1]; - while (true) - { - if (subscriber.TryDequeue(messageBuffer, default, out var message)) - logger.LogInformation("Dequeue #" + messageBuffer[0]); - } + while (true) + { + if (subscriber.TryDequeue(messageBuffer, default, out var message)) + LogDequeue(logger, messageBuffer[0]); } } -} + + [LoggerMessage(Level = LogLevel.Information, Message = "Dequeue #{i}")] + private static partial void LogDequeue(ILogger logger, int i); +} \ No newline at end of file From 1886c48270fa5a5810c3d1971d4a6770f72344c2 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:04:47 -0800 Subject: [PATCH 02/11] one more --- .vscode/launch.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b3ec532 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "C#: Publisher Sample", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Sample/Publisher/Publisher.csproj" + }, + { + "name": "C#: Subscriber Sample", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Sample/Subscriber/Subscriber.csproj" + } + ], + "compounds": [ + { + "name": "Both Projects", + "configurations": [ + "C#: Publisher Sample", + "C#: Subscriber Sample" + ] + } + ] +} \ No newline at end of file From b9085378e7d5131956c725c9c07e0622f83d0071 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:15:23 -0800 Subject: [PATCH 03/11] more --- .vscode/launch.json | 10 +++++----- src/Sample/Publisher/Program.cs | 14 ++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b3ec532..d5eda5c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,13 +5,13 @@ "version": "0.2.0", "configurations": [ { - "name": "C#: Publisher Sample", + "name": "Publisher Sample", "type": "dotnet", "request": "launch", "projectPath": "${workspaceFolder}/src/Sample/Publisher/Publisher.csproj" }, { - "name": "C#: Subscriber Sample", + "name": "Subscriber Sample", "type": "dotnet", "request": "launch", "projectPath": "${workspaceFolder}/src/Sample/Subscriber/Subscriber.csproj" @@ -19,10 +19,10 @@ ], "compounds": [ { - "name": "Both Projects", + "name": "Full Sample", "configurations": [ - "C#: Publisher Sample", - "C#: Subscriber Sample" + "Publisher Sample", + "Subscriber Sample" ] } ] diff --git a/src/Sample/Publisher/Program.cs b/src/Sample/Publisher/Program.cs index 52a8fe1..3423c83 100644 --- a/src/Sample/Publisher/Program.cs +++ b/src/Sample/Publisher/Program.cs @@ -5,7 +5,7 @@ namespace Publisher; internal static partial class Program { - internal static void Main() + internal static async Task Main() { // Set up an optional logger factory to redirect the traces to he console @@ -27,15 +27,13 @@ internal static void Main() // Enqueue messages - byte i = 0; + int i = 0; while (true) { - LogEnqueue(logger, i); - - if (publisher.TryEnqueue([i])) - i++; - - Thread.Yield(); + if (publisher.TryEnqueue([(byte)(i % 256)])) + LogEnqueue(logger, i++); + else + await Task.Delay(100); } } From edc70d5f825378f350a446a7e24f02849a1187a4 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:21:01 -0800 Subject: [PATCH 04/11] one more --- src/Interprocess.Benchmark/Program.cs | 2 +- src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs | 2 +- src/Interprocess.Benchmark/Queue/QueueBenchmark.cs | 2 +- src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs | 2 +- src/Interprocess.Tests/CircularBufferTests.cs | 2 +- src/Interprocess.Tests/Properties/AssemblyInfo.cs | 2 +- src/Interprocess.Tests/QueueTests.cs | 2 +- src/Interprocess.Tests/SemaphoreTests.cs | 2 +- src/Interprocess.Tests/Utils/FactAttribute.cs | 2 +- src/Interprocess.Tests/Utils/Platform.cs | 2 +- src/Interprocess.Tests/Utils/RepeatAttribute.cs | 2 +- src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs | 2 +- src/Interprocess.Tests/Utils/UniquePathFixture.cs | 2 +- src/Interprocess.Tests/Utils/XunitLogger.cs | 2 +- src/Interprocess.Tests/Utils/XunitLoggerProvider.cs | 2 +- src/Interprocess/Contracts/IPublisher.cs | 2 +- src/Interprocess/Contracts/IQueueFactory.cs | 2 +- src/Interprocess/Contracts/ISubscriber.cs | 2 +- src/Interprocess/Contracts/MessageHeader.cs | 2 +- src/Interprocess/Contracts/QueueHeader.cs | 2 +- src/Interprocess/Contracts/QueueOptions.cs | 2 +- src/Interprocess/Log.cs | 2 +- src/Interprocess/Memory/CircularBuffer.cs | 2 +- src/Interprocess/Memory/IMemoryFile.cs | 2 +- src/Interprocess/Memory/MemoryFileUnix.cs | 2 +- src/Interprocess/Memory/MemoryFileWindows.cs | 2 +- src/Interprocess/Memory/MemoryView.cs | 2 +- src/Interprocess/Properties/AssemblyInfo.cs | 2 +- src/Interprocess/Queue/Publisher.cs | 2 +- src/Interprocess/Queue/Queue.cs | 2 +- src/Interprocess/Queue/QueueFactory.cs | 2 +- src/Interprocess/Queue/Subscriber.cs | 2 +- src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs | 2 +- src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs | 2 +- src/Interprocess/Semaphore/InterprocessSemaphore.cs | 2 +- src/Interprocess/Semaphore/Linux/Interop.cs | 2 +- src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs | 2 +- src/Interprocess/Semaphore/MacOS/Interop.cs | 2 +- src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs | 2 +- src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs | 2 +- src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs | 2 +- src/Interprocess/Semaphore/Posix/PosixTimespec.cs | 2 +- src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs | 2 +- src/Interprocess/ServiceCollectionExtensions.cs | 2 +- src/Interprocess/Util.cs | 2 +- src/Sample/Publisher/Program.cs | 2 +- src/Sample/Subscriber/Program.cs | 2 +- 47 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/Interprocess.Benchmark/Program.cs b/src/Interprocess.Benchmark/Program.cs index b176e0f..d466157 100644 --- a/src/Interprocess.Benchmark/Program.cs +++ b/src/Interprocess.Benchmark/Program.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; namespace Cloudtoid.Interprocess.Benchmark; diff --git a/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs b/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs index bf2eb3e..2164d68 100644 --- a/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/EnqueueBenchmark.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; namespace Cloudtoid.Interprocess.Benchmark; diff --git a/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs b/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs index 29b21e0..a40330f 100644 --- a/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/QueueBenchmark.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; namespace Cloudtoid.Interprocess.Benchmark; diff --git a/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs b/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs index 1116f8a..bed7be0 100644 --- a/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs +++ b/src/Interprocess.Benchmark/Queue/QueueExtendedBenchmark.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; namespace Cloudtoid.Interprocess.Benchmark; diff --git a/src/Interprocess.Tests/CircularBufferTests.cs b/src/Interprocess.Tests/CircularBufferTests.cs index e404e6c..f954135 100644 --- a/src/Interprocess.Tests/CircularBufferTests.cs +++ b/src/Interprocess.Tests/CircularBufferTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Xunit; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess.Tests/Properties/AssemblyInfo.cs b/src/Interprocess.Tests/Properties/AssemblyInfo.cs index 669ff39..94c43b9 100644 --- a/src/Interprocess.Tests/Properties/AssemblyInfo.cs +++ b/src/Interprocess.Tests/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; [assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/src/Interprocess.Tests/QueueTests.cs b/src/Interprocess.Tests/QueueTests.cs index 9d6ebfb..160541a 100644 --- a/src/Interprocess.Tests/QueueTests.cs +++ b/src/Interprocess.Tests/QueueTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit; diff --git a/src/Interprocess.Tests/SemaphoreTests.cs b/src/Interprocess.Tests/SemaphoreTests.cs index 4c6ecbe..7139a83 100644 --- a/src/Interprocess.Tests/SemaphoreTests.cs +++ b/src/Interprocess.Tests/SemaphoreTests.cs @@ -1,4 +1,4 @@ -using Cloudtoid.Interprocess.Semaphore.Linux; +using Cloudtoid.Interprocess.Semaphore.Linux; using Cloudtoid.Interprocess.Semaphore.MacOS; using FluentAssertions; diff --git a/src/Interprocess.Tests/Utils/FactAttribute.cs b/src/Interprocess.Tests/Utils/FactAttribute.cs index 3f602fc..691dc75 100644 --- a/src/Interprocess.Tests/Utils/FactAttribute.cs +++ b/src/Interprocess.Tests/Utils/FactAttribute.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess.Tests/Utils/Platform.cs b/src/Interprocess.Tests/Utils/Platform.cs index 753d3db..7e11235 100644 --- a/src/Interprocess.Tests/Utils/Platform.cs +++ b/src/Interprocess.Tests/Utils/Platform.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Tests; +namespace Cloudtoid.Interprocess.Tests; [Flags] public enum Platform diff --git a/src/Interprocess.Tests/Utils/RepeatAttribute.cs b/src/Interprocess.Tests/Utils/RepeatAttribute.cs index 99383ce..a80f2d8 100644 --- a/src/Interprocess.Tests/Utils/RepeatAttribute.cs +++ b/src/Interprocess.Tests/Utils/RepeatAttribute.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Xunit.Sdk; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs b/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs index 2220f7a..fbddda9 100644 --- a/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs +++ b/src/Interprocess.Tests/Utils/TestBeforeAfterAttribute.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Xunit.Sdk; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess.Tests/Utils/UniquePathFixture.cs b/src/Interprocess.Tests/Utils/UniquePathFixture.cs index 9657d8f..a92bbcc 100644 --- a/src/Interprocess.Tests/Utils/UniquePathFixture.cs +++ b/src/Interprocess.Tests/Utils/UniquePathFixture.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Tests; +namespace Cloudtoid.Interprocess.Tests; public class UniquePathFixture : IDisposable { diff --git a/src/Interprocess.Tests/Utils/XunitLogger.cs b/src/Interprocess.Tests/Utils/XunitLogger.cs index f197306..849167d 100644 --- a/src/Interprocess.Tests/Utils/XunitLogger.cs +++ b/src/Interprocess.Tests/Utils/XunitLogger.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs b/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs index 7c28b84..cb3b075 100644 --- a/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs +++ b/src/Interprocess.Tests/Utils/XunitLoggerProvider.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace Cloudtoid.Interprocess.Tests; diff --git a/src/Interprocess/Contracts/IPublisher.cs b/src/Interprocess/Contracts/IPublisher.cs index 1b5da59..7224e8e 100644 --- a/src/Interprocess/Contracts/IPublisher.cs +++ b/src/Interprocess/Contracts/IPublisher.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess; +namespace Cloudtoid.Interprocess; /// /// Message publisher that publishes messages to the subscribers. diff --git a/src/Interprocess/Contracts/IQueueFactory.cs b/src/Interprocess/Contracts/IQueueFactory.cs index 3b37007..cafbeb2 100644 --- a/src/Interprocess/Contracts/IQueueFactory.cs +++ b/src/Interprocess/Contracts/IQueueFactory.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess; +namespace Cloudtoid.Interprocess; /// Factory to create queue publishers and subscribers. public interface IQueueFactory diff --git a/src/Interprocess/Contracts/ISubscriber.cs b/src/Interprocess/Contracts/ISubscriber.cs index b06f013..d074321 100644 --- a/src/Interprocess/Contracts/ISubscriber.cs +++ b/src/Interprocess/Contracts/ISubscriber.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Contracts/MessageHeader.cs b/src/Interprocess/Contracts/MessageHeader.cs index c692cba..29b1392 100644 --- a/src/Interprocess/Contracts/MessageHeader.cs +++ b/src/Interprocess/Contracts/MessageHeader.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Contracts/QueueHeader.cs b/src/Interprocess/Contracts/QueueHeader.cs index 2d64f38..a9a2922 100644 --- a/src/Interprocess/Contracts/QueueHeader.cs +++ b/src/Interprocess/Contracts/QueueHeader.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Contracts/QueueOptions.cs b/src/Interprocess/Contracts/QueueOptions.cs index 4f0d143..fffb216 100644 --- a/src/Interprocess/Contracts/QueueOptions.cs +++ b/src/Interprocess/Contracts/QueueOptions.cs @@ -1,4 +1,4 @@ -using static Cloudtoid.Contract; +using static Cloudtoid.Contract; using SysPath = System.IO.Path; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Log.cs b/src/Interprocess/Log.cs index 6f8bed4..a0c6ecb 100644 --- a/src/Interprocess/Log.cs +++ b/src/Interprocess/Log.cs @@ -1,4 +1,4 @@ -using Cloudtoid.Interprocess.Memory.Unix; +using Cloudtoid.Interprocess.Memory.Unix; using Microsoft.Extensions.Logging; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Memory/CircularBuffer.cs b/src/Interprocess/Memory/CircularBuffer.cs index 5dded88..ff5fbd2 100644 --- a/src/Interprocess/Memory/CircularBuffer.cs +++ b/src/Interprocess/Memory/CircularBuffer.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Memory/IMemoryFile.cs b/src/Interprocess/Memory/IMemoryFile.cs index 49e6fc0..6eb7f7c 100644 --- a/src/Interprocess/Memory/IMemoryFile.cs +++ b/src/Interprocess/Memory/IMemoryFile.cs @@ -1,4 +1,4 @@ -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Memory/MemoryFileUnix.cs b/src/Interprocess/Memory/MemoryFileUnix.cs index b16ba62..2f8611e 100644 --- a/src/Interprocess/Memory/MemoryFileUnix.cs +++ b/src/Interprocess/Memory/MemoryFileUnix.cs @@ -1,4 +1,4 @@ -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; using Microsoft.Extensions.Logging; namespace Cloudtoid.Interprocess.Memory.Unix; diff --git a/src/Interprocess/Memory/MemoryFileWindows.cs b/src/Interprocess/Memory/MemoryFileWindows.cs index 82e13fd..635b81d 100644 --- a/src/Interprocess/Memory/MemoryFileWindows.cs +++ b/src/Interprocess/Memory/MemoryFileWindows.cs @@ -1,4 +1,4 @@ -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; namespace Cloudtoid.Interprocess.Memory.Windows; diff --git a/src/Interprocess/Memory/MemoryView.cs b/src/Interprocess/Memory/MemoryView.cs index aac68dd..9b2cc2e 100644 --- a/src/Interprocess/Memory/MemoryView.cs +++ b/src/Interprocess/Memory/MemoryView.cs @@ -1,4 +1,4 @@ -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Memory.Unix; using Cloudtoid.Interprocess.Memory.Windows; diff --git a/src/Interprocess/Properties/AssemblyInfo.cs b/src/Interprocess/Properties/AssemblyInfo.cs index b394cfd..8b165e3 100644 --- a/src/Interprocess/Properties/AssemblyInfo.cs +++ b/src/Interprocess/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Cloudtoid.Interprocess.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // needed by NSubstitute \ No newline at end of file diff --git a/src/Interprocess/Queue/Publisher.cs b/src/Interprocess/Queue/Publisher.cs index 49ad859..51f8eac 100644 --- a/src/Interprocess/Queue/Publisher.cs +++ b/src/Interprocess/Queue/Publisher.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Queue/Queue.cs b/src/Interprocess/Queue/Queue.cs index fd030d9..4490400 100644 --- a/src/Interprocess/Queue/Queue.cs +++ b/src/Interprocess/Queue/Queue.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Queue/QueueFactory.cs b/src/Interprocess/Queue/QueueFactory.cs index e6d3e29..2e72dda 100644 --- a/src/Interprocess/Queue/QueueFactory.cs +++ b/src/Interprocess/Queue/QueueFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using static Cloudtoid.Contract; diff --git a/src/Interprocess/Queue/Subscriber.cs b/src/Interprocess/Queue/Subscriber.cs index b4c15fb..9a838bb 100644 --- a/src/Interprocess/Queue/Subscriber.cs +++ b/src/Interprocess/Queue/Subscriber.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs b/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs index 76693a4..dc446c8 100644 --- a/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs +++ b/src/Interprocess/Semaphore/IInterprocessSemaphoreReleaser.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess; +namespace Cloudtoid.Interprocess; internal interface IInterprocessSemaphoreReleaser : IDisposable { diff --git a/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs b/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs index 241f573..5da1087 100644 --- a/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs +++ b/src/Interprocess/Semaphore/IInterprocessSemaphoreWaiter.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess; +namespace Cloudtoid.Interprocess; internal interface IInterprocessSemaphoreWaiter : IDisposable { diff --git a/src/Interprocess/Semaphore/InterprocessSemaphore.cs b/src/Interprocess/Semaphore/InterprocessSemaphore.cs index f0a8ac3..210e79b 100644 --- a/src/Interprocess/Semaphore/InterprocessSemaphore.cs +++ b/src/Interprocess/Semaphore/InterprocessSemaphore.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Semaphore.Linux; using Cloudtoid.Interprocess.Semaphore.MacOS; using Cloudtoid.Interprocess.Semaphore.Windows; diff --git a/src/Interprocess/Semaphore/Linux/Interop.cs b/src/Interprocess/Semaphore/Linux/Interop.cs index 2347418..eff6a84 100644 --- a/src/Interprocess/Semaphore/Linux/Interop.cs +++ b/src/Interprocess/Semaphore/Linux/Interop.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Semaphore.Posix; namespace Cloudtoid.Interprocess.Semaphore.Linux; diff --git a/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs b/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs index cdc06bf..52f14de 100644 --- a/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs +++ b/src/Interprocess/Semaphore/Linux/SemaphoreLinux.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Semaphore.Linux; +namespace Cloudtoid.Interprocess.Semaphore.Linux; internal sealed class SemaphoreLinux : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser { diff --git a/src/Interprocess/Semaphore/MacOS/Interop.cs b/src/Interprocess/Semaphore/MacOS/Interop.cs index d62a905..be70543 100644 --- a/src/Interprocess/Semaphore/MacOS/Interop.cs +++ b/src/Interprocess/Semaphore/MacOS/Interop.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using Cloudtoid.Interprocess.Semaphore.Posix; namespace Cloudtoid.Interprocess.Semaphore.MacOS; diff --git a/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs b/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs index 03ae975..c1881fe 100644 --- a/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs +++ b/src/Interprocess/Semaphore/MacOS/SemaphoreMacOS.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Semaphore.MacOS; +namespace Cloudtoid.Interprocess.Semaphore.MacOS; internal sealed class SemaphoreMacOS : IInterprocessSemaphoreWaiter, IInterprocessSemaphoreReleaser { diff --git a/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs b/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs index 0e2f529..b0cb468 100644 --- a/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs +++ b/src/Interprocess/Semaphore/Posix/PosixFilePermissions.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Semaphore.Posix; +namespace Cloudtoid.Interprocess.Semaphore.Posix; #pragma warning disable CA1707 // Identifiers should not contain underscores diff --git a/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs b/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs index b190569..ae7e044 100644 --- a/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs +++ b/src/Interprocess/Semaphore/Posix/PosixSemaphoreException.cs @@ -1,4 +1,4 @@ -namespace Cloudtoid.Interprocess.Semaphore.Posix; +namespace Cloudtoid.Interprocess.Semaphore.Posix; internal class PosixSemaphoreException : Exception diff --git a/src/Interprocess/Semaphore/Posix/PosixTimespec.cs b/src/Interprocess/Semaphore/Posix/PosixTimespec.cs index 8e2c0f6..2e27c95 100644 --- a/src/Interprocess/Semaphore/Posix/PosixTimespec.cs +++ b/src/Interprocess/Semaphore/Posix/PosixTimespec.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Cloudtoid.Interprocess.Semaphore.Posix; diff --git a/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs b/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs index 134b64d..24789a3 100644 --- a/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs +++ b/src/Interprocess/Semaphore/Windows/SemaphoreWindows.cs @@ -1,4 +1,4 @@ -using SysSemaphore = System.Threading.Semaphore; +using SysSemaphore = System.Threading.Semaphore; namespace Cloudtoid.Interprocess.Semaphore.Windows; diff --git a/src/Interprocess/ServiceCollectionExtensions.cs b/src/Interprocess/ServiceCollectionExtensions.cs index 9c2f4b0..3e16054 100644 --- a/src/Interprocess/ServiceCollectionExtensions.cs +++ b/src/Interprocess/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using static Cloudtoid.Contract; namespace Cloudtoid.Interprocess; diff --git a/src/Interprocess/Util.cs b/src/Interprocess/Util.cs index bae8b26..c76fc88 100644 --- a/src/Interprocess/Util.cs +++ b/src/Interprocess/Util.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; namespace Cloudtoid.Interprocess; diff --git a/src/Sample/Publisher/Program.cs b/src/Sample/Publisher/Program.cs index 3423c83..ce3cfa4 100644 --- a/src/Sample/Publisher/Program.cs +++ b/src/Sample/Publisher/Program.cs @@ -1,4 +1,4 @@ -using Cloudtoid.Interprocess; +using Cloudtoid.Interprocess; using Microsoft.Extensions.Logging; namespace Publisher; diff --git a/src/Sample/Subscriber/Program.cs b/src/Sample/Subscriber/Program.cs index ea2c90d..c3ba64a 100644 --- a/src/Sample/Subscriber/Program.cs +++ b/src/Sample/Subscriber/Program.cs @@ -1,4 +1,4 @@ -using Cloudtoid.Interprocess; +using Cloudtoid.Interprocess; using Microsoft.Extensions.Logging; namespace Subscriber; From 4005dd3b094df3118f6f5d8d8706faca4c2fdd1f Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:22:34 -0800 Subject: [PATCH 05/11] one more --- src/.editorconfig | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/.editorconfig b/src/.editorconfig index 9983788..c697be3 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -3,48 +3,41 @@ root = true # Don't use tabs for indentation. [*] +end_of_line = lf indent_style = space # XML project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] -end_of_line = lf indent_size = 2 trim_trailing_whitespace = true # XML config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] -end_of_line = lf indent_size = 2 trim_trailing_whitespace = true [*.{liquid}] -end_of_line = lf indent_size = 4 # Powershell files [*.ps1] -end_of_line = lf indent_size = 2 # Shell script files [*.sh] -end_of_line = lf indent_size = 2 # JSON files [*.json] -end_of_line = lf indent_size = 2 trim_trailing_whitespace = true [*.{js,jsx,ts,tsx}] -end_of_line = lf indent_size = 2 trim_trailing_whitespace = true # Code files [*.{cs,csx,vb,vbx}] -end_of_line = lf indent_size = 4 trim_trailing_whitespace = true tab_width = 4 From 0c9647b79cb26983af6bc9bdc0485d4d08586a25 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:26:58 -0800 Subject: [PATCH 06/11] one more --- src/.editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/.editorconfig b/src/.editorconfig index c697be3..8d76caf 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -543,7 +543,7 @@ dotnet_diagnostic.RCS0032.severity = error dotnet_diagnostic.RCS0033.severity = error dotnet_diagnostic.RCS0041.severity = error dotnet_diagnostic.RCS0042.severity = error -dotnet_diagnostic.RCS0045.severity = error +dotnet_diagnostic.RCS0045.severity = none dotnet_diagnostic.RCS0046.severity = error dotnet_diagnostic.RCS0050.severity = error dotnet_diagnostic.RCS0051.severity = error From c7922eeb23c9f42e513cef8ed24e7e5091afe5b3 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:29:15 -0800 Subject: [PATCH 07/11] one more --- src/Interprocess/Semaphore/MacOS/Interop.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Interprocess/Semaphore/MacOS/Interop.cs b/src/Interprocess/Semaphore/MacOS/Interop.cs index be70543..a70d84c 100644 --- a/src/Interprocess/Semaphore/MacOS/Interop.cs +++ b/src/Interprocess/Semaphore/MacOS/Interop.cs @@ -37,8 +37,7 @@ internal static IntPtr CreateOrOpenSemaphore(string name, uint initialCount) throw Error switch { EINVAL => new ArgumentException( - $"The initial count cannot be greater than {SEMVALUEMAX}.", - nameof(initialCount)), + $"One of the arguments passed to sem_open is invalid. Please also ensure {nameof(initialCount)} is less than {SEMVALUEMAX}."), ENAMETOOLONG => new ArgumentException("The specified semaphore name is too long.", nameof(name)), EACCES => new PosixSemaphoreUnauthorizedAccessException(), EEXIST => new PosixSemaphoreExistsException(), From d4fb234fc9292210c6094e961aefce2a3fd42aad Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:48:14 -0800 Subject: [PATCH 08/11] one more --- src/Sample/Subscriber/Subscriber.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sample/Subscriber/Subscriber.csproj b/src/Sample/Subscriber/Subscriber.csproj index 03f81bc..db337d9 100644 --- a/src/Sample/Subscriber/Subscriber.csproj +++ b/src/Sample/Subscriber/Subscriber.csproj @@ -5,7 +5,7 @@ - + From bbb4e998833709b631c4b0f1b75156bd5a6133cd Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:52:33 -0800 Subject: [PATCH 09/11] one more --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 939d131..c89ed05 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,6 @@ A lot has gone into optimizing the implementation of this library. For instance, | Method | Description | |------------------------------------------------ |-------------- | -| Message enqueue | Benchmarks the performance of enqueuing a message. | | Message enqueue and dequeue | Benchmarks the performance of sending a message to a client and receiving that message. It is inclusive of the duration to enqueue and dequeue a message. | | Message enqueue and dequeue - no message buffer | Benchmarks the performance of sending a message to a client and receiving that message. It is inclusive of the duration to enqueue and dequeue a message and memory allocation for the received message. | @@ -117,12 +116,6 @@ You can replicate the results by running the following command: dotnet run Interprocess.Benchmark.csproj -c Release ``` -You can also be explicit about the .NET SDK and Runtime(s) versions: - -```sh -dotnet run Interprocess.Benchmark.csproj -c Release -f net9.0 --runtimes net9.0 -``` - --- ### On Windows @@ -141,7 +134,6 @@ Results: | Method | Mean (ns) | Error (ns) | StdDev (ns) | Allocated | |------------------------------------------------ |----------:|-----------:|------------:|----------:| -| Message enqueue | `192.7`| `3.61`| `3.21`| `-` | | Message enqueue and dequeue | `305.6`| `5.96`| `6.62`| `-` | | Message enqueue and dequeue - no message buffer | `311.5`| `5.90`| `9.85`| `32 B` | @@ -182,7 +174,6 @@ Results: | Method | Mean (ns) | Error (ns) | StdDev (ns) | Allocated | |------------------------------------------------ |----------:|-----------:|------------:|----------:| -| Message enqueue | `5.3`| `-`| `-`| `-`| | Message enqueue and dequeue | `169.9`| `3.08`| `4.01`| `-`| | Message enqueue and dequeue - no message buffer | `179.4`| `1.91`| `1.60`| `32 B`| From e14646fe51755b01cb1dc9534e1454073f1f5a40 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:53:00 -0800 Subject: [PATCH 10/11] one more --- .vscode/launch.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d5eda5c..94ba145 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,14 +16,5 @@ "request": "launch", "projectPath": "${workspaceFolder}/src/Sample/Subscriber/Subscriber.csproj" } - ], - "compounds": [ - { - "name": "Full Sample", - "configurations": [ - "Publisher Sample", - "Subscriber Sample" - ] - } ] } \ No newline at end of file From cc2894875e87212a6e3ebe45e8fbde7f16e2cad6 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 11 Jan 2025 22:56:36 -0800 Subject: [PATCH 11/11] one more --- .github/workflows/publish.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e801455..d3681f5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,11 +19,8 @@ jobs: dotnet-version: | 9.0.x - - name: Build - Debug - run: dotnet build src/Interprocess.sln - - name: Test - Debug - run: dotnet test --no-build --verbosity=detailed src/Interprocess.sln + run: dotnet test src/Interprocess.sln - name: Build - Release run: dotnet build -c Release src/Interprocess.sln