From 3479cbc7003993511a0d53391270dc8d83e6eaec Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 5 Jan 2024 07:51:16 -0600 Subject: [PATCH] Updated editor config with standard formatting --- .editorconfig | 257 +++---- .../Jobs/EvenMinutesJob.cs | 12 +- .../Jobs/EveryMinuteJob.cs | 12 +- .../Jobs/Sample1Job.cs | 14 +- .../Jobs/Sample2Job.cs | 17 +- .../MyCriticalHealthCheck.cs | 9 +- samples/Foundatio.HostingSample/Program.cs | 67 +- .../Startup/MyStartupAction.cs | 15 +- .../Startup/OtherStartupAction.cs | 15 +- src/Foundatio.AppMetrics/AppMetricsClient.cs | 18 +- .../DataProtectionBuilderExtensions.cs | 30 +- .../FoundatioStorageXmlRepository.cs | 37 +- .../Cronos/CalendarHelper.cs | 10 +- .../Cronos/CronExpression.cs | 34 +- .../Cronos/CronExpressionFlag.cs | 6 +- .../Cronos/CronField.cs | 2 +- .../Cronos/CronFormat.cs | 2 +- .../Cronos/CronFormatException.cs | 2 +- .../Jobs/HostedJobOptions.cs | 6 +- .../Jobs/HostedJobService.cs | 67 +- .../Jobs/JobHostExtensions.cs | 70 +- .../Jobs/JobOptionsBuilder.cs | 51 +- .../Jobs/ScheduledJobRegistration.cs | 9 +- .../Jobs/ScheduledJobService.cs | 45 +- .../ShutdownHostIfNoJobsRunningService.cs | 34 +- .../Startup/IStartupAction.cs | 6 +- .../Startup/RunStartupActionsService.cs | 14 +- .../Startup/StartupActionRegistration.cs | 31 +- .../Startup/StartupActionsContext.cs | 25 +- .../Startup/StartupExtensions.cs | 97 ++- .../Startup/StartupHealthcheck.cs | 20 +- .../Startup/StartupPriorityAttribute.cs | 11 +- ...pActionsBeforeServingRequestsMiddleware.cs | 30 +- src/Foundatio.JsonNet/JsonNetSerializer.cs | 15 +- .../MessagePackSerializer.cs | 17 +- src/Foundatio.MetricsNET/MetricsNETClient.cs | 19 +- .../Caching/CacheClientTestsBase.cs | 307 ++++++--- .../Caching/HybridCacheClientTests.cs | 89 ++- .../Extensions/TaskExtensions.cs | 19 +- .../Jobs/HelloWorldJob.cs | 37 +- .../Jobs/JobQueueTestsBase.cs | 67 +- .../Jobs/SampleQueueJob.cs | 63 +- .../Jobs/ThrottledJob.cs | 19 +- .../Jobs/WithDependencyJob.cs | 15 +- .../Jobs/WithLockingJob.cs | 17 +- .../Locks/LockTestBase.cs | 113 ++-- .../Messaging/MessageBusTestBase.cs | 373 ++++++---- .../Messaging/Samples.cs | 33 +- .../Metrics/DiagnosticsMetricsCollector.cs | 212 ++++-- .../Metrics/MetricsClientTestBase.cs | 63 +- .../Queue/QueueTestBase.cs | 639 ++++++++++++------ src/Foundatio.TestHarness/Queue/Samples.cs | 6 +- .../Serializer/SerializerTestsBase.cs | 57 +- .../Storage/FileStorageTestsBase.cs | 232 ++++--- .../Utility/BenchmarkToJson.cs | 28 +- .../Utility/Configuration.cs | 21 +- .../Utility/NonSeekableStream.cs | 35 +- src/Foundatio.Utf8Json/Utf8JsonSerializer.cs | 17 +- src/Foundatio.Xunit/Logging/LogEntry.cs | 17 +- src/Foundatio.Xunit/Logging/TestLogger.cs | 46 +- .../Logging/TestLoggerFactory.cs | 48 +- .../Logging/TestWithLoggingBase.cs | 11 +- .../Retry/DelayedMessageBus.cs | 15 +- src/Foundatio.Xunit/Retry/RetryAttribute.cs | 9 +- .../Retry/RetryFactDiscoverer.cs | 12 +- src/Foundatio.Xunit/Retry/RetryTestCase.cs | 28 +- .../Retry/RetryTheoryAttribute.cs | 9 +- .../Retry/RetryTheoryDiscoverer.cs | 12 +- .../Retry/RetryTheoryTestCase.cs | 24 +- src/Foundatio/Caching/CacheValue.cs | 14 +- src/Foundatio/Caching/HybridCacheClient.cs | 135 ++-- src/Foundatio/Caching/ICacheClient.cs | 6 +- src/Foundatio/Caching/InMemoryCacheClient.cs | 395 +++++++---- .../Caching/InMemoryCacheClientOptions.cs | 24 +- src/Foundatio/Caching/NullCacheClient.cs | 89 ++- src/Foundatio/Caching/ScopedCacheClient.cs | 102 ++- .../DeepCloner/DeepClonerExtensions.cs | 128 ++-- .../Helpers/ClonerToExprGenerator.cs | 556 +++++++-------- .../DeepCloner/Helpers/DeepCloneState.cs | 400 +++++------ .../DeepCloner/Helpers/DeepClonerCache.cs | 182 ++--- .../Helpers/DeepClonerExprGenerator.cs | 486 ++++++------- .../DeepCloner/Helpers/DeepClonerGenerator.cs | 438 ++++++------ .../DeepCloner/Helpers/DeepClonerSafeTypes.cs | 234 +++---- .../DeepCloner/Helpers/ReflectionHelper.cs | 168 ++--- .../Helpers/ShallowClonerGenerator.cs | 44 +- .../DeepCloner/Helpers/ShallowObjectCloner.cs | 98 +-- .../Extensions/CacheClientExtensions.cs | 106 ++- .../Extensions/CollectionExtensions.cs | 12 +- .../ConcurrentDictionaryExtensions.cs | 14 +- .../Extensions/ConcurrentQueueExtensions.cs | 9 +- .../Extensions/DateTimeExtensions.cs | 29 +- src/Foundatio/Extensions/EnumExtensions.cs | 20 +- .../Extensions/EnumerableExtensions.cs | 14 +- .../Extensions/ExceptionExtensions.cs | 14 +- src/Foundatio/Extensions/LoggerExtensions.cs | 69 +- src/Foundatio/Extensions/NumberExtensions.cs | 33 +- src/Foundatio/Extensions/ObjectExtensions.cs | 11 +- src/Foundatio/Extensions/StringExtensions.cs | 13 +- src/Foundatio/Extensions/TaskExtensions.cs | 24 +- .../Extensions/TimespanExtensions.cs | 26 +- src/Foundatio/Extensions/TypeExtensions.cs | 66 +- src/Foundatio/Jobs/IJob.cs | 48 +- src/Foundatio/Jobs/IQueueJob.cs | 19 +- src/Foundatio/Jobs/JobAttribute.cs | 8 +- src/Foundatio/Jobs/JobBase.cs | 14 +- src/Foundatio/Jobs/JobContext.cs | 14 +- src/Foundatio/Jobs/JobOptions.cs | 44 +- src/Foundatio/Jobs/JobResult.cs | 50 +- src/Foundatio/Jobs/JobRunner.cs | 149 ++-- src/Foundatio/Jobs/JobWithLockBase.cs | 26 +- src/Foundatio/Jobs/QueueEntryContext.cs | 16 +- src/Foundatio/Jobs/QueueJobBase.cs | 79 ++- .../Jobs/WorkItemJob/WorkItemContext.cs | 20 +- .../Jobs/WorkItemJob/WorkItemData.cs | 8 +- .../Jobs/WorkItemJob/WorkItemHandlers.cs | 62 +- src/Foundatio/Jobs/WorkItemJob/WorkItemJob.cs | 130 ++-- .../WorkItemJob/WorkItemQueueExtensions.cs | 24 +- .../Jobs/WorkItemJob/WorkItemStatus.cs | 8 +- src/Foundatio/Lock/CacheLockProvider.cs | 108 +-- src/Foundatio/Lock/DisposableLock.cs | 30 +- .../Lock/DisposableLockCollection.cs | 36 +- src/Foundatio/Lock/ILockProvider.cs | 150 ++-- src/Foundatio/Lock/ScopedLockProvider.cs | 32 +- src/Foundatio/Lock/ThrottlingLockProvider.cs | 61 +- src/Foundatio/Messaging/IMessageBus.cs | 12 +- src/Foundatio/Messaging/IMessagePublisher.cs | 15 +- src/Foundatio/Messaging/IMessageSubscriber.cs | 30 +- src/Foundatio/Messaging/InMemoryMessageBus.cs | 37 +- .../Messaging/InMemoryMessageBusOptions.cs | 7 +- src/Foundatio/Messaging/Message.cs | 23 +- src/Foundatio/Messaging/MessageBusBase.cs | 198 ++++-- src/Foundatio/Messaging/NullMessageBus.cs | 14 +- .../Messaging/SharedMessageBusOptions.cs | 24 +- .../Metrics/BufferedMetricsClientBase.cs | 141 ++-- .../Metrics/CacheBucketMetricsClientBase.cs | 69 +- src/Foundatio/Metrics/CounterStat.cs | 17 +- .../Metrics/DiagnosticsMetricsClient.cs | 27 +- .../DiagnosticsMetricsClientOptions.cs | 17 +- src/Foundatio/Metrics/GaugeStat.cs | 17 +- src/Foundatio/Metrics/IHaveSubMetricName.cs | 6 +- src/Foundatio/Metrics/IMetricsClient.cs | 26 +- src/Foundatio/Metrics/IMetricsClientStats.cs | 25 +- .../Metrics/InMemoryMetricsClient.cs | 15 +- .../Metrics/InMemoryMetricsClientOptions.cs | 7 +- src/Foundatio/Metrics/MetricKey.cs | 29 +- src/Foundatio/Metrics/MetricTimer.cs | 14 +- src/Foundatio/Metrics/NullMetricsClient.cs | 14 +- .../Metrics/SharedMetricsClientOptions.cs | 21 +- src/Foundatio/Metrics/StatsDMetricsClient.cs | 83 ++- .../Metrics/StatsDMetricsClientOptions.cs | 12 +- src/Foundatio/Metrics/TimingStat.cs | 17 +- .../AsyncAutoResetEvent.cs | 3 +- .../AsyncConditionVariable.cs | 3 +- .../AsyncCountdownEvent.cs | 3 +- .../Nito.AsyncEx.Coordination/AsyncLazy.cs | 6 +- .../Nito.AsyncEx.Coordination/AsyncLock.cs | 2 +- .../AsyncManualResetEvent.cs | 4 +- .../AsyncReaderWriterLock.cs | 4 +- .../AsyncSemaphore.cs | 3 +- .../Nito.AsyncEx.Coordination/IdManager.cs | 4 +- .../Nito.AsyncEx.Tasks/AwaitableDisposable.cs | 2 +- .../CancellationTokenTaskSource.cs | 2 +- .../Nito.AsyncEx.Tasks/ExceptionHelpers.cs | 2 +- .../Nito.AsyncEx.Tasks/TaskExtensions.cs | 5 +- src/Foundatio/Nito.Collections.Deque/Deque.cs | 2 +- .../Queues/DuplicateDetectionQueueBehavior.cs | 32 +- src/Foundatio/Queues/IQueue.cs | 40 +- src/Foundatio/Queues/IQueueActivity.cs | 8 +- src/Foundatio/Queues/IQueueEntry.cs | 13 +- src/Foundatio/Queues/InMemoryQueue.cs | 188 ++++-- src/Foundatio/Queues/InMemoryQueueOptions.cs | 31 +- src/Foundatio/Queues/MetricsQueueBehavior.cs | 47 +- src/Foundatio/Queues/QueueBase.cs | 120 ++-- src/Foundatio/Queues/QueueBehaviour.cs | 33 +- src/Foundatio/Queues/QueueEntry.cs | 35 +- src/Foundatio/Queues/QueueStatSummary.cs | 6 +- src/Foundatio/Queues/SharedQueueOptions.cs | 38 +- src/Foundatio/Serializer/IHaveSerializer.cs | 6 +- src/Foundatio/Serializer/ISerializer.cs | 41 +- .../Serializer/SystemTextJsonSerializer.cs | 38 +- src/Foundatio/Storage/ActionableStream.cs | 50 +- src/Foundatio/Storage/FolderFileStorage.cs | 159 +++-- .../Storage/FolderFileStorageOptions.cs | 12 +- src/Foundatio/Storage/IFileStorage.cs | 67 +- src/Foundatio/Storage/InMemoryFileStorage.cs | 101 ++- .../Storage/InMemoryFileStorageOptions.cs | 15 +- src/Foundatio/Storage/ScopedFileStorage.cs | 42 +- src/Foundatio/Storage/StreamMode.cs | 3 +- src/Foundatio/Utility/AsyncEvent.cs | 39 +- .../Utility/ConnectionStringParser.cs | 42 +- src/Foundatio/Utility/DataDictionary.cs | 56 +- src/Foundatio/Utility/DisposableAction.cs | 14 +- src/Foundatio/Utility/EmptyDisposable.cs | 23 +- src/Foundatio/Utility/FoundatioDiagnostics.cs | 5 +- src/Foundatio/Utility/IAsyncDisposable.cs | 37 +- src/Foundatio/Utility/IAsyncLifetime.cs | 8 +- src/Foundatio/Utility/IHaveLogger.cs | 12 +- src/Foundatio/Utility/InstrumentsValues.cs | 24 +- src/Foundatio/Utility/MaintenanceBase.cs | 21 +- src/Foundatio/Utility/OptionsBuilder.cs | 23 +- src/Foundatio/Utility/PathHelper.cs | 21 +- src/Foundatio/Utility/Run.cs | 36 +- src/Foundatio/Utility/ScheduledTimer.cs | 72 +- src/Foundatio/Utility/SharedOptions.cs | 15 +- src/Foundatio/Utility/SystemClock.cs | 138 ++-- src/Foundatio/Utility/TimeUnit.cs | 36 +- src/Foundatio/Utility/TypeHelper.cs | 39 +- .../Caching/InMemoryCacheClientTests.cs | 93 ++- .../Caching/InMemoryHybridCacheClientTests.cs | 69 +- tests/Foundatio.Tests/Hosting/HostingTests.cs | 96 +-- .../Hosting/TestServerExtensions.cs | 16 +- .../Jobs/InMemoryJobQueueTests.cs | 27 +- tests/Foundatio.Tests/Jobs/JobTests.cs | 69 +- .../Foundatio.Tests/Jobs/WorkItemJobTests.cs | 106 ++- .../Locks/InMemoryLockTests.cs | 53 +- .../Messaging/InMemoryMessageBusTests.cs | 79 ++- .../Metrics/DiagnosticsMetricsTests.cs | 51 +- .../Metrics/InMemoryMetricsTests.cs | 32 +- .../Properties/AssemblyInfo.cs | 2 +- .../Queue/InMemoryQueueTests.cs | 200 ++++-- .../CompressedMessagePackSerializerTests.cs | 29 +- .../Serializer/JsonNetSerializerTests.cs | 35 +- .../Serializer/MessagePackSerializerTests.cs | 35 +- .../SystemTextJsonSerializerTests.cs | 35 +- .../Serializer/Utf8JsonSerializerTests.cs | 30 +- .../Storage/FolderFileStorageTests.cs | 77 ++- .../Storage/InMemoryFileStorageTests.cs | 72 +- .../Storage/ScopedFolderFileStorageTests.cs | 72 +- .../Storage/ScopedInMemoryFileStorageTests.cs | 72 +- tests/Foundatio.Tests/Utility/CloneTests.cs | 35 +- .../Utility/ConnectionStringParserTests.cs | 34 +- .../Utility/DataDictionaryTests.cs | 33 +- tests/Foundatio.Tests/Utility/RunTests.cs | 93 ++- .../Utility/ScheduledTimerTests.cs | 45 +- .../Utility/SystemClockTests.cs | 46 +- .../Utility/TestUdpListener.cs | 87 ++- 236 files changed, 8312 insertions(+), 5073 deletions(-) diff --git a/.editorconfig b/.editorconfig index eed69fdce..44c634eb1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,164 +1,189 @@ -############################### -# Core EditorConfig Options # -############################### +# editorconfig.org (https://github.com/dotnet/runtime/blob/main/.editorconfig) +# top-most EditorConfig file root = true -# All files +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation [*] +insert_final_newline = true indent_style = space - -# Code files -[*.{cs,csx,vb,vbx}] indent_size = 4 +trim_trailing_whitespace = true + +[*.json] insert_final_newline = false -charset = utf-8-bom -# Xml project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] -indent_size = 2 +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] +generated_code = true -# Xml config files -[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] -indent_size = 2 - -# JSON files -[*.json] -indent_size = 2 +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true -############################### -# .NET Coding Conventions # -############################### +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current -[*.{cs,vb}] -# Organize usings -dotnet_sort_system_directives_first = true +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion -# this. preferences +# avoid this. unless absolutely necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion -# Language keywords vs BCL types preferences +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = true:suggestion dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = false:suggestion -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent - -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = false:none +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion dotnet_style_readonly_field = true:suggestion # Expression-level preferences dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_null_propagation = true:suggestion dotnet_style_coalesce_expression = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = false:none -dotnet_style_prefer_conditional_expression_over_return = false:none - -dotnet_diagnostic.CS0618.severity = none - -############################### -# Naming Conventions # -############################### - -# Style Definitions -dotnet_naming_style.pascal_case_style.capitalization = pascal_case - -# Use PascalCase for constant fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.applicable_accessibilities = * -dotnet_naming_symbols.constant_fields.required_modifiers = const - -############################### -# C# Coding Conventions # -############################### - -[*.cs] -# var preferences -csharp_style_var_for_built_in_types = false:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion # Expression-bodied members -csharp_style_expression_bodied_methods = false:none -csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_operators = false:none -csharp_style_expression_bodied_properties = true:suggestion -csharp_style_expression_bodied_indexers = true:suggestion -csharp_style_expression_bodied_accessors = true:suggestion - -# Pattern matching preferences +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion -# Null-checking preferences +# Null checking preferences csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion -# Modifier preferences -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion - -# Expression-level preferences -csharp_prefer_braces = false:suggestion -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_prefer_simple_default_expression = true:suggestion -csharp_style_pattern_local_over_anonymous_function = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion - -############################### -# C# Formatting Rules # -############################### - -# New line preferences -csharp_new_line_before_open_brace = none -csharp_new_line_before_else = false -csharp_new_line_before_catch = false -csharp_new_line_before_finally = false -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_case_contents = true -csharp_indent_switch_labels = true -csharp_indent_labels = flush_left +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none # Space preferences csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_around_binary_operators = before_and_after -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_square_brackets = false -# Wrapping preferences -csharp_preserve_single_line_statements = true -csharp_preserve_single_line_blocks = true +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman -############################### -# VB Coding Conventions # -############################### +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 -[*.vb] -# Modifier preferences -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion \ No newline at end of file +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf diff --git a/samples/Foundatio.HostingSample/Jobs/EvenMinutesJob.cs b/samples/Foundatio.HostingSample/Jobs/EvenMinutesJob.cs index acc43ac04..bb33d7507 100644 --- a/samples/Foundatio.HostingSample/Jobs/EvenMinutesJob.cs +++ b/samples/Foundatio.HostingSample/Jobs/EvenMinutesJob.cs @@ -4,15 +4,19 @@ using Foundatio.Jobs; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { - public class EvenMinutesJob : IJob { +namespace Foundatio.HostingSample +{ + public class EvenMinutesJob : IJob + { private readonly ILogger _logger; - public EvenMinutesJob(ILoggerFactory loggerFactory) { + public EvenMinutesJob(ILoggerFactory loggerFactory) + { _logger = loggerFactory.CreateLogger(); } - public async Task RunAsync(CancellationToken cancellationToken = default) { + public async Task RunAsync(CancellationToken cancellationToken = default) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("EvenMinuteJob Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); diff --git a/samples/Foundatio.HostingSample/Jobs/EveryMinuteJob.cs b/samples/Foundatio.HostingSample/Jobs/EveryMinuteJob.cs index d5ad9feee..2eeb323c8 100644 --- a/samples/Foundatio.HostingSample/Jobs/EveryMinuteJob.cs +++ b/samples/Foundatio.HostingSample/Jobs/EveryMinuteJob.cs @@ -4,15 +4,19 @@ using Foundatio.Jobs; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { - public class EveryMinuteJob : IJob { +namespace Foundatio.HostingSample +{ + public class EveryMinuteJob : IJob + { private readonly ILogger _logger; - public EveryMinuteJob(ILoggerFactory loggerFactory) { + public EveryMinuteJob(ILoggerFactory loggerFactory) + { _logger = loggerFactory.CreateLogger(); } - public async Task RunAsync(CancellationToken cancellationToken = default) { + public async Task RunAsync(CancellationToken cancellationToken = default) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("EveryMinuteJob Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); diff --git a/samples/Foundatio.HostingSample/Jobs/Sample1Job.cs b/samples/Foundatio.HostingSample/Jobs/Sample1Job.cs index 0f9c5630b..561a0cfab 100644 --- a/samples/Foundatio.HostingSample/Jobs/Sample1Job.cs +++ b/samples/Foundatio.HostingSample/Jobs/Sample1Job.cs @@ -3,17 +3,21 @@ using Foundatio.Jobs; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { +namespace Foundatio.HostingSample +{ [Job(Description = "Sample 1 job", Interval = "5s", IterationLimit = 5)] - public class Sample1Job : IJob { + public class Sample1Job : IJob + { private readonly ILogger _logger; private int _iterationCount = 0; - public Sample1Job(ILoggerFactory loggerFactory) { + public Sample1Job(ILoggerFactory loggerFactory) + { _logger = loggerFactory.CreateLogger(); } - public Task RunAsync(CancellationToken cancellationToken = default) { + public Task RunAsync(CancellationToken cancellationToken = default) + { Interlocked.Increment(ref _iterationCount); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogTrace("Sample1Job Run #{IterationCount} Thread={ManagedThreadId}", _iterationCount, Thread.CurrentThread.ManagedThreadId); @@ -21,4 +25,4 @@ public Task RunAsync(CancellationToken cancellationToken = default) { return Task.FromResult(JobResult.Success); } } -} \ No newline at end of file +} diff --git a/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs b/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs index 606b78829..5b519b345 100644 --- a/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs +++ b/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs @@ -6,18 +6,22 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { +namespace Foundatio.HostingSample +{ [Job(Description = "Sample 2 job", Interval = "2s", IterationLimit = 10)] - public class Sample2Job : IJob, IHealthCheck { + public class Sample2Job : IJob, IHealthCheck + { private readonly ILogger _logger; private int _iterationCount = 0; private DateTime? _lastRun = null; - public Sample2Job(ILoggerFactory loggerFactory) { + public Sample2Job(ILoggerFactory loggerFactory) + { _logger = loggerFactory.CreateLogger(); } - public Task RunAsync(CancellationToken cancellationToken = default) { + public Task RunAsync(CancellationToken cancellationToken = default) + { _lastRun = SystemClock.UtcNow; Interlocked.Increment(ref _iterationCount); if (_logger.IsEnabled(LogLevel.Information)) @@ -26,7 +30,8 @@ public Task RunAsync(CancellationToken cancellationToken = default) { return Task.FromResult(JobResult.Success); } - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { if (!_lastRun.HasValue) return Task.FromResult(HealthCheckResult.Healthy("Job has not been run yet.")); @@ -36,4 +41,4 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc return Task.FromResult(HealthCheckResult.Healthy("Job has run in the last 5 seconds.")); } } -} \ No newline at end of file +} diff --git a/samples/Foundatio.HostingSample/MyCriticalHealthCheck.cs b/samples/Foundatio.HostingSample/MyCriticalHealthCheck.cs index a08efb5d2..60707a73d 100644 --- a/samples/Foundatio.HostingSample/MyCriticalHealthCheck.cs +++ b/samples/Foundatio.HostingSample/MyCriticalHealthCheck.cs @@ -3,11 +3,14 @@ using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; -namespace Foundatio.HostingSample { - public class MyCriticalHealthCheck : IHealthCheck { +namespace Foundatio.HostingSample +{ + public class MyCriticalHealthCheck : IHealthCheck + { private static DateTime _startTime = DateTime.Now; - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) + { return DateTime.Now.Subtract(_startTime) > TimeSpan.FromSeconds(3) ? Task.FromResult(HealthCheckResult.Healthy("Critical resource is available.")) : Task.FromResult(HealthCheckResult.Unhealthy("Critical resource not available.")); diff --git a/samples/Foundatio.HostingSample/Program.cs b/samples/Foundatio.HostingSample/Program.cs index 6ef168df5..1aebd349f 100644 --- a/samples/Foundatio.HostingSample/Program.cs +++ b/samples/Foundatio.HostingSample/Program.cs @@ -1,20 +1,23 @@ using System; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Foundatio.Extensions.Hosting.Jobs; +using Foundatio.Extensions.Hosting.Startup; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; -using Serilog; -using System.Diagnostics; using Microsoft.Extensions.Hosting; -using Foundatio.Extensions.Hosting.Startup; +using Microsoft.Extensions.Logging; +using Serilog; using Serilog.Events; -using Foundatio.Extensions.Hosting.Jobs; -namespace Foundatio.HostingSample { - public class Program { - public static int Main(string[] args) { +namespace Foundatio.HostingSample +{ + public class Program + { + public static int Main(string[] args) + { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) @@ -22,23 +25,29 @@ public static int Main(string[] args) { .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); - - try { + + try + { Log.Information("Starting host"); CreateHostBuilder(args).Build().Run(); return 0; - } catch (Exception ex) { + } + catch (Exception ex) + { Log.Fatal(ex, "Host terminated unexpectedly"); return 1; - } finally { + } + finally + { Log.CloseAndFlush(); - + if (Debugger.IsAttached) Console.ReadKey(); } } - - public static IHostBuilder CreateHostBuilder(string[] args) { + + public static IHostBuilder CreateHostBuilder(string[] args) + { bool all = args.Contains("all", StringComparer.OrdinalIgnoreCase); bool sample1 = all || args.Contains("sample1", StringComparer.OrdinalIgnoreCase); bool sample2 = all || args.Contains("sample2", StringComparer.OrdinalIgnoreCase); @@ -47,10 +56,12 @@ public static IHostBuilder CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => { - webBuilder.Configure(app => { + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.Configure(app => + { app.UseSerilogRequestLogging(); - + app.UseHealthChecks("/health"); app.UseReadyHealthChecks("Critical"); @@ -60,7 +71,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) { // add mvc or other request middleware after the UseWaitForStartupActionsBeforeServingRequests call }); }) - .ConfigureServices(s => { + .ConfigureServices(s => + { // will shutdown the host if no jobs are running s.AddJobLifetimeService(); @@ -83,16 +95,19 @@ public static IHostBuilder CreateHostBuilder(string[] args) { if (sample1) s.AddJob(sp => new Sample1Job(sp.GetRequiredService()), o => o.ApplyDefaults().WaitForStartupActions(true).InitialDelay(TimeSpan.FromSeconds(4))); - if (sample2) { + if (sample2) + { s.AddHealthChecks().AddCheck("Sample2Job"); s.AddJob(true); } // if you don't specify priority, actions will automatically be assigned an incrementing priority starting at 0 - s.AddStartupAction("Test1", async sp => { + s.AddStartupAction("Test1", async sp => + { var logger = sp.GetRequiredService>(); logger.LogTrace("Running startup 1 action"); - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) + { await Task.Delay(1000); logger.LogTrace("Running startup 1 action..."); } @@ -104,17 +119,19 @@ public static IHostBuilder CreateHostBuilder(string[] args) { s.AddStartupAction(priority: 100); s.AddStartupAction(priority: 100); - s.AddStartupAction("Test2", async sp => { + s.AddStartupAction("Test2", async sp => + { var logger = sp.GetRequiredService>(); logger.LogTrace("Running startup 2 action"); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 2; i++) + { await Task.Delay(1500); logger.LogTrace("Running startup 2 action..."); } //throw new ApplicationException("Boom goes the startup"); logger.LogTrace("Done running startup 2 action"); }); - + //s.AddStartupAction("Boom", () => throw new ApplicationException("Boom goes the startup")); }); diff --git a/samples/Foundatio.HostingSample/Startup/MyStartupAction.cs b/samples/Foundatio.HostingSample/Startup/MyStartupAction.cs index ab353c774..7eee41247 100644 --- a/samples/Foundatio.HostingSample/Startup/MyStartupAction.cs +++ b/samples/Foundatio.HostingSample/Startup/MyStartupAction.cs @@ -3,16 +3,21 @@ using Foundatio.Extensions.Hosting.Startup; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { - public class MyStartupAction : IStartupAction { +namespace Foundatio.HostingSample +{ + public class MyStartupAction : IStartupAction + { private readonly ILogger _logger; - public MyStartupAction(ILogger logger) { + public MyStartupAction(ILogger logger) + { _logger = logger; } - public async Task RunAsync(CancellationToken cancellationToken = default) { - for (int i = 0; i < 5; i++) { + public async Task RunAsync(CancellationToken cancellationToken = default) + { + for (int i = 0; i < 5; i++) + { _logger.LogTrace("MyStartupAction Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); await Task.Delay(500); } diff --git a/samples/Foundatio.HostingSample/Startup/OtherStartupAction.cs b/samples/Foundatio.HostingSample/Startup/OtherStartupAction.cs index eb0faaca4..0a17292f7 100644 --- a/samples/Foundatio.HostingSample/Startup/OtherStartupAction.cs +++ b/samples/Foundatio.HostingSample/Startup/OtherStartupAction.cs @@ -3,16 +3,21 @@ using Foundatio.Extensions.Hosting.Startup; using Microsoft.Extensions.Logging; -namespace Foundatio.HostingSample { - public class OtherStartupAction : IStartupAction { +namespace Foundatio.HostingSample +{ + public class OtherStartupAction : IStartupAction + { private readonly ILogger _logger; - public OtherStartupAction(ILogger logger) { + public OtherStartupAction(ILogger logger) + { _logger = logger; } - public async Task RunAsync(CancellationToken cancellationToken = default) { - for (int i = 0; i < 5; i++) { + public async Task RunAsync(CancellationToken cancellationToken = default) + { + for (int i = 0; i < 5; i++) + { _logger.LogTrace("OtherStartupAction Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); await Task.Delay(900); } diff --git a/src/Foundatio.AppMetrics/AppMetricsClient.cs b/src/Foundatio.AppMetrics/AppMetricsClient.cs index 4af0cf4be..83df016e9 100644 --- a/src/Foundatio.AppMetrics/AppMetricsClient.cs +++ b/src/Foundatio.AppMetrics/AppMetricsClient.cs @@ -4,23 +4,29 @@ using App.Metrics.Gauge; using App.Metrics.Timer; -namespace Foundatio.Metrics { - public class AppMetricsClient : IMetricsClient { +namespace Foundatio.Metrics +{ + public class AppMetricsClient : IMetricsClient + { private readonly IMetrics _metrics; - public AppMetricsClient(IMetrics metrics) { + public AppMetricsClient(IMetrics metrics) + { _metrics = metrics; } - public void Counter(string name, int value = 1) { + public void Counter(string name, int value = 1) + { _metrics.Provider.Counter.Instance(new CounterOptions { Name = name }).Increment(value); } - public void Gauge(string name, double value) { + public void Gauge(string name, double value) + { _metrics.Provider.Gauge.Instance(new GaugeOptions { Name = name }).SetValue(value); } - public void Timer(string name, int milliseconds) { + public void Timer(string name, int milliseconds) + { _metrics.Provider.Timer.Instance(new TimerOptions { Name = name }).Record(milliseconds, TimeUnit.Milliseconds); } diff --git a/src/Foundatio.DataProtection/Extensions/DataProtectionBuilderExtensions.cs b/src/Foundatio.DataProtection/Extensions/DataProtectionBuilderExtensions.cs index 395656796..45a06890d 100644 --- a/src/Foundatio.DataProtection/Extensions/DataProtectionBuilderExtensions.cs +++ b/src/Foundatio.DataProtection/Extensions/DataProtectionBuilderExtensions.cs @@ -6,8 +6,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace Foundatio.DataProtection { - public static class DataProtectionBuilderExtensions { +namespace Foundatio.DataProtection +{ + public static class DataProtectionBuilderExtensions + { /// /// Configures the data protection system to persist keys to file storage. /// @@ -15,35 +17,39 @@ public static class DataProtectionBuilderExtensions { /// The storage account to use. /// The logger factory to use. /// The value . - public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder, IFileStorage storage, ILoggerFactory loggerFactory = null) { + public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder, IFileStorage storage, ILoggerFactory loggerFactory = null) + { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (storage == null) throw new ArgumentNullException(nameof(storage)); - builder.Services.Configure(options => { + builder.Services.Configure(options => + { options.XmlRepository = new FoundatioStorageXmlRepository(storage, loggerFactory); }); return builder; } - + /// /// Configures the data protection system to persist keys to file storage. /// /// The builder instance to modify. /// The storage factory to use. /// The value . - public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder, Func storageFactory) { + public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder, Func storageFactory) + { if (builder == null) throw new ArgumentNullException(nameof(builder)); - builder.Services.AddSingleton>(services => { + builder.Services.AddSingleton>(services => + { var storage = storageFactory?.Invoke(services); if (storage == null) throw new ArgumentNullException(nameof(storageFactory)); - + var loggerFactory = services.GetService(); return new ConfigureOptions(options => options.XmlRepository = new FoundatioStorageXmlRepository(storage, loggerFactory)); }); @@ -56,11 +62,13 @@ public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtecti /// /// The builder instance to modify. /// The value . - public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder) { + public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtectionBuilder builder) + { if (builder == null) throw new ArgumentNullException(nameof(builder)); - builder.Services.AddSingleton>(services => { + builder.Services.AddSingleton>(services => + { var storage = services.GetRequiredService(); var loggerFactory = services.GetService(); @@ -70,4 +78,4 @@ public static IDataProtectionBuilder PersistKeysToFileStorage(this IDataProtecti return builder; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.DataProtection/FoundatioStorageXmlRepository.cs b/src/Foundatio.DataProtection/FoundatioStorageXmlRepository.cs index 687b59f3f..2e2481e43 100644 --- a/src/Foundatio.DataProtection/FoundatioStorageXmlRepository.cs +++ b/src/Foundatio.DataProtection/FoundatioStorageXmlRepository.cs @@ -10,21 +10,24 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.DataProtection { +namespace Foundatio.DataProtection +{ /// /// An which is backed by Foundatio Storage. /// /// /// Instances of this type are thread-safe. /// - public sealed class FoundatioStorageXmlRepository : IXmlRepository { + public sealed class FoundatioStorageXmlRepository : IXmlRepository + { private readonly IFileStorage _storage; private readonly ILogger _logger; /// /// Creates a new instance of the . /// - public FoundatioStorageXmlRepository(IFileStorage storage, ILoggerFactory loggerFactory = null) { + public FoundatioStorageXmlRepository(IFileStorage storage, ILoggerFactory loggerFactory = null) + { if (storage == null) throw new ArgumentNullException(nameof(storage)); @@ -33,26 +36,31 @@ public FoundatioStorageXmlRepository(IFileStorage storage, ILoggerFactory logger } /// - public IReadOnlyCollection GetAllElements() { + public IReadOnlyCollection GetAllElements() + { return GetAllElementsAsync().GetAwaiter().GetResult(); } - private async Task> GetAllElementsAsync() { + private async Task> GetAllElementsAsync() + { _logger.LogTrace("Loading elements..."); var files = (await _storage.GetFileListAsync("*.xml").AnyContext()).ToList(); - if (files.Count == 0) { + if (files.Count == 0) + { _logger.LogTrace("No elements were found"); return new XElement[0]; } _logger.LogTrace("Found {FileCount} elements.", files.Count); var elements = new List(files.Count); - foreach (var file in files) { + foreach (var file in files) + { _logger.LogTrace("Loading element: {File}", file.Path); - using (var stream = await _storage.GetFileStreamAsync(file.Path).AnyContext()) { + using (var stream = await _storage.GetFileStreamAsync(file.Path).AnyContext()) + { elements.Add(XElement.Load(stream)); } - + _logger.LogTrace("Loaded element: {File}", file.Path); } @@ -60,18 +68,21 @@ private async Task> GetAllElementsAsync() { } /// - public void StoreElement(XElement element, string friendlyName) { + public void StoreElement(XElement element, string friendlyName) + { if (element == null) throw new ArgumentNullException(nameof(element)); StoreElementAsync(element, friendlyName).GetAwaiter().GetResult(); } - private Task StoreElementAsync(XElement element, string friendlyName) { + private Task StoreElementAsync(XElement element, string friendlyName) + { string path = String.Concat(!String.IsNullOrEmpty(friendlyName) ? friendlyName : Guid.NewGuid().ToString("N"), ".xml"); _logger.LogTrace("Saving element: {File}.", path); - return Run.WithRetriesAsync(async () => { + return Run.WithRetriesAsync(async () => + { using var memoryStream = new MemoryStream(); element.Save(memoryStream, SaveOptions.DisableFormatting); memoryStream.Seek(0, SeekOrigin.Begin); @@ -81,4 +92,4 @@ private Task StoreElementAsync(XElement element, string friendlyName) { }); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CalendarHelper.cs b/src/Foundatio.Extensions.Hosting/Cronos/CalendarHelper.cs index df43fb2cd..cde4f57b9 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CalendarHelper.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CalendarHelper.cs @@ -70,13 +70,13 @@ public static long DateTimeToTicks(int year, int month, int day, int hour, int m public static void FillDateTimeParts(long ticks, out int second, out int minute, out int hour, out int day, out int month, out int year) { - second = (int) (ticks / TicksPerSecond % 60); + second = (int)(ticks / TicksPerSecond % 60); if (ticks % TicksPerSecond != 0) second++; - minute = (int) (ticks / TicksPerMinute % 60); - hour = (int) (ticks / TicksPerHour % 24); + minute = (int)(ticks / TicksPerMinute % 60); + hour = (int)(ticks / TicksPerHour % 24); // n = number of days since 1/1/0001 - int n = (int) (ticks / TicksPerDay); + int n = (int)(ticks / TicksPerDay); // y400 = number of whole 400-year periods since 1/1/0001 int y400 = n / DaysPer400Years; // n = day number within 400-year period @@ -172,4 +172,4 @@ public static bool IsLastDayOfWeek(int year, int month, int day) return day + DaysPerWeekCount > GetDaysInMonth(year, month); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CronExpression.cs b/src/Foundatio.Extensions.Hosting/Cronos/CronExpression.cs index fcc99cf27..5d21988ff 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CronExpression.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CronExpression.cs @@ -31,7 +31,7 @@ namespace Cronos /// /// Provides a parser and scheduler for cron expressions. /// - public sealed class CronExpression: IEquatable + public sealed class CronExpression : IEquatable { private const long NotFound = 0; @@ -63,15 +63,15 @@ public sealed class CronExpression: IEquatable 50, 31, 19, 15, 30, 14, 13, 12 }; - private long _second; // 60 bits -> from 0 bit to 59 bit - private long _minute; // 60 bits -> from 0 bit to 59 bit - private int _hour; // 24 bits -> from 0 bit to 23 bit - private int _dayOfMonth; // 31 bits -> from 1 bit to 31 bit + private long _second; // 60 bits -> from 0 bit to 59 bit + private long _minute; // 60 bits -> from 0 bit to 59 bit + private int _hour; // 24 bits -> from 0 bit to 23 bit + private int _dayOfMonth; // 31 bits -> from 1 bit to 31 bit private short _month; // 12 bits -> from 1 bit to 12 bit - private byte _dayOfWeek; // 8 bits -> from 0 bit to 7 bit + private byte _dayOfWeek; // 8 bits -> from 0 bit to 7 bit - private byte _nthDayOfWeek; - private byte _lastMonthOffset; + private byte _nthDayOfWeek; + private byte _lastMonthOffset; private CronExpressionFlag _flags; @@ -286,7 +286,7 @@ public IEnumerable GetOccurrences( public override string ToString() { var expressionBuilder = new StringBuilder(); - + AppendFieldValue(expressionBuilder, CronField.Seconds, _second).Append(' '); AppendFieldValue(expressionBuilder, CronField.Minutes, _minute).Append(' '); AppendFieldValue(expressionBuilder, CronField.Hours, _hour).Append(' '); @@ -374,7 +374,7 @@ public override int GetHashCode() { var currentOffset = from.Offset; var standardOffset = zone.BaseUtcOffset; - + if (standardOffset != currentOffset) { var daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, fromLocal); @@ -478,11 +478,11 @@ private long FindOccurence(long ticks, bool startInclusive) if (minute > startMinute) goto RolloverMinute; goto ReturnResult; - RolloverDay: hour = GetFirstSet(_hour); - RolloverHour: minute = GetFirstSet(_minute); - RolloverMinute: second = GetFirstSet(_second); + RolloverDay: hour = GetFirstSet(_hour); + RolloverHour: minute = GetFirstSet(_minute); + RolloverMinute: second = GetFirstSet(_second); - ReturnResult: + ReturnResult: var found = CalendarHelper.DateTimeToTicks(year, month, day, hour, minute, second); if (found >= ticks) return found; @@ -876,7 +876,7 @@ private static StringBuilder AppendFieldValue(StringBuilder expressionBuilder, C // Unset 7 bit for Day of week field because both 0 and 7 stand for Sunday. if (field == CronField.DaysOfWeek) fieldValue &= ~(1 << field.Last); - for (var i = GetFirstSet(fieldValue);; i = GetFirstSet(fieldValue >> i << i)) + for (var i = GetFirstSet(fieldValue); ; i = GetFirstSet(fieldValue >> i << i)) { expressionBuilder.Append(i); if (fieldValue >> ++i == 0) break; @@ -951,7 +951,7 @@ private static long GetReversedRangeBits(CronField field, int num1, int num2, in if (field == CronField.DaysOfWeek) high--; var bits = GetRangeBits(num1, high, step); - + num1 = field.First + step - (high - num1) % step - 1; return bits | GetRangeBits(num1, num2, step); } @@ -1095,4 +1095,4 @@ private static int ToUpper(int code) return code; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CronExpressionFlag.cs b/src/Foundatio.Extensions.Hosting/Cronos/CronExpressionFlag.cs index b993ed42c..de9d02358 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CronExpressionFlag.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CronExpressionFlag.cs @@ -28,9 +28,9 @@ namespace Cronos internal enum CronExpressionFlag : byte { DayOfMonthLast = 0b00001, - DayOfWeekLast = 0b00010, - Interval = 0b00100, + DayOfWeekLast = 0b00010, + Interval = 0b00100, NearestWeekday = 0b01000, - NthDayOfWeek = 0b10000 + NthDayOfWeek = 0b10000 } } diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CronField.cs b/src/Foundatio.Extensions.Hosting/Cronos/CronField.cs index 87e6572c9..abc8f130a 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CronField.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CronField.cs @@ -100,4 +100,4 @@ public override string ToString() return Name; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CronFormat.cs b/src/Foundatio.Extensions.Hosting/Cronos/CronFormat.cs index 9eef70588..e22ae25e4 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CronFormat.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CronFormat.cs @@ -40,4 +40,4 @@ public enum CronFormat /// IncludeSeconds = 1 } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Cronos/CronFormatException.cs b/src/Foundatio.Extensions.Hosting/Cronos/CronFormatException.cs index b5c056ce0..0297bb063 100644 --- a/src/Foundatio.Extensions.Hosting/Cronos/CronFormatException.cs +++ b/src/Foundatio.Extensions.Hosting/Cronos/CronFormatException.cs @@ -44,4 +44,4 @@ internal CronFormatException(CronField field, string message) : this($"{field}: { } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Jobs/HostedJobOptions.cs b/src/Foundatio.Extensions.Hosting/Jobs/HostedJobOptions.cs index 0ccc043dc..46dace23e 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/HostedJobOptions.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/HostedJobOptions.cs @@ -1,7 +1,9 @@ using Foundatio.Jobs; -namespace Foundatio.Extensions.Hosting.Jobs { - public class HostedJobOptions : JobOptions { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class HostedJobOptions : JobOptions + { public bool WaitForStartupActions { get; set; } public string CronSchedule { get; set; } } diff --git a/src/Foundatio.Extensions.Hosting/Jobs/HostedJobService.cs b/src/Foundatio.Extensions.Hosting/Jobs/HostedJobService.cs index b51010d7e..a370f4030 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/HostedJobService.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/HostedJobService.cs @@ -1,15 +1,17 @@ using System; using System.Threading; using System.Threading.Tasks; +using Foundatio.Extensions.Hosting.Startup; +using Foundatio.Jobs; +using Foundatio.Utility; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Foundatio.Jobs; -using Foundatio.Extensions.Hosting.Startup; -using Foundatio.Utility; -namespace Foundatio.Extensions.Hosting.Jobs { - public class HostedJobService : IHostedService, IJobStatus, IDisposable { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class HostedJobService : IHostedService, IJobStatus, IDisposable + { private readonly CancellationTokenSource _stoppingCts = new(); private Task _executingTask; private readonly IServiceProvider _serviceProvider; @@ -18,7 +20,8 @@ public class HostedJobService : IHostedService, IJobStatus, IDisposable { private readonly HostedJobOptions _jobOptions; private bool _hasStarted = false; - public HostedJobService(IServiceProvider serviceProvider, HostedJobOptions jobOptions, ILoggerFactory loggerFactory) { + public HostedJobService(IServiceProvider serviceProvider, HostedJobOptions jobOptions, ILoggerFactory loggerFactory) + { _serviceProvider = serviceProvider; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); @@ -28,12 +31,16 @@ public HostedJobService(IServiceProvider serviceProvider, HostedJobOptions jobOp lifetime?.RegisterHostedJobInstance(this); } - private async Task ExecuteAsync(CancellationToken stoppingToken) { - if (_jobOptions.WaitForStartupActions) { + private async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (_jobOptions.WaitForStartupActions) + { var startupContext = _serviceProvider.GetService(); - if (startupContext != null) { + if (startupContext != null) + { var result = await startupContext.WaitForStartupAsync(stoppingToken).AnyContext(); - if (!result.Success) { + if (!result.Success) + { _logger.LogError("Unable to start {JobName} job due to startup actions failure", _jobOptions.Name); return; } @@ -42,32 +49,55 @@ private async Task ExecuteAsync(CancellationToken stoppingToken) { var runner = new JobRunner(_jobOptions, _loggerFactory); - try { + try +/* Unmerged change from project 'Foundatio.Extensions.Hosting(net8.0)' +Before: + _stoppingCts.Cancel(); +After: + await _stoppingCts.CancelAsync(); +*/ + + { await runner.RunAsync(stoppingToken).AnyContext(); _stoppingCts.Cancel(); - } finally { + } + finally + { _logger.LogInformation("{JobName} job completed", _jobOptions.Name); } } - public Task StartAsync(CancellationToken cancellationToken) { + public Task StartAsync(CancellationToken cancellationToken) + { _executingTask = ExecuteAsync(_stoppingCts.Token); _hasStarted = true; return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; } - public async Task StopAsync(CancellationToken cancellationToken) { + public async Task StopAsync(CancellationToken cancellationToken) + { if (_executingTask == null) return; - try { + try +/* Unmerged change from project 'Foundatio.Extensions.Hosting(net8.0)' +Before: _stoppingCts.Cancel(); - } finally { +After: + await _stoppingCts.CancelAsync(); +*/ + + { + _stoppingCts.Cancel(); + } + finally + { await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)).AnyContext(); } } - public void Dispose() { + public void Dispose() + { _stoppingCts.Cancel(); _stoppingCts.Dispose(); } @@ -75,7 +105,8 @@ public void Dispose() { public bool IsRunning => _hasStarted == false || (_executingTask != null && !_executingTask.IsCompleted); } - public interface IJobStatus { + public interface IJobStatus + { bool IsRunning { get; } } } diff --git a/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs b/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs index 65b77031c..f7bc2df88 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs @@ -1,19 +1,25 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using System; +using System.Linq; using Foundatio.Jobs; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -namespace Foundatio.Extensions.Hosting.Jobs { - public static class JobHostExtensions { - public static IServiceCollection AddJob(this IServiceCollection services, HostedJobOptions jobOptions) { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public static class JobHostExtensions + { + public static IServiceCollection AddJob(this IServiceCollection services, HostedJobOptions jobOptions) + { if (jobOptions.JobFactory == null) throw new ArgumentNullException(nameof(jobOptions), "jobOptions.JobFactory is required"); - if (String.IsNullOrEmpty(jobOptions.CronSchedule)) { + if (String.IsNullOrEmpty(jobOptions.CronSchedule)) + { return services.AddTransient(s => new HostedJobService(s, jobOptions, s.GetService())); - } else { + } + else + { if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(ScheduledJobService))) services.AddTransient(); @@ -21,14 +27,19 @@ public static IServiceCollection AddJob(this IServiceCollection services, Hosted } } - public static IServiceCollection AddJob(this IServiceCollection services, Func jobFactory, HostedJobOptions jobOptions) { - if (String.IsNullOrEmpty(jobOptions.CronSchedule)) { - return services.AddTransient(s => { + public static IServiceCollection AddJob(this IServiceCollection services, Func jobFactory, HostedJobOptions jobOptions) + { + if (String.IsNullOrEmpty(jobOptions.CronSchedule)) + { + return services.AddTransient(s => + { jobOptions.JobFactory = () => jobFactory(s); return new HostedJobService(s, jobOptions, s.GetService()); }); - } else { + } + else + { if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(ScheduledJobService))) services.AddTransient(); @@ -36,16 +47,21 @@ public static IServiceCollection AddJob(this IServiceCollection services, Func(this IServiceCollection services, HostedJobOptions jobOptions) where T : class, IJob { + public static IServiceCollection AddJob(this IServiceCollection services, HostedJobOptions jobOptions) where T : class, IJob + { services.AddTransient(); - if (String.IsNullOrEmpty(jobOptions.CronSchedule)) { - return services.AddTransient(s => { + if (String.IsNullOrEmpty(jobOptions.CronSchedule)) + { + return services.AddTransient(s => + { if (jobOptions.JobFactory == null) jobOptions.JobFactory = s.GetRequiredService; return new HostedJobService(s, jobOptions, s.GetService()); }); - } else { + } + else + { if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(ScheduledJobService))) services.AddTransient(); @@ -53,36 +69,42 @@ public static IServiceCollection AddJob(this IServiceCollection services, Hos } } - public static IServiceCollection AddJob(this IServiceCollection services, bool waitForStartupActions = false) where T : class, IJob { + public static IServiceCollection AddJob(this IServiceCollection services, bool waitForStartupActions = false) where T : class, IJob + { return services.AddJob(o => o.ApplyDefaults().WaitForStartupActions(waitForStartupActions)); } - public static IServiceCollection AddCronJob(this IServiceCollection services, string cronSchedule) where T : class, IJob { + public static IServiceCollection AddCronJob(this IServiceCollection services, string cronSchedule) where T : class, IJob + { return services.AddJob(o => o.CronSchedule(cronSchedule)); } - public static IServiceCollection AddJob(this IServiceCollection services, Action configureJobOptions) where T : class, IJob { + public static IServiceCollection AddJob(this IServiceCollection services, Action configureJobOptions) where T : class, IJob + { var jobOptionsBuilder = new HostedJobOptionsBuilder(); configureJobOptions?.Invoke(jobOptionsBuilder); return services.AddJob(jobOptionsBuilder.Target); } - public static IServiceCollection AddJob(this IServiceCollection services, Action configureJobOptions) { + public static IServiceCollection AddJob(this IServiceCollection services, Action configureJobOptions) + { var jobOptionsBuilder = new HostedJobOptionsBuilder(); configureJobOptions?.Invoke(jobOptionsBuilder); return services.AddJob(jobOptionsBuilder.Target); } - public static IServiceCollection AddJob(this IServiceCollection services, Func jobFactory, Action configureJobOptions) { + public static IServiceCollection AddJob(this IServiceCollection services, Func jobFactory, Action configureJobOptions) + { var jobOptionsBuilder = new HostedJobOptionsBuilder(); configureJobOptions?.Invoke(jobOptionsBuilder); return services.AddJob(jobFactory, jobOptionsBuilder.Target); } - public static IServiceCollection AddJobLifetimeService(this IServiceCollection services) { + public static IServiceCollection AddJobLifetimeService(this IServiceCollection services) + { services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); return services; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Jobs/JobOptionsBuilder.cs b/src/Foundatio.Extensions.Hosting/Jobs/JobOptionsBuilder.cs index 8401bf4a6..6450bcace 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/JobOptionsBuilder.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/JobOptionsBuilder.cs @@ -1,72 +1,87 @@ using System; using Foundatio.Jobs; -namespace Foundatio.Extensions.Hosting.Jobs { - public class HostedJobOptionsBuilder { - public HostedJobOptionsBuilder(HostedJobOptions target = null) { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class HostedJobOptionsBuilder + { + public HostedJobOptionsBuilder(HostedJobOptions target = null) + { Target = target ?? new HostedJobOptions(); } public HostedJobOptions Target { get; } - - public HostedJobOptionsBuilder ApplyDefaults() where T: IJob { + + public HostedJobOptionsBuilder ApplyDefaults() where T : IJob + { Target.ApplyDefaults(); return this; } - - public HostedJobOptionsBuilder ApplyDefaults(Type jobType) { + + public HostedJobOptionsBuilder ApplyDefaults(Type jobType) + { JobOptions.ApplyDefaults(Target, jobType); return this; } - public HostedJobOptionsBuilder Name(string value) { + public HostedJobOptionsBuilder Name(string value) + { Target.Name = value; return this; } - public HostedJobOptionsBuilder Description(string value) { + public HostedJobOptionsBuilder Description(string value) + { Target.Description = value; return this; } - public HostedJobOptionsBuilder JobFactory(Func value) { + public HostedJobOptionsBuilder JobFactory(Func value) + { Target.JobFactory = value; return this; } - public HostedJobOptionsBuilder RunContinuous(bool value) { + public HostedJobOptionsBuilder RunContinuous(bool value) + { Target.RunContinuous = value; return this; } - public HostedJobOptionsBuilder CronSchedule(string value) { + public HostedJobOptionsBuilder CronSchedule(string value) + { Target.CronSchedule = value; return this; } - public HostedJobOptionsBuilder Interval(TimeSpan? value) { + public HostedJobOptionsBuilder Interval(TimeSpan? value) + { Target.Interval = value; return this; } - public HostedJobOptionsBuilder InitialDelay(TimeSpan? value) { + public HostedJobOptionsBuilder InitialDelay(TimeSpan? value) + { Target.InitialDelay = value; return this; } - public HostedJobOptionsBuilder IterationLimit(int value) { + public HostedJobOptionsBuilder IterationLimit(int value) + { Target.IterationLimit = value; return this; } - public HostedJobOptionsBuilder InstanceCount(int value) { + public HostedJobOptionsBuilder InstanceCount(int value) + { Target.InstanceCount = value; return this; } - public HostedJobOptionsBuilder WaitForStartupActions(bool value) { + public HostedJobOptionsBuilder WaitForStartupActions(bool value) + { Target.WaitForStartupActions = value; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRegistration.cs b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRegistration.cs index b87b29922..fc9ab0b8d 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRegistration.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRegistration.cs @@ -1,9 +1,12 @@ using System; using Foundatio.Jobs; -namespace Foundatio.Extensions.Hosting.Jobs { - public class ScheduledJobRegistration { - public ScheduledJobRegistration(Func jobFactory, string schedule) { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class ScheduledJobRegistration + { + public ScheduledJobRegistration(Func jobFactory, string schedule) + { JobFactory = jobFactory; Schedule = schedule; } diff --git a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs index 55c500008..fd7ae42e7 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs @@ -14,12 +14,15 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Extensions.Hosting.Jobs { - public class ScheduledJobService : BackgroundService, IJobStatus { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class ScheduledJobService : BackgroundService, IJobStatus + { private readonly List _jobs; private readonly IServiceProvider _serviceProvider; - public ScheduledJobService(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) { + public ScheduledJobService(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) + { _serviceProvider = serviceProvider; var cacheClient = serviceProvider.GetService() ?? new InMemoryCacheClient(); _jobs = new List(serviceProvider.GetServices().Select(j => new ScheduledJobRunner(j.JobFactory, j.Schedule, cacheClient, loggerFactory))); @@ -30,18 +33,22 @@ public ScheduledJobService(IServiceProvider serviceProvider, ILoggerFactory logg public bool IsRunning { get; private set; } = true; - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { // TODO: Add more logging throughout var startupContext = _serviceProvider.GetService(); - if (startupContext != null) { + if (startupContext != null) + { var result = await startupContext.WaitForStartupAsync(stoppingToken).AnyContext(); - if (!result.Success) { + if (!result.Success) + { IsRunning = false; throw new ApplicationException("Failed to wait for startup actions to complete"); } } - while (!stoppingToken.IsCancellationRequested) { + while (!stoppingToken.IsCancellationRequested) + { var jobsToRun = _jobs.Where(j => j.ShouldRun()).ToArray(); foreach (var jobToRun in jobsToRun) @@ -55,7 +62,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { } } - private class ScheduledJobRunner { + private class ScheduledJobRunner + { private readonly Func _jobFactory; private readonly CronExpression _cronSchedule; private readonly ILockProvider _lockProvider; @@ -63,7 +71,8 @@ private class ScheduledJobRunner { private readonly DateTime _baseDate = new(2010, 1, 1); private string _cacheKeyPrefix; - public ScheduledJobRunner(Func jobFactory, string schedule, ICacheClient cacheClient, ILoggerFactory loggerFactory = null) { + public ScheduledJobRunner(Func jobFactory, string schedule, ICacheClient cacheClient, ILoggerFactory loggerFactory = null) + { _jobFactory = jobFactory; Schedule = schedule; _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; @@ -75,7 +84,8 @@ public ScheduledJobRunner(Func jobFactory, string schedule, ICacheClient c var interval = TimeSpan.FromDays(1); var nextOccurrence = _cronSchedule.GetNextOccurrence(SystemClock.UtcNow); - if (nextOccurrence.HasValue) { + if (nextOccurrence.HasValue) + { var nextNextOccurrence = _cronSchedule.GetNextOccurrence(nextOccurrence.Value); if (nextNextOccurrence.HasValue) interval = nextNextOccurrence.Value.Subtract(nextOccurrence.Value); @@ -91,7 +101,8 @@ public ScheduledJobRunner(Func jobFactory, string schedule, ICacheClient c public DateTime? NextRun { get; private set; } public Task RunTask { get; private set; } - public bool ShouldRun() { + public bool ShouldRun() + { if (!NextRun.HasValue) return false; @@ -106,12 +117,15 @@ public bool ShouldRun() { return true; } - public Task StartAsync(CancellationToken cancellationToken = default) { + public Task StartAsync(CancellationToken cancellationToken = default) + { // using lock provider in a cluster with a distributed cache implementation keeps cron jobs from running duplicates // TODO: provide ability to run cron jobs on a per host isolated schedule - return _lockProvider.TryUsingAsync(GetLockKey(NextRun.Value), t => { + return _lockProvider.TryUsingAsync(GetLockKey(NextRun.Value), t => + { // start running the job in a thread - RunTask = Task.Factory.StartNew(async () => { + RunTask = Task.Factory.StartNew(async () => + { var job = _jobFactory(); // TODO: Don't calculate job name every time string jobName = job.GetType().Name; @@ -127,7 +141,8 @@ public Task StartAsync(CancellationToken cancellationToken = default) { }, TimeSpan.Zero, TimeSpan.Zero); } - private string GetLockKey(DateTime date) { + private string GetLockKey(DateTime date) + { if (_cacheKeyPrefix == null) _cacheKeyPrefix = TypeHelper.GetTypeDisplayName(_jobFactory().GetType()); diff --git a/src/Foundatio.Extensions.Hosting/Jobs/ShutdownHostIfNoJobsRunningService.cs b/src/Foundatio.Extensions.Hosting/Jobs/ShutdownHostIfNoJobsRunningService.cs index ded580106..c53541d26 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/ShutdownHostIfNoJobsRunningService.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/ShutdownHostIfNoJobsRunningService.cs @@ -10,8 +10,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Extensions.Hosting.Jobs { - public class ShutdownHostIfNoJobsRunningService : IHostedService, IDisposable { +namespace Foundatio.Extensions.Hosting.Jobs +{ + public class ShutdownHostIfNoJobsRunningService : IHostedService, IDisposable + { private Timer _timer; private readonly List _jobs = new(); private readonly IHostApplicationLifetime _lifetime; @@ -19,19 +21,23 @@ public class ShutdownHostIfNoJobsRunningService : IHostedService, IDisposable { private bool _isStarted = false; private readonly ILogger _logger; - public ShutdownHostIfNoJobsRunningService(IHostApplicationLifetime applicationLifetime, IServiceProvider serviceProvider, ILogger logger) { + public ShutdownHostIfNoJobsRunningService(IHostApplicationLifetime applicationLifetime, IServiceProvider serviceProvider, ILogger logger) + { _lifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime)); _serviceProvider = serviceProvider; _logger = logger ?? NullLogger.Instance; - _lifetime.ApplicationStarted.Register(() => { + _lifetime.ApplicationStarted.Register(() => + { _timer = new Timer(e => CheckForShutdown(), null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(2)); }); } - public Task StartAsync(CancellationToken cancellationToken) { + public Task StartAsync(CancellationToken cancellationToken) + { // if there are startup actions, don't allow shutdown to happen until after the startup actions have completed - _ = Task.Run(async () => { + _ = Task.Run(async () => + { var startupContext = _serviceProvider.GetService(); if (startupContext != null) await startupContext.WaitForStartupAsync(cancellationToken).AnyContext(); @@ -42,29 +48,33 @@ public Task StartAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken) { + public Task StopAsync(CancellationToken cancellationToken) + { _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } - public void RegisterHostedJobInstance(IJobStatus job) { + public void RegisterHostedJobInstance(IJobStatus job) + { _jobs.Add(job); } - public void CheckForShutdown() { + public void CheckForShutdown() + { if (!_isStarted) return; - + int runningJobCount = _jobs.Count(s => s.IsRunning); if (runningJobCount != 0) return; - + _timer?.Change(Timeout.Infinite, 0); _logger.LogInformation("Stopping host due to no running jobs"); _lifetime.StopApplication(); } - public void Dispose() { + public void Dispose() + { _timer?.Dispose(); } } diff --git a/src/Foundatio.Extensions.Hosting/Startup/IStartupAction.cs b/src/Foundatio.Extensions.Hosting/Startup/IStartupAction.cs index 7d38f1570..cb022d3d1 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/IStartupAction.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/IStartupAction.cs @@ -2,8 +2,10 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Extensions.Hosting.Startup { - public interface IStartupAction { +namespace Foundatio.Extensions.Hosting.Startup +{ + public interface IStartupAction + { Task RunAsync(CancellationToken shutdownToken = default); } } diff --git a/src/Foundatio.Extensions.Hosting/Startup/RunStartupActionsService.cs b/src/Foundatio.Extensions.Hosting/Startup/RunStartupActionsService.cs index f9d4792c8..3cec6e45e 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/RunStartupActionsService.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/RunStartupActionsService.cs @@ -4,19 +4,23 @@ using Foundatio.Utility; using Microsoft.Extensions.Hosting; -namespace Foundatio.Extensions.Hosting.Startup { - public class RunStartupActionsService : BackgroundService { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class RunStartupActionsService : BackgroundService + { private readonly StartupActionsContext _startupContext; private readonly IServiceProvider _serviceProvider; - public RunStartupActionsService(StartupActionsContext startupContext, IServiceProvider serviceProvider) { + public RunStartupActionsService(StartupActionsContext startupContext, IServiceProvider serviceProvider) + { _startupContext = startupContext; _serviceProvider = serviceProvider; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { var result = await _serviceProvider.RunStartupActionsAsync(stoppingToken).AnyContext(); _startupContext.MarkStartupComplete(result); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Startup/StartupActionRegistration.cs b/src/Foundatio.Extensions.Hosting/Startup/StartupActionRegistration.cs index 6b2f6c36c..c5fd3441a 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/StartupActionRegistration.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/StartupActionRegistration.cs @@ -5,24 +5,31 @@ using Foundatio.Utility; using Microsoft.Extensions.DependencyInjection; -namespace Foundatio.Extensions.Hosting.Startup { - public class StartupActionRegistration { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class StartupActionRegistration + { private readonly Func _action; private readonly Type _actionType; private static int _currentAutoPriority; - public StartupActionRegistration(string name, Type startupType, int? priority = null) { + public StartupActionRegistration(string name, Type startupType, int? priority = null) + { Name = name; _actionType = startupType; - if (!priority.HasValue) { + if (!priority.HasValue) + { var priorityAttribute = _actionType.GetCustomAttributes(typeof(StartupPriorityAttribute), true).FirstOrDefault() as StartupPriorityAttribute; Priority = priorityAttribute?.Priority ?? Interlocked.Increment(ref _currentAutoPriority); - } else { + } + else + { Priority = priority.Value; } } - public StartupActionRegistration(string name, Func action, int? priority = null) { + public StartupActionRegistration(string name, Func action, int? priority = null) + { Name = name; _action = action; if (!priority.HasValue) @@ -35,14 +42,18 @@ public StartupActionRegistration(string name, Func logger) { + public StartupActionsContext(ILogger logger) + { _logger = logger; } public bool IsStartupComplete { get; private set; } public RunStartupActionsResult Result { get; private set; } - internal void MarkStartupComplete(RunStartupActionsResult result) { + internal void MarkStartupComplete(RunStartupActionsResult result) + { IsStartupComplete = true; Result = result; } - public async Task WaitForStartupAsync(CancellationToken cancellationToken, TimeSpan? maxTimeToWait = null) { + public async Task WaitForStartupAsync(CancellationToken cancellationToken, TimeSpan? maxTimeToWait = null) + { bool isFirstWaiter = Interlocked.Increment(ref _waitCount) == 1; var startTime = SystemClock.UtcNow; var lastStatus = SystemClock.UtcNow; maxTimeToWait ??= TimeSpan.FromMinutes(5); - while (!cancellationToken.IsCancellationRequested && SystemClock.UtcNow.Subtract(startTime) < maxTimeToWait) { + while (!cancellationToken.IsCancellationRequested && SystemClock.UtcNow.Subtract(startTime) < maxTimeToWait) + { if (IsStartupComplete) return Result; - if (isFirstWaiter && SystemClock.UtcNow.Subtract(lastStatus) > TimeSpan.FromSeconds(5) && _logger.IsEnabled(LogLevel.Information)) { + if (isFirstWaiter && SystemClock.UtcNow.Subtract(lastStatus) > TimeSpan.FromSeconds(5) && _logger.IsEnabled(LogLevel.Information)) + { lastStatus = SystemClock.UtcNow; _logger.LogInformation("Waiting for startup actions to be completed for {Duration:mm\\:ss}...", SystemClock.UtcNow.Subtract(startTime)); } await Task.Delay(1000, cancellationToken).AnyContext(); } - + if (isFirstWaiter && _logger.IsEnabled(LogLevel.Error)) _logger.LogError("Timed out waiting for startup actions to be completed after {Duration:mm\\:ss}", SystemClock.UtcNow.Subtract(startTime)); return new RunStartupActionsResult { Success = false, ErrorMessage = $"Timed out waiting for startup actions to be completed after {SystemClock.UtcNow.Subtract(startTime):mm\\:ss}" }; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Startup/StartupExtensions.cs b/src/Foundatio.Extensions.Hosting/Startup/StartupExtensions.cs index 5da558091..8751defcf 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/StartupExtensions.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/StartupExtensions.cs @@ -13,15 +13,19 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Extensions.Hosting.Startup { - public class RunStartupActionsResult { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class RunStartupActionsResult + { public bool Success { get; set; } public string FailedActionName { get; set; } public string ErrorMessage { get; set; } } - - public static partial class StartupExtensions { - public static async Task RunStartupActionsAsync(this IServiceProvider serviceProvider, CancellationToken shutdownToken = default) { + + public static partial class StartupExtensions + { + public static async Task RunStartupActionsAsync(this IServiceProvider serviceProvider, CancellationToken shutdownToken = default) + { using var startupActionsScope = serviceProvider.CreateScope(); var sw = Stopwatch.StartNew(); var logger = startupActionsScope.ServiceProvider.GetService()?.CreateLogger("StartupActions") ?? NullLogger.Instance; @@ -29,13 +33,15 @@ public static async Task RunStartupActionsAsync(this IS logger.LogInformation("Found {StartupActionCount} registered startup action(s).", startupActions.Length); var startupActionPriorityGroups = startupActions.GroupBy(s => s.Priority).OrderBy(s => s.Key).ToArray(); - foreach (var startupActionGroup in startupActionPriorityGroups) { + foreach (var startupActionGroup in startupActionPriorityGroups) + { int startupActionsCount = startupActionGroup.Count(); string[] startupActionsNames = startupActionGroup.Select(a => a.Name).ToArray(); var swGroup = Stopwatch.StartNew(); string failedActionName = null; string errorMessage = null; - try { + try + { if (startupActionsCount == 1) logger.LogInformation("Running {StartupActions} (priority {Priority}) startup action...", startupActionsNames, startupActionGroup.Key); @@ -44,11 +50,15 @@ public static async Task RunStartupActionsAsync(this IS "Running {StartupActions} (priority {Priority}) startup actions in parallel...", startupActionsNames, startupActionGroup.Key); - await Task.WhenAll(startupActionGroup.Select(async a => { - try { + await Task.WhenAll(startupActionGroup.Select(async a => + { + try + { // ReSharper disable once AccessToDisposedClosure await a.RunAsync(startupActionsScope.ServiceProvider, shutdownToken).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { failedActionName = a.Name; errorMessage = ex.Message; logger.LogError(ex, "Error running {StartupAction} startup action: {Message}", a.Name, @@ -64,8 +74,11 @@ await Task.WhenAll(startupActionGroup.Select(async a => { else logger.LogInformation("Completed {StartupActions} startup actions in {Duration:mm\\:ss}.", startupActionsNames, swGroup.Elapsed); - } catch { - return new RunStartupActionsResult { + } + catch + { + return new RunStartupActionsResult + { Success = false, FailedActionName = failedActionName, ErrorMessage = errorMessage @@ -80,7 +93,8 @@ await Task.WhenAll(startupActionGroup.Select(async a => { return new RunStartupActionsResult { Success = true }; } - public static void AddStartupAction(this IServiceCollection services, int? priority = null) where T : IStartupAction { + public static void AddStartupAction(this IServiceCollection services, int? priority = null) where T : IStartupAction + { services.TryAddSingleton(); if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(RunStartupActionsService))) services.AddSingleton(); @@ -88,7 +102,8 @@ public static void AddStartupAction(this IServiceCollection services, int? pr services.AddTransient(s => new StartupActionRegistration(typeof(T).Name, typeof(T), priority)); } - public static void AddStartupAction(this IServiceCollection services, string name, int? priority = null) where T : IStartupAction { + public static void AddStartupAction(this IServiceCollection services, string name, int? priority = null) where T : IStartupAction + { services.TryAddSingleton(); if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(RunStartupActionsService))) services.AddSingleton(); @@ -96,33 +111,40 @@ public static void AddStartupAction(this IServiceCollection services, string services.AddTransient(s => new StartupActionRegistration(name, typeof(T), priority)); } - public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) + { services.AddStartupAction(name, ct => action(), priority); } - public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) + { services.AddStartupAction(name, (sp, ct) => action(sp), priority); } - public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Action action, int? priority = null) + { services.TryAddSingleton(); if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(RunStartupActionsService))) services.AddSingleton(); - services.AddTransient(s => new StartupActionRegistration(name, (sp, ct) => { + services.AddTransient(s => new StartupActionRegistration(name, (sp, ct) => + { action(sp, ct); return Task.CompletedTask; }, priority)); } - public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) + { services.AddStartupAction(name, (sp, ct) => action(), priority); } - public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) + { services.AddStartupAction(name, (sp, ct) => action(sp), priority); } - public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) { + public static void AddStartupAction(this IServiceCollection services, string name, Func action, int? priority = null) + { services.TryAddSingleton(); if (!services.Any(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType == typeof(RunStartupActionsService))) services.AddSingleton(); @@ -130,50 +152,59 @@ public static void AddStartupAction(this IServiceCollection services, string nam } public const string CheckForStartupActionsName = "CheckForStartupActions"; - public static IHealthChecksBuilder AddCheckForStartupActions(this IHealthChecksBuilder builder, params string[] tags) { + public static IHealthChecksBuilder AddCheckForStartupActions(this IHealthChecksBuilder builder, params string[] tags) + { return builder.AddCheck(CheckForStartupActionsName, null, tags); } - public static IApplicationBuilder UseWaitForStartupActionsBeforeServingRequests(this IApplicationBuilder builder) { + public static IApplicationBuilder UseWaitForStartupActionsBeforeServingRequests(this IApplicationBuilder builder) + { return builder.UseMiddleware(); } - public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder builder, string path, params string[] tags) { + public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder builder, string path, params string[] tags) + { if (tags == null) tags = Array.Empty(); - + return builder.UseHealthChecks(path, new HealthCheckOptions { Predicate = c => c.Tags.Any(t => tags.Contains(t, StringComparer.OrdinalIgnoreCase)) }); } - public static IApplicationBuilder UseReadyHealthChecks(this IApplicationBuilder builder, params string[] tags) { + public static IApplicationBuilder UseReadyHealthChecks(this IApplicationBuilder builder, params string[] tags) + { if (tags == null) tags = Array.Empty(); - var options = new HealthCheckOptions { + var options = new HealthCheckOptions + { Predicate = c => c.Tags.Any(t => tags.Contains(t, StringComparer.OrdinalIgnoreCase)) }; return builder.UseHealthChecks("/ready", options); } - public static void AddStartupActionToWaitForHealthChecks(this IServiceCollection services, params string[] tags) { + public static void AddStartupActionToWaitForHealthChecks(this IServiceCollection services, params string[] tags) + { if (tags == null) tags = Array.Empty(); - + services.AddStartupActionToWaitForHealthChecks(c => c.Tags.Any(t => tags.Contains(t, StringComparer.OrdinalIgnoreCase))); } - public static void AddStartupActionToWaitForHealthChecks(this IServiceCollection services, Func shouldWaitForHealthCheck = null) { + public static void AddStartupActionToWaitForHealthChecks(this IServiceCollection services, Func shouldWaitForHealthCheck = null) + { if (shouldWaitForHealthCheck == null) shouldWaitForHealthCheck = c => c.Tags.Contains("Critical", StringComparer.OrdinalIgnoreCase); - services.AddStartupAction("WaitForHealthChecks", async (sp, t) => { + services.AddStartupAction("WaitForHealthChecks", async (sp, t) => + { if (t.IsCancellationRequested) return; - + var healthCheckService = sp.GetService(); var logger = sp.GetService()?.CreateLogger("StartupActions") ?? NullLogger.Instance; var result = await healthCheckService.CheckHealthAsync(c => c.Name != CheckForStartupActionsName && shouldWaitForHealthCheck(c), t).AnyContext(); - while (result.Status == HealthStatus.Unhealthy && !t.IsCancellationRequested) { + while (result.Status == HealthStatus.Unhealthy && !t.IsCancellationRequested) + { logger.LogDebug("Last health check was unhealthy. Waiting 1s until next health check"); await Task.Delay(1000, t).AnyContext(); result = await healthCheckService.CheckHealthAsync(c => c.Name != CheckForStartupActionsName && shouldWaitForHealthCheck(c), t).AnyContext(); diff --git a/src/Foundatio.Extensions.Hosting/Startup/StartupHealthcheck.cs b/src/Foundatio.Extensions.Hosting/Startup/StartupHealthcheck.cs index 5f28a88e1..0da833aa7 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/StartupHealthcheck.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/StartupHealthcheck.cs @@ -1,18 +1,22 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; -namespace Foundatio.Extensions.Hosting.Startup { - public class StartupActionsHealthCheck : IHealthCheck { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class StartupActionsHealthCheck : IHealthCheck + { private readonly IServiceProvider _serviceProvider; - public StartupActionsHealthCheck(IServiceProvider serviceProvider) { + public StartupActionsHealthCheck(IServiceProvider serviceProvider) + { _serviceProvider = serviceProvider; } - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { var startupContext = _serviceProvider.GetService(); // no startup actions registered @@ -21,11 +25,11 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc if (startupContext.IsStartupComplete && startupContext.Result.Success) return Task.FromResult(HealthCheckResult.Healthy("All startup actions completed")); - + if (startupContext.IsStartupComplete && !startupContext.Result.Success) return Task.FromResult(HealthCheckResult.Unhealthy($"Startup action \"{startupContext.Result.FailedActionName}\" failed to complete: {startupContext.Result.ErrorMessage}")); - + return Task.FromResult(HealthCheckResult.Unhealthy("Startup actions have not completed")); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Startup/StartupPriorityAttribute.cs b/src/Foundatio.Extensions.Hosting/Startup/StartupPriorityAttribute.cs index 2cff1d749..ec42f31e2 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/StartupPriorityAttribute.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/StartupPriorityAttribute.cs @@ -1,11 +1,14 @@ using System; -namespace Foundatio.Extensions.Hosting.Startup { - public class StartupPriorityAttribute : Attribute { - public StartupPriorityAttribute(int priority) { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class StartupPriorityAttribute : Attribute + { + public StartupPriorityAttribute(int priority) + { Priority = priority; } public int Priority { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Extensions.Hosting/Startup/WaitForStartupActionsBeforeServingRequestsMiddleware.cs b/src/Foundatio.Extensions.Hosting/Startup/WaitForStartupActionsBeforeServingRequestsMiddleware.cs index 302b7130c..b51bfa66c 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/WaitForStartupActionsBeforeServingRequestsMiddleware.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/WaitForStartupActionsBeforeServingRequestsMiddleware.cs @@ -2,40 +2,50 @@ using System.Threading.Tasks; using Foundatio.Utility; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; -namespace Foundatio.Extensions.Hosting.Startup { - public class WaitForStartupActionsBeforeServingRequestsMiddleware { +namespace Foundatio.Extensions.Hosting.Startup +{ + public class WaitForStartupActionsBeforeServingRequestsMiddleware + { private readonly IServiceProvider _serviceProvider; private readonly RequestDelegate _next; private readonly IHostApplicationLifetime _applicationLifetime; - public WaitForStartupActionsBeforeServingRequestsMiddleware(IServiceProvider serviceProvider, RequestDelegate next, IHostApplicationLifetime applicationLifetime) { + public WaitForStartupActionsBeforeServingRequestsMiddleware(IServiceProvider serviceProvider, RequestDelegate next, IHostApplicationLifetime applicationLifetime) + { _serviceProvider = serviceProvider; _next = next; _applicationLifetime = applicationLifetime; } - public async Task Invoke(HttpContext httpContext) { + public async Task Invoke(HttpContext httpContext) + { var startupContext = _serviceProvider.GetService(); // no startup actions registered - if (startupContext == null) { + if (startupContext == null) + { await _next(httpContext).AnyContext(); return; } - if (startupContext.IsStartupComplete && startupContext.Result.Success) { + if (startupContext.IsStartupComplete && startupContext.Result.Success) + { await _next(httpContext).AnyContext(); - } else if (startupContext.IsStartupComplete && !startupContext.Result.Success) { + } + else if (startupContext.IsStartupComplete && !startupContext.Result.Success) + { // kill the server if the startup actions failed _applicationLifetime.StopApplication(); - } else { + } + else + { httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; httpContext.Response.Headers["Retry-After"] = "10"; await httpContext.Response.WriteAsync("Service Unavailable").AnyContext(); } } } -} \ No newline at end of file +} diff --git a/src/Foundatio.JsonNet/JsonNetSerializer.cs b/src/Foundatio.JsonNet/JsonNetSerializer.cs index 582ecc8d1..0e798885b 100644 --- a/src/Foundatio.JsonNet/JsonNetSerializer.cs +++ b/src/Foundatio.JsonNet/JsonNetSerializer.cs @@ -2,21 +2,26 @@ using System.IO; using Newtonsoft.Json; -namespace Foundatio.Serializer { - public class JsonNetSerializer : ITextSerializer { +namespace Foundatio.Serializer +{ + public class JsonNetSerializer : ITextSerializer + { private readonly JsonSerializer _serializer; - public JsonNetSerializer(JsonSerializerSettings settings = null) { + public JsonNetSerializer(JsonSerializerSettings settings = null) + { _serializer = JsonSerializer.Create(settings ?? new JsonSerializerSettings()); } - public void Serialize(object data, Stream outputStream) { + public void Serialize(object data, Stream outputStream) + { var writer = new JsonTextWriter(new StreamWriter(outputStream)); _serializer.Serialize(writer, data, data.GetType()); writer.Flush(); } - public object Deserialize(Stream inputStream, Type objectType) { + public object Deserialize(Stream inputStream, Type objectType) + { using var sr = new StreamReader(inputStream); using var reader = new JsonTextReader(sr); return _serializer.Deserialize(reader, objectType); diff --git a/src/Foundatio.MessagePack/MessagePackSerializer.cs b/src/Foundatio.MessagePack/MessagePackSerializer.cs index 5f79f1b01..ca36d53c4 100644 --- a/src/Foundatio.MessagePack/MessagePackSerializer.cs +++ b/src/Foundatio.MessagePack/MessagePackSerializer.cs @@ -3,20 +3,25 @@ using MessagePack; using MessagePack.Resolvers; -namespace Foundatio.Serializer { - public class MessagePackSerializer : ISerializer { +namespace Foundatio.Serializer +{ + public class MessagePackSerializer : ISerializer + { private readonly MessagePackSerializerOptions _options; - public MessagePackSerializer(MessagePackSerializerOptions options = null) { + public MessagePackSerializer(MessagePackSerializerOptions options = null) + { _options = options ?? MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance); } - public void Serialize(object data, Stream output) { + public void Serialize(object data, Stream output) + { MessagePack.MessagePackSerializer.Serialize(data.GetType(), output, data, _options); } - public object Deserialize(Stream input, Type objectType) { + public object Deserialize(Stream input, Type objectType) + { return MessagePack.MessagePackSerializer.Deserialize(objectType, input, _options); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.MetricsNET/MetricsNETClient.cs b/src/Foundatio.MetricsNET/MetricsNETClient.cs index f60048086..f93e9ad8f 100644 --- a/src/Foundatio.MetricsNET/MetricsNETClient.cs +++ b/src/Foundatio.MetricsNET/MetricsNETClient.cs @@ -1,20 +1,25 @@ using System; using Metrics; -namespace Foundatio.Metrics { - public class MetricsNETClient : IMetricsClient { - public void Counter(string name, int value = 1) { +namespace Foundatio.Metrics +{ + public class MetricsNETClient : IMetricsClient + { + public void Counter(string name, int value = 1) + { Metric.Counter(name, Unit.None).Increment(); } - public void Gauge(string name, double value) { + public void Gauge(string name, double value) + { Metric.Gauge(name, () => value, Unit.None); } - public void Timer(string name, int milliseconds) { + public void Timer(string name, int milliseconds) + { Metric.Timer(name, Unit.Calls, SamplingType.SlidingWindow, TimeUnit.Milliseconds).Record(milliseconds, TimeUnit.Milliseconds); } - public void Dispose() {} + public void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs index f61ae1a20..0d2d8fc5e 100644 --- a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs +++ b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs @@ -5,40 +5,46 @@ using System.Threading; using System.Threading.Tasks; using Foundatio.Caching; -using Foundatio.Xunit; using Foundatio.Metrics; using Foundatio.Utility; +using Foundatio.Xunit; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Caching { - public abstract class CacheClientTestsBase : TestWithLoggingBase { - protected CacheClientTestsBase(ITestOutputHelper output) : base(output) { +namespace Foundatio.Tests.Caching +{ + public abstract class CacheClientTestsBase : TestWithLoggingBase + { + protected CacheClientTestsBase(ITestOutputHelper output) : base(output) + { } - protected virtual ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { + protected virtual ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return null; } - public virtual async Task CanGetAllAsync() { + public virtual async Task CanGetAllAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); await cache.SetAsync("test1", 1); await cache.SetAsync("test2", 2); await cache.SetAsync("test3", 3); - var result = await cache.GetAllAsync(new [] { "test1", "test2", "test3" }); + var result = await cache.GetAllAsync(new[] { "test1", "test2", "test3" }); Assert.NotNull(result); Assert.Equal(3, result.Count); Assert.Equal(1, result["test1"].Value); Assert.Equal(2, result["test2"].Value); Assert.Equal(3, result["test3"].Value); - await cache.SetAsync("obj1", new SimpleModel {Data1 = "data 1", Data2 = 1 }); + await cache.SetAsync("obj1", new SimpleModel { Data1 = "data 1", Data2 = 1 }); await cache.SetAsync("obj2", new SimpleModel { Data1 = "data 2", Data2 = 2 }); await cache.SetAsync("obj3", (SimpleModel)null); await cache.SetAsync("obj4", new SimpleModel { Data1 = "test 1", Data2 = 4 }); @@ -62,12 +68,14 @@ public virtual async Task CanGetAllAsync() { } } - public virtual async Task CanGetAllWithOverlapAsync() { + public virtual async Task CanGetAllWithOverlapAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); await cache.SetAsync("test1", 1.0); @@ -90,12 +98,14 @@ await cache.SetAllAsync(new Dictionary { } } - public virtual async Task CanSetAsync() { + public virtual async Task CanSetAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); Assert.Equal(3, await cache.ListAddAsync("set", new List { 1, 1, 2, 3 })); @@ -110,12 +120,14 @@ public virtual async Task CanSetAsync() { } } - public virtual async Task CanSetAndGetValueAsync() { + public virtual async Task CanSetAndGetValueAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); Assert.False((await cache.GetAsync("donkey")).HasValue); @@ -159,12 +171,14 @@ public virtual async Task CanSetAndGetValueAsync() { } } - public virtual async Task CanAddAsync() { + public virtual async Task CanAddAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); const string key = "type-id"; @@ -184,17 +198,20 @@ public virtual async Task CanAddAsync() { } } - public virtual async Task CanAddConcurrentlyAsync() { + public virtual async Task CanAddConcurrentlyAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); string cacheKey = Guid.NewGuid().ToString("N").Substring(10); long adds = 0; - await Run.InParallelAsync(5, async i => { + await Run.InParallelAsync(5, async i => + { if (await cache.AddAsync(cacheKey, i, TimeSpan.FromMinutes(1))) Interlocked.Increment(ref adds); }); @@ -203,12 +220,14 @@ await Run.InParallelAsync(5, async i => { } } - public virtual async Task CanGetAsync() { + public virtual async Task CanGetAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); await cache.SetAsync("test", 1); @@ -222,7 +241,8 @@ public virtual async Task CanGetAsync() { Assert.Equal(1L, cacheValue2.Value); await cache.SetAsync("test", Int64.MaxValue); - await Assert.ThrowsAnyAsync(async () => { + await Assert.ThrowsAnyAsync(async () => + { var cacheValue3 = await cache.GetAsync("test"); Assert.False(cacheValue3.HasValue); }); @@ -233,12 +253,14 @@ await Assert.ThrowsAnyAsync(async () => { } } - public virtual async Task CanTryGetAsync() { + public virtual async Task CanTryGetAsync() + { var cache = GetCacheClient(false); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); await cache.SetAsync("test", 1); @@ -259,7 +281,8 @@ public virtual async Task CanTryGetAsync() { Assert.True(cacheValue.HasValue); Assert.Equal(Int64.MaxValue, cacheValue.Value); - await cache.SetAsync("test", new MyData { + await cache.SetAsync("test", new MyData + { Message = "test" }); cacheValue = await cache.GetAsync("test"); @@ -267,12 +290,14 @@ public virtual async Task CanTryGetAsync() { } } - public virtual async Task CanUseScopedCachesAsync() { + public virtual async Task CanUseScopedCachesAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var scopedCache1 = new ScopedCacheClient(cache, "scoped1"); @@ -329,12 +354,14 @@ public virtual async Task CanUseScopedCachesAsync() { } } - public virtual async Task CanRemoveByPrefixAsync() { + public virtual async Task CanRemoveByPrefixAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); string prefix = "blah:"; @@ -353,13 +380,15 @@ public virtual async Task CanRemoveByPrefixAsync() { Assert.Equal(1, await cache.RemoveByPrefixAsync(String.Empty)); } } - - public virtual async Task CanRemoveByPrefixMultipleEntriesAsync(int count) { + + public virtual async Task CanRemoveByPrefixMultipleEntriesAsync(int count) + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); const string prefix = "prefix:"; await cache.SetAsync("test", 1); @@ -374,16 +403,19 @@ public virtual async Task CanRemoveByPrefixMultipleEntriesAsync(int count) { } } - public virtual async Task CanSetAndGetObjectAsync() { + public virtual async Task CanSetAndGetObjectAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var dt = DateTimeOffset.Now; - var value = new MyData { + var value = new MyData + { Type = "test", Date = dt, Message = "Hello World" @@ -399,12 +431,14 @@ public virtual async Task CanSetAndGetObjectAsync() { } } - public virtual async Task CanSetExpirationAsync() { + public virtual async Task CanSetExpirationAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var expiresAt = SystemClock.UtcNow.AddMilliseconds(300); @@ -423,15 +457,18 @@ public virtual async Task CanSetExpirationAsync() { } } - public virtual async Task CanSetMinMaxExpirationAsync() { + public virtual async Task CanSetMinMaxExpirationAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); - using (TestSystemClock.Install()) { + using (TestSystemClock.Install()) + { var now = DateTime.UtcNow; TestSystemClock.SetFrozenTime(now); @@ -440,7 +477,7 @@ public virtual async Task CanSetMinMaxExpirationAsync() { Assert.False(await cache.SetAsync("test2", 1, DateTime.MinValue)); Assert.True(await cache.SetAsync("test3", 1, DateTime.MaxValue)); Assert.True(await cache.SetAsync("test4", 1, DateTime.MaxValue - now.AddDays(-1))); - + Assert.Equal(1, (await cache.GetAsync("test1")).Value); Assert.InRange((await cache.GetExpirationAsync("test1")).Value, expires.Subtract(TimeSpan.FromSeconds(10)), expires); @@ -453,12 +490,14 @@ public virtual async Task CanSetMinMaxExpirationAsync() { } } - public virtual async Task CanIncrementAsync() { + public virtual async Task CanIncrementAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); Assert.True(await cache.SetAsync("test", 0)); @@ -467,19 +506,22 @@ public virtual async Task CanIncrementAsync() { Assert.Equal(0, await cache.IncrementAsync("test3", 0)); // The following is not supported by redis. - if (cache is InMemoryCacheClient) { + if (cache is InMemoryCacheClient) + { Assert.True(await cache.SetAsync("test2", "stringValue")); Assert.Equal(1, await cache.IncrementAsync("test2")); } } } - public virtual async Task CanIncrementAndExpireAsync() { + public virtual async Task CanIncrementAndExpireAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); bool success = await cache.SetAsync("test", 0); @@ -494,13 +536,15 @@ public virtual async Task CanIncrementAndExpireAsync() { Assert.False((await cache.GetAsync("test")).HasValue); } } - - public virtual async Task CanReplaceIfEqual() { + + public virtual async Task CanReplaceIfEqual() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); const string cacheKey = "replace-if-equal"; @@ -518,13 +562,15 @@ public virtual async Task CanReplaceIfEqual() { Assert.NotNull(await cache.GetExpirationAsync(cacheKey)); } } - - public virtual async Task CanRemoveIfEqual() { + + public virtual async Task CanRemoveIfEqual() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); Assert.True(await cache.AddAsync("remove-if-equal", "123")); @@ -540,14 +586,16 @@ public virtual async Task CanRemoveIfEqual() { } } - public virtual async Task CanRoundTripLargeNumbersAsync() { + public virtual async Task CanRoundTripLargeNumbersAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); - + double value = 2 * 1000 * 1000 * 1000; Assert.True(await cache.SetAsync("test", value)); Assert.Equal(value, await cache.GetAsync("test", 0)); @@ -567,12 +615,14 @@ public virtual async Task CanRoundTripLargeNumbersAsync() { } } - public virtual async Task CanGetAndSetDateTimeAsync() { + public virtual async Task CanGetAndSetDateTimeAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); DateTime value = SystemClock.UtcNow.Floor(TimeSpan.FromSeconds(1)); @@ -605,25 +655,25 @@ public virtual async Task CanGetAndSetDateTimeAsync() { Assert.Equal(lowerUnixTimeValue, await cache.GetAsync("test", 0)); await cache.RemoveAsync("test"); - + Assert.Equal(unixTimeValue, await cache.SetIfLowerAsync("test", value)); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); Assert.Equal(value, await cache.GetUnixTimeMillisecondsAsync("test")); - + Assert.Equal(0, await cache.SetIfLowerAsync("test", value.AddHours(1))); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); Assert.Equal(value, await cache.GetUnixTimeMillisecondsAsync("test")); - + await cache.RemoveAsync("test"); - + Assert.Equal(unixTimeValue, await cache.SetIfHigherAsync("test", value)); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); Assert.Equal(value, await cache.GetUnixTimeMillisecondsAsync("test")); - + Assert.Equal(0, await cache.SetIfHigherAsync("test", value.AddHours(-1))); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); Assert.Equal(value, await cache.GetUnixTimeMillisecondsAsync("test")); - + var higherValue = value + TimeSpan.FromHours(1); var higherUnixTimeValue = higherValue.ToUnixTimeMilliseconds(); Assert.Equal((long)TimeSpan.FromHours(1).TotalMilliseconds, await cache.SetIfHigherAsync("test", higherValue)); @@ -632,12 +682,14 @@ public virtual async Task CanGetAndSetDateTimeAsync() { } } - public virtual async Task CanRoundTripLargeNumbersWithExpirationAsync() { + public virtual async Task CanRoundTripLargeNumbersWithExpirationAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var minExpiration = TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(59)).Add(TimeSpan.FromSeconds(55)); @@ -665,12 +717,14 @@ public virtual async Task CanRoundTripLargeNumbersWithExpirationAsync() { } } - public virtual async Task CanManageListsAsync() { + public virtual async Task CanManageListsAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); await Assert.ThrowsAsync(() => cache.ListAddAsync(null, 1)); @@ -699,7 +753,7 @@ public virtual async Task CanManageListsAsync() { var stringResult = await cache.GetListAsync("stringlist"); Assert.Single(stringResult.Value); Assert.Equal("myvalue", stringResult.Value.First()); - + await cache.ListRemoveAsync("stringlist", "myvalue"); stringResult = await cache.GetListAsync("stringlist"); Assert.Empty(stringResult.Value); @@ -724,25 +778,26 @@ public virtual async Task CanManageListsAsync() { Assert.NotNull(result); Assert.Empty(result.Value); - await Assert.ThrowsAnyAsync(async () => { + await Assert.ThrowsAnyAsync(async () => + { await cache.AddAsync("key1", 1); await cache.ListAddAsync("key1", 1); }); - + // test paging through items in list await cache.ListAddAsync("testpaging", new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }); var pagedResult = await cache.GetListAsync("testpaging", 1, 5); Assert.NotNull(pagedResult); Assert.Equal(5, pagedResult.Value.Count); Assert.Equal(pagedResult.Value.ToArray(), new[] { 1, 2, 3, 4, 5 }); - + pagedResult = await cache.GetListAsync("testpaging", 2, 5); Assert.NotNull(pagedResult); Assert.Equal(5, pagedResult.Value.Count); Assert.Equal(pagedResult.Value.ToArray(), new[] { 6, 7, 8, 9, 10 }); - + await cache.ListAddAsync("testpaging", new[] { 21, 22 }); - + pagedResult = await cache.GetListAsync("testpaging", 5, 5); Assert.NotNull(pagedResult); Assert.Equal(2, pagedResult.Value.Count); @@ -756,18 +811,21 @@ await Assert.ThrowsAnyAsync(async () => { } } - public virtual async Task MeasureThroughputAsync() { + public virtual async Task MeasureThroughputAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var start = SystemClock.UtcNow; const int itemCount = 10000; var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - for (int i = 0; i < itemCount; i++) { + for (int i = 0; i < itemCount; i++) + { await cache.SetAsync("test", 13422); await cache.SetAsync("flag", true); Assert.Equal(13422, (await cache.GetAsync("test")).Value); @@ -780,22 +838,26 @@ public virtual async Task MeasureThroughputAsync() { } } - public virtual async Task MeasureSerializerSimpleThroughputAsync() { + public virtual async Task MeasureSerializerSimpleThroughputAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var start = SystemClock.UtcNow; const int itemCount = 10000; var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - for (int i = 0; i < itemCount; i++) { - await cache.SetAsync("test", new SimpleModel { - Data1 = "Hello", - Data2 = 12 - }); + for (int i = 0; i < itemCount; i++) + { + await cache.SetAsync("test", new SimpleModel + { + Data1 = "Hello", + Data2 = 12 + }); var model = await cache.GetAsync("test"); Assert.True(model.HasValue); Assert.Equal("Hello", model.Value.Data1); @@ -807,23 +869,28 @@ public virtual async Task MeasureSerializerSimpleThroughputAsync() { } } - public virtual async Task MeasureSerializerComplexThroughputAsync() { + public virtual async Task MeasureSerializerComplexThroughputAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var start = SystemClock.UtcNow; const int itemCount = 10000; var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - for (int i = 0; i < itemCount; i++) { - await cache.SetAsync("test", new ComplexModel { + for (int i = 0; i < itemCount; i++) + { + await cache.SetAsync("test", new ComplexModel + { Data1 = "Hello", Data2 = 12, Data3 = true, - Simple = new SimpleModel { + Simple = new SimpleModel + { Data1 = "hi", Data2 = 13 }, @@ -858,12 +925,14 @@ public virtual async Task MeasureSerializerComplexThroughputAsync() { } } - public class SimpleModel { + public class SimpleModel + { public string Data1 { get; set; } public int Data2 { get; set; } } - public class ComplexModel { + public class ComplexModel + { public string Data1 { get; set; } public int Data2 { get; set; } public SimpleModel Simple { get; set; } @@ -873,7 +942,8 @@ public class ComplexModel { public SampleDictionary DerivedDictionarySimples { get; set; } } - public class MyData { + public class MyData + { private readonly string _blah = "blah"; public string Blah => _blah; public string Type { get; set; } @@ -881,58 +951,72 @@ public class MyData { public string Message { get; set; } } - public class SampleDictionary : IDictionary { + public class SampleDictionary : IDictionary + { private readonly IDictionary _dictionary; - public SampleDictionary() { + public SampleDictionary() + { _dictionary = new Dictionary(); } - public SampleDictionary(IDictionary dictionary) { + public SampleDictionary(IDictionary dictionary) + { _dictionary = new Dictionary(dictionary); } - public SampleDictionary(IEqualityComparer comparer) { + public SampleDictionary(IEqualityComparer comparer) + { _dictionary = new Dictionary(comparer); } - public SampleDictionary(IDictionary dictionary, IEqualityComparer comparer) { + public SampleDictionary(IDictionary dictionary, IEqualityComparer comparer) + { _dictionary = new Dictionary(dictionary, comparer); } - public void Add(TKey key, TValue value) { + public void Add(TKey key, TValue value) + { _dictionary.Add(key, value); } - public void Add(KeyValuePair item) { + public void Add(KeyValuePair item) + { _dictionary.Add(item); } - public bool Remove(TKey key) { + public bool Remove(TKey key) + { return _dictionary.Remove(key); } - public bool Remove(KeyValuePair item) { + public bool Remove(KeyValuePair item) + { return _dictionary.Remove(item); } - public void Clear() { + public void Clear() + { _dictionary.Clear(); } - public bool ContainsKey(TKey key) { + public bool ContainsKey(TKey key) + { return _dictionary.ContainsKey(key); } - public bool Contains(KeyValuePair item) { + public bool Contains(KeyValuePair item) + { return _dictionary.Contains(item); } - public bool TryGetValue(TKey key, out TValue value) { + public bool TryGetValue(TKey key, out TValue value) + { return _dictionary.TryGetValue(key, out value); } - public void CopyTo(KeyValuePair[] array, int arrayIndex) { + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { _dictionary.CopyTo(array, arrayIndex); } @@ -944,17 +1028,20 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) { public bool IsReadOnly => _dictionary.IsReadOnly; - public TValue this[TKey key] { + public TValue this[TKey key] + { get => _dictionary[key]; set => _dictionary[key] = value; } - public IEnumerator> GetEnumerator() { + public IEnumerator> GetEnumerator() + { return _dictionary.GetEnumerator(); } - IEnumerator IEnumerable.GetEnumerator() { + IEnumerator IEnumerable.GetEnumerator() + { return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs b/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs index 943401d5d..330cb7528 100644 --- a/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs +++ b/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs @@ -9,111 +9,133 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Caching { - public class HybridCacheClientTests: CacheClientTestsBase, IDisposable { +namespace Foundatio.Tests.Caching +{ + public class HybridCacheClientTests : CacheClientTestsBase, IDisposable + { protected readonly ICacheClient _distributedCache = new InMemoryCacheClient(new InMemoryCacheClientOptions()); protected readonly IMessageBus _messageBus = new InMemoryMessageBus(new InMemoryMessageBusOptions()); - public HybridCacheClientTests(ITestOutputHelper output) : base(output) {} + public HybridCacheClientTests(ITestOutputHelper output) : base(output) { } - protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { + protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return new HybridCacheClient(_distributedCache, _messageBus, new InMemoryCacheClientOptions { CloneValues = true, ShouldThrowOnSerializationError = shouldThrowOnSerializationError }, Log); } [Fact] - public override Task CanGetAllAsync() { + public override Task CanGetAllAsync() + { return base.CanGetAllAsync(); } [Fact] - public override Task CanGetAllWithOverlapAsync() { + public override Task CanGetAllWithOverlapAsync() + { return base.CanGetAllWithOverlapAsync(); } [Fact] - public override Task CanSetAsync() { + public override Task CanSetAsync() + { return base.CanSetAsync(); } [Fact] - public override Task CanSetAndGetValueAsync() { + public override Task CanSetAndGetValueAsync() + { return base.CanSetAndGetValueAsync(); } [Fact] - public override Task CanAddAsync() { + public override Task CanAddAsync() + { return base.CanAddAsync(); } [Fact] - public override Task CanAddConcurrentlyAsync() { + public override Task CanAddConcurrentlyAsync() + { return base.CanAddConcurrentlyAsync(); } [Fact] - public override Task CanTryGetAsync() { + public override Task CanTryGetAsync() + { return base.CanTryGetAsync(); } [Fact] - public override Task CanUseScopedCachesAsync() { + public override Task CanUseScopedCachesAsync() + { return base.CanUseScopedCachesAsync(); } [Fact] - public override Task CanSetAndGetObjectAsync() { + public override Task CanSetAndGetObjectAsync() + { return base.CanSetAndGetObjectAsync(); } [Fact] - public override Task CanRemoveByPrefixAsync() { + public override Task CanRemoveByPrefixAsync() + { return base.CanRemoveByPrefixAsync(); } [Theory] [InlineData(50)] [InlineData(500)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) { + public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + { return base.CanRemoveByPrefixMultipleEntriesAsync(count); } [Fact] - public override Task CanSetExpirationAsync() { + public override Task CanSetExpirationAsync() + { return base.CanSetExpirationAsync(); } [Fact] - public override Task CanIncrementAsync() { + public override Task CanIncrementAsync() + { return base.CanIncrementAsync(); } [Fact] - public override Task CanIncrementAndExpireAsync() { + public override Task CanIncrementAndExpireAsync() + { return base.CanIncrementAndExpireAsync(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() { + public override Task CanGetAndSetDateTimeAsync() + { return base.CanGetAndSetDateTimeAsync(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() { + public override Task CanRoundTripLargeNumbersAsync() + { return base.CanRoundTripLargeNumbersAsync(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() { + public override Task CanRoundTripLargeNumbersWithExpirationAsync() + { return base.CanRoundTripLargeNumbersWithExpirationAsync(); } [Fact] - public override Task CanManageListsAsync() { + public override Task CanManageListsAsync() + { return base.CanManageListsAsync(); } [Fact] - public virtual async Task WillUseLocalCache() { + public virtual async Task WillUseLocalCache() + { using var firstCache = GetCacheClient() as HybridCacheClient; Assert.NotNull(firstCache); @@ -143,29 +165,34 @@ public virtual async Task WillUseLocalCache() { } [Fact] - public virtual async Task WillExpireRemoteItems() { + public virtual async Task WillExpireRemoteItems() + { using var firstCache = GetCacheClient() as HybridCacheClient; Assert.NotNull(firstCache); var firstResetEvent = new AsyncAutoResetEvent(false); - void ExpiredHandler(object sender, ItemExpiredEventArgs args) { + void ExpiredHandler(object sender, ItemExpiredEventArgs args) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("First local cache expired: {Key}", args.Key); firstResetEvent.Set(); } - using (firstCache.LocalCache.ItemExpired.AddSyncHandler(ExpiredHandler)) { + using (firstCache.LocalCache.ItemExpired.AddSyncHandler(ExpiredHandler)) + { using var secondCache = GetCacheClient() as HybridCacheClient; Assert.NotNull(secondCache); var secondResetEvent = new AsyncAutoResetEvent(false); - void ExpiredHandler2(object sender, ItemExpiredEventArgs args) { + void ExpiredHandler2(object sender, ItemExpiredEventArgs args) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Second local cache expired: {Key}", args.Key); secondResetEvent.Set(); } - using (secondCache.LocalCache.ItemExpired.AddSyncHandler(ExpiredHandler2)) { + using (secondCache.LocalCache.ItemExpired.AddSyncHandler(ExpiredHandler2)) + { string cacheKey = "will-expire-remote"; _logger.LogTrace("First Set"); Assert.True(await firstCache.AddAsync(cacheKey, new SimpleModel { Data1 = "test" }, TimeSpan.FromMilliseconds(250))); @@ -188,7 +215,8 @@ void ExpiredHandler2(object sender, ItemExpiredEventArgs args) { } [Fact] - public virtual async Task WillWorkWithSets() { + public virtual async Task WillWorkWithSets() + { using var firstCache = GetCacheClient() as HybridCacheClient; Assert.NotNull(firstCache); @@ -202,7 +230,8 @@ public virtual async Task WillWorkWithSets() { Assert.Equal(3, values.Value.Count); } - public void Dispose() { + public void Dispose() + { _distributedCache.Dispose(); _messageBus.Dispose(); } diff --git a/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs b/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs index ca370fe0c..e5c9a2def 100644 --- a/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs +++ b/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs @@ -1,25 +1,30 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.AsyncEx; +using Foundatio.Utility; -namespace Foundatio.Tests.Extensions { - public static class TaskExtensions { +namespace Foundatio.Tests.Extensions +{ + public static class TaskExtensions + { [DebuggerStepThrough] - public static async Task WaitAsync(this AsyncManualResetEvent resetEvent, TimeSpan timeout) { + public static async Task WaitAsync(this AsyncManualResetEvent resetEvent, TimeSpan timeout) + { using var timeoutCancellationTokenSource = timeout.ToCancellationTokenSource(); await resetEvent.WaitAsync(timeoutCancellationTokenSource.Token).AnyContext(); } [DebuggerStepThrough] - public static async Task WaitAsync(this AsyncAutoResetEvent resetEvent, TimeSpan timeout) { + public static async Task WaitAsync(this AsyncAutoResetEvent resetEvent, TimeSpan timeout) + { using var timeoutCancellationTokenSource = timeout.ToCancellationTokenSource(); await resetEvent.WaitAsync(timeoutCancellationTokenSource.Token).AnyContext(); } - public static Task WaitAsync(this AsyncCountdownEvent countdownEvent, TimeSpan timeout) { + public static Task WaitAsync(this AsyncCountdownEvent countdownEvent, TimeSpan timeout) + { return Task.WhenAny(countdownEvent.WaitAsync(), SystemClock.SleepAsync(timeout)); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs b/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs index 13ebb4b2f..02bef7d44 100644 --- a/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs +++ b/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs @@ -4,18 +4,22 @@ using Foundatio.Jobs; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Jobs { - public class HelloWorldJob : JobBase { +namespace Foundatio.Tests.Jobs +{ + public class HelloWorldJob : JobBase + { private readonly string _id; - public HelloWorldJob(ILoggerFactory loggerFactory) : base(loggerFactory) { + public HelloWorldJob(ILoggerFactory loggerFactory) : base(loggerFactory) + { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } public static int GlobalRunCount; public int RunCount { get; set; } - protected override Task RunInternalAsync(JobContext context) { + protected override Task RunInternalAsync(JobContext context) + { RunCount++; Interlocked.Increment(ref GlobalRunCount); @@ -26,16 +30,19 @@ protected override Task RunInternalAsync(JobContext context) { } } - public class FailingJob : JobBase { + public class FailingJob : JobBase + { private readonly string _id; public int RunCount { get; set; } - public FailingJob(ILoggerFactory loggerFactory) : base(loggerFactory) { + public FailingJob(ILoggerFactory loggerFactory) : base(loggerFactory) + { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } - protected override Task RunInternalAsync(JobContext context) { + protected override Task RunInternalAsync(JobContext context) + { RunCount++; if (_logger.IsEnabled(LogLevel.Trace)) @@ -45,22 +52,26 @@ protected override Task RunInternalAsync(JobContext context) { } } - public class LongRunningJob : JobBase { + public class LongRunningJob : JobBase + { private readonly string _id; private int _iterationCount; - public LongRunningJob(ILoggerFactory loggerFactory) : base(loggerFactory) { + public LongRunningJob(ILoggerFactory loggerFactory) : base(loggerFactory) + { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } public int IterationCount => _iterationCount; - protected override Task RunInternalAsync(JobContext context) { - do { + protected override Task RunInternalAsync(JobContext context) + { + do + { Interlocked.Increment(ref _iterationCount); if (context.CancellationToken.IsCancellationRequested) break; - + if (_iterationCount % 10000 == 0 && _logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("LongRunningJob Running: instance={Id} iterations={IterationCount}", _id, IterationCount); } while (true); @@ -68,4 +79,4 @@ protected override Task RunInternalAsync(JobContext context) { return Task.FromResult(JobResult.Success); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs b/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs index bddbaf6c4..2ea57daaf 100644 --- a/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs +++ b/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -7,32 +8,36 @@ using Foundatio.Caching; using Foundatio.Jobs; using Foundatio.Lock; -using Foundatio.Xunit; using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Utility; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -using System.Diagnostics; -namespace Foundatio.Tests.Jobs { - public abstract class JobQueueTestsBase: TestWithLoggingBase { +namespace Foundatio.Tests.Jobs +{ + public abstract class JobQueueTestsBase : TestWithLoggingBase + { private readonly ActivitySource _activitySource = new(nameof(JobQueueTestsBase)); public JobQueueTestsBase(ITestOutputHelper output) : base(output) { } protected abstract IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay); - public virtual async Task ActivityWillFlowThroughQueueJobAsync() { + public virtual async Task ActivityWillFlowThroughQueueJobAsync() + { using var queue = GetSampleWorkItemQueue(retries: 0, retryDelay: TimeSpan.Zero); await queue.DeleteQueueAsync(); Activity parentActivity = null; - using var listener = new ActivityListener { + using var listener = new ActivityListener + { ShouldListenTo = s => s.Name == nameof(JobQueueTestsBase) || s.Name == "Foundatio", Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, - ActivityStarted = a => { + ActivityStarted = a => + { if (a.OperationName != "ProcessQueueEntry") return; @@ -46,7 +51,8 @@ public virtual async Task ActivityWillFlowThroughQueueJobAsync() { parentActivity = _activitySource.StartActivity("Parent"); Assert.NotNull(parentActivity); - var enqueueTask = await queue.EnqueueAsync(new SampleQueueWorkItem { + var enqueueTask = await queue.EnqueueAsync(new SampleQueueWorkItem + { Created = SystemClock.UtcNow, Path = "somepath" }); @@ -63,12 +69,14 @@ public virtual async Task ActivityWillFlowThroughQueueJobAsync() { Assert.Equal(1, stats.Dequeued); } - public virtual async Task CanRunQueueJobAsync() { + public virtual async Task CanRunQueueJobAsync() + { const int workItemCount = 100; using var queue = GetSampleWorkItemQueue(retries: 0, retryDelay: TimeSpan.Zero); await queue.DeleteQueueAsync(); - var enqueueTask = Run.InParallelAsync(workItemCount, index => queue.EnqueueAsync(new SampleQueueWorkItem { + var enqueueTask = Run.InParallelAsync(workItemCount, index => queue.EnqueueAsync(new SampleQueueWorkItem + { Created = SystemClock.UtcNow, Path = "somepath" + index })); @@ -83,7 +91,8 @@ public virtual async Task CanRunQueueJobAsync() { Assert.Equal(workItemCount, stats.Dequeued); } - public virtual async Task CanRunQueueJobWithLockFailAsync() { + public virtual async Task CanRunQueueJobWithLockFailAsync() + { const int workItemCount = 10; const int allowedLockCount = 5; Log.SetLogLevel(LogLevel.Trace); @@ -91,9 +100,11 @@ public virtual async Task CanRunQueueJobWithLockFailAsync() { using var queue = GetSampleWorkItemQueue(retries: 3, retryDelay: TimeSpan.Zero); await queue.DeleteQueueAsync(); - var enqueueTask = Run.InParallelAsync(workItemCount, index => { + var enqueueTask = Run.InParallelAsync(workItemCount, index => + { _logger.LogInformation($"Enqueue #{index}"); - return queue.EnqueueAsync(new SampleQueueWorkItem { + return queue.EnqueueAsync(new SampleQueueWorkItem + { Created = SystemClock.UtcNow, Path = "somepath" + index }); @@ -114,7 +125,8 @@ public virtual async Task CanRunQueueJobWithLockFailAsync() { Assert.Equal(allowedLockCount, stats.Deadletter); } - public virtual async Task CanRunMultipleQueueJobsAsync() { + public virtual async Task CanRunMultipleQueueJobsAsync() + { const int jobCount = 5; const int workItemCount = 100; @@ -123,8 +135,10 @@ public virtual async Task CanRunMultipleQueueJobsAsync() { using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { LoggerFactory = Log, Buffered = true }); var queues = new List>(); - try { - for (int i = 0; i < jobCount; i++) { + try + { + for (int i = 0; i < jobCount; i++) + { var q = GetSampleWorkItemQueue(retries: 1, retryDelay: TimeSpan.Zero); await q.DeleteQueueAsync(); q.AttachBehavior(new MetricsQueueBehavior(metrics, "test", loggerFactory: Log)); @@ -132,9 +146,11 @@ public virtual async Task CanRunMultipleQueueJobsAsync() { } _logger.LogInformation("Done setting up queues"); - var enqueueTask = Run.InParallelAsync(workItemCount, index => { + var enqueueTask = Run.InParallelAsync(workItemCount, index => + { var queue = queues[RandomData.GetInt(0, jobCount - 1)]; - return queue.EnqueueAsync(new SampleQueueWorkItem { + return queue.EnqueueAsync(new SampleQueueWorkItem + { Created = SystemClock.UtcNow, Path = RandomData.GetString() }); @@ -142,7 +158,8 @@ public virtual async Task CanRunMultipleQueueJobsAsync() { _logger.LogInformation("Done enqueueing"); var cancellationTokenSource = new CancellationTokenSource(); - await Run.InParallelAsync(jobCount, async index => { + await Run.InParallelAsync(jobCount, async index => + { var queue = queues[index - 1]; var job = new SampleQueueWithRandomErrorsAndAbandonsJob(queue, metrics, Log); await job.RunUntilEmptyAsync(cancellationTokenSource.Token); @@ -153,7 +170,8 @@ await Run.InParallelAsync(jobCount, async index => { await enqueueTask; var queueStats = new List(); - for (int i = 0; i < queues.Count; i++) { + for (int i = 0; i < queues.Count; i++) + { var stats = await queues[i].GetQueueStatsAsync(); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Queue#{Id}: Working: {Working} Completed: {Completed} Abandoned: {Abandoned} Error: {Errors} Deadletter: {Deadletter}", i, stats.Working, stats.Completed, stats.Abandoned, stats.Errors, stats.Deadletter); @@ -167,12 +185,15 @@ await Run.InParallelAsync(jobCount, async index => { var queueSummary = await metrics.GetQueueStatsAsync("test.samplequeueworkitem"); Assert.Equal(queueStats.Sum(s => s.Completed), queueSummary.Completed.Count); Assert.InRange(queueStats.Sum(s => s.Completed), 0, workItemCount); - } finally { - foreach (var q in queues) { + } + finally + { + foreach (var q in queues) + { await q.DeleteQueueAsync(); q.Dispose(); } } } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs b/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs index b18e862c8..83ca8bd69 100644 --- a/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs +++ b/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs @@ -8,89 +8,108 @@ using Foundatio.Queues; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Jobs { - public class SampleQueueWithRandomErrorsAndAbandonsJob : QueueJobBase { +namespace Foundatio.Tests.Jobs +{ + public class SampleQueueWithRandomErrorsAndAbandonsJob : QueueJobBase + { private readonly IMetricsClient _metrics; - public SampleQueueWithRandomErrorsAndAbandonsJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) { + public SampleQueueWithRandomErrorsAndAbandonsJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + { _metrics = metrics ?? NullMetricsClient.Instance; } - protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { + protected override Task ProcessQueueEntryAsync(QueueEntryContext context) + { _metrics.Counter("dequeued"); - if (RandomData.GetBool(10)) { + if (RandomData.GetBool(10)) + { _metrics.Counter("errors"); throw new Exception("Boom!"); } - if (RandomData.GetBool(10)) { + if (RandomData.GetBool(10)) + { _metrics.Counter("abandoned"); return Task.FromResult(JobResult.FailedWithMessage("Abandoned")); } - + _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } - - public class SampleQueueJob : QueueJobBase { + + public class SampleQueueJob : QueueJobBase + { private readonly IMetricsClient _metrics; - public SampleQueueJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) { + public SampleQueueJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + { _metrics = metrics ?? NullMetricsClient.Instance; } - protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { + protected override Task ProcessQueueEntryAsync(QueueEntryContext context) + { _metrics.Counter("dequeued"); _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } - public class SampleQueueJobWithLocking : QueueJobBase { + public class SampleQueueJobWithLocking : QueueJobBase + { private readonly IMetricsClient _metrics; private readonly ILockProvider _lockProvider; - public SampleQueueJobWithLocking(IQueue queue, IMetricsClient metrics, ILockProvider lockProvider, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) { + public SampleQueueJobWithLocking(IQueue queue, IMetricsClient metrics, ILockProvider lockProvider, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + { _metrics = metrics ?? NullMetricsClient.Instance; _lockProvider = lockProvider; } - protected override Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = default(CancellationToken)) { + protected override Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = default(CancellationToken)) + { if (_lockProvider != null) return _lockProvider.AcquireAsync("job", TimeSpan.FromMilliseconds(100), TimeSpan.Zero); return base.GetQueueEntryLockAsync(queueEntry, cancellationToken); } - protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { + protected override Task ProcessQueueEntryAsync(QueueEntryContext context) + { _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } - public class SampleQueueWorkItem { + public class SampleQueueWorkItem + { public string Path { get; set; } public DateTime Created { get; set; } } - public class SampleJob : JobBase { + public class SampleJob : JobBase + { private readonly IMetricsClient _metrics; - public SampleJob(IMetricsClient metrics, ILoggerFactory loggerFactory) : base(loggerFactory) { + public SampleJob(IMetricsClient metrics, ILoggerFactory loggerFactory) : base(loggerFactory) + { _metrics = metrics; } - protected override Task RunInternalAsync(JobContext context) { + protected override Task RunInternalAsync(JobContext context) + { _metrics.Counter("runs"); - if (RandomData.GetBool(10)) { + if (RandomData.GetBool(10)) + { _metrics.Counter("errors"); throw new Exception("Boom!"); } - if (RandomData.GetBool(10)) { + if (RandomData.GetBool(10)) + { _metrics.Counter("failed"); return Task.FromResult(JobResult.FailedWithMessage("Failed")); } @@ -99,4 +118,4 @@ protected override Task RunInternalAsync(JobContext context) { return Task.FromResult(JobResult.Success); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs b/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs index bdd652c2c..9e426f4b3 100644 --- a/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs +++ b/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs @@ -6,23 +6,28 @@ using Foundatio.Lock; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Jobs { - public class ThrottledJob : JobWithLockBase { - public ThrottledJob(ICacheClient client, ILoggerFactory loggerFactory = null) : base(loggerFactory) { +namespace Foundatio.Tests.Jobs +{ + public class ThrottledJob : JobWithLockBase + { + public ThrottledJob(ICacheClient client, ILoggerFactory loggerFactory = null) : base(loggerFactory) + { _locker = new ThrottlingLockProvider(client, 1, TimeSpan.FromMilliseconds(100), loggerFactory); } private readonly ILockProvider _locker; public int RunCount { get; set; } - - protected override Task GetLockAsync(CancellationToken cancellationToken = default(CancellationToken)) { + + protected override Task GetLockAsync(CancellationToken cancellationToken = default(CancellationToken)) + { return _locker.AcquireAsync(nameof(ThrottledJob), acquireTimeout: TimeSpan.Zero); } - protected override Task RunInternalAsync(JobContext context) { + protected override Task RunInternalAsync(JobContext context) + { RunCount++; return Task.FromResult(JobResult.Success); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs b/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs index 2a57b576a..c4c0f7611 100644 --- a/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs +++ b/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs @@ -2,9 +2,12 @@ using Foundatio.Jobs; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Jobs { - public class WithDependencyJob : JobBase { - public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = null) : base(loggerFactory) { +namespace Foundatio.Tests.Jobs +{ + public class WithDependencyJob : JobBase + { + public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = null) : base(loggerFactory) + { Dependency = dependency; } @@ -12,14 +15,16 @@ public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = public int RunCount { get; set; } - protected override Task RunInternalAsync(JobContext context) { + protected override Task RunInternalAsync(JobContext context) + { RunCount++; return Task.FromResult(JobResult.Success); } } - public class MyDependency { + public class MyDependency + { public int MyProperty { get; set; } } } diff --git a/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs b/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs index e8e07f21c..da8073a8b 100644 --- a/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs +++ b/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs @@ -9,21 +9,26 @@ using Microsoft.Extensions.Logging; using Xunit; -namespace Foundatio.Tests.Jobs { - public class WithLockingJob : JobWithLockBase { +namespace Foundatio.Tests.Jobs +{ + public class WithLockingJob : JobWithLockBase + { private readonly ILockProvider _locker; - public WithLockingJob(ILoggerFactory loggerFactory) : base(loggerFactory) { - _locker = new CacheLockProvider(new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = loggerFactory } ), new InMemoryMessageBus(new InMemoryMessageBusOptions { LoggerFactory = loggerFactory }), loggerFactory); + public WithLockingJob(ILoggerFactory loggerFactory) : base(loggerFactory) + { + _locker = new CacheLockProvider(new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = loggerFactory }), new InMemoryMessageBus(new InMemoryMessageBusOptions { LoggerFactory = loggerFactory }), loggerFactory); } public int RunCount { get; set; } - protected override Task GetLockAsync(CancellationToken cancellationToken = default(CancellationToken)){ + protected override Task GetLockAsync(CancellationToken cancellationToken = default(CancellationToken)) + { return _locker.AcquireAsync(nameof(WithLockingJob), TimeSpan.FromSeconds(1), TimeSpan.Zero); } - protected override async Task RunInternalAsync(JobContext context) { + protected override async Task RunInternalAsync(JobContext context) + { RunCount++; await SystemClock.SleepAsync(150, context.CancellationToken); diff --git a/src/Foundatio.TestHarness/Locks/LockTestBase.cs b/src/Foundatio.TestHarness/Locks/LockTestBase.cs index c10a62de7..60ca14d38 100644 --- a/src/Foundatio.TestHarness/Locks/LockTestBase.cs +++ b/src/Foundatio.TestHarness/Locks/LockTestBase.cs @@ -1,30 +1,35 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Foundatio.Caching; using Foundatio.Lock; -using Foundatio.Xunit; using Foundatio.Utility; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -using System.Linq; -namespace Foundatio.Tests.Locks { - public abstract class LockTestBase : TestWithLoggingBase { - protected LockTestBase(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Locks +{ + public abstract class LockTestBase : TestWithLoggingBase + { + protected LockTestBase(ITestOutputHelper output) : base(output) { } - protected virtual ILockProvider GetThrottlingLockProvider(int maxHits, TimeSpan period) { + protected virtual ILockProvider GetThrottlingLockProvider(int maxHits, TimeSpan period) + { return null; } - protected virtual ILockProvider GetLockProvider() { + protected virtual ILockProvider GetLockProvider() + { return null; } - public virtual async Task CanAcquireAndReleaseLockAsync() { + public virtual async Task CanAcquireAndReleaseLockAsync() + { Log.SetLogLevel(LogLevel.Trace); var locker = GetLockProvider(); @@ -33,13 +38,16 @@ public virtual async Task CanAcquireAndReleaseLockAsync() { var lock1 = await locker.AcquireAsync("test", acquireTimeout: TimeSpan.FromMilliseconds(100), timeUntilExpires: TimeSpan.FromSeconds(1)); - try { + try + { Assert.NotNull(lock1); Assert.True(await locker.IsLockedAsync("test")); var lock2Task = locker.AcquireAsync("test", acquireTimeout: TimeSpan.FromMilliseconds(250)); await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(250)); Assert.Null(await lock2Task); - } finally { + } + finally + { await lock1.ReleaseAsync(); } @@ -48,8 +56,10 @@ public virtual async Task CanAcquireAndReleaseLockAsync() { int counter = 0; bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - await Run.InParallelAsync(25, async i => { - bool success = await locker.TryUsingAsync("test", () => { + await Run.InParallelAsync(25, async i => + { + bool success = await locker.TryUsingAsync("test", () => + { Interlocked.Increment(ref counter); }, acquireTimeout: TimeSpan.FromSeconds(10)); @@ -59,7 +69,8 @@ await Run.InParallelAsync(25, async i => { Assert.Equal(25, counter); } - public virtual async Task CanReleaseLockMultipleTimes() { + public virtual async Task CanReleaseLockMultipleTimes() + { var locker = GetLockProvider(); if (locker == null) return; @@ -69,11 +80,11 @@ public virtual async Task CanReleaseLockMultipleTimes() { Assert.False(await locker.IsLockedAsync("test")); var lock2 = await locker.AcquireAsync("test", acquireTimeout: TimeSpan.FromMilliseconds(100), timeUntilExpires: TimeSpan.FromSeconds(1)); - + // has already been released, should not release other people's lock await lock1.ReleaseAsync(); Assert.True(await locker.IsLockedAsync("test")); - + // has already been released, should not release other people's lock await lock1.DisposeAsync(); Assert.True(await locker.IsLockedAsync("test")); @@ -82,7 +93,8 @@ public virtual async Task CanReleaseLockMultipleTimes() { Assert.False(await locker.IsLockedAsync("test")); } - public virtual async Task LockWillTimeoutAsync() { + public virtual async Task LockWillTimeoutAsync() + { Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); @@ -107,7 +119,8 @@ public virtual async Task LockWillTimeoutAsync() { Assert.NotNull(testLock); } - public virtual async Task CanAcquireMultipleResources() { + public virtual async Task CanAcquireMultipleResources() + { Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); @@ -120,21 +133,22 @@ public virtual async Task CanAcquireMultipleResources() { var testLock = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250)); _logger.LogInformation(testLock != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.NotNull(testLock); - + resources.Add("other"); var testLock2 = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250), acquireTimeout: TimeSpan.FromMilliseconds(10)); _logger.LogInformation(testLock2 != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.Null(testLock2); - + await testLock.RenewAsync(); await testLock.ReleaseAsync(); - + var testLock3 = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250), acquireTimeout: TimeSpan.FromMilliseconds(10)); _logger.LogInformation(testLock3 != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.NotNull(testLock3); } - public virtual async Task CanAcquireMultipleScopedResources() { + public virtual async Task CanAcquireMultipleScopedResources() + { Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); @@ -144,26 +158,27 @@ public virtual async Task CanAcquireMultipleScopedResources() { return; locker = new ScopedLockProvider(locker, "myscope"); - + var resources = new List { "test1", "test2", "test3", "test4", "test5" }; var testLock = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250)); _logger.LogInformation(testLock != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.NotNull(testLock); - + resources.Add("other"); var testLock2 = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250), acquireTimeout: TimeSpan.FromMilliseconds(10)); _logger.LogInformation(testLock2 != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.Null(testLock2); - + await testLock.RenewAsync(); await testLock.ReleaseAsync(); - + var testLock3 = await locker.AcquireAsync(resources, timeUntilExpires: TimeSpan.FromMilliseconds(250), acquireTimeout: TimeSpan.FromMilliseconds(10)); _logger.LogInformation(testLock3 != null ? "Acquired lock #1" : "Unable to acquire lock #1"); Assert.NotNull(testLock3); } - public virtual async Task CanAcquireLocksInParallel() { + public virtual async Task CanAcquireLocksInParallel() + { var locker = GetLockProvider(); if (locker == null) return; @@ -175,7 +190,8 @@ public virtual async Task CanAcquireLocksInParallel() { var used = new List(); int concurrency = 0; - await Parallel.ForEachAsync(Enumerable.Range(1, COUNT), async (index, ct) => { + await Parallel.ForEachAsync(Enumerable.Range(1, COUNT), async (index, ct) => + { await using var myLock = await locker.AcquireAsync("test", TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); Assert.NotNull(myLock); @@ -195,7 +211,8 @@ await Parallel.ForEachAsync(Enumerable.Range(1, COUNT), async (index, ct) => { Assert.Equal(COUNT, used.Count); } - public virtual async Task LockOneAtATimeAsync() { + public virtual async Task LockOneAtATimeAsync() + { var locker = GetLockProvider(); if (locker == null) return; @@ -203,26 +220,34 @@ public virtual async Task LockOneAtATimeAsync() { Log.SetLogLevel(LogLevel.Trace); int successCount = 0; - var lockTask1 = Task.Run(async () => { - if (await DoLockedWorkAsync(locker)) { + var lockTask1 = Task.Run(async () => + { + if (await DoLockedWorkAsync(locker)) + { Interlocked.Increment(ref successCount); _logger.LogInformation("LockTask1 Success"); } }); - var lockTask2 = Task.Run(async () => { - if (await DoLockedWorkAsync(locker)) { + var lockTask2 = Task.Run(async () => + { + if (await DoLockedWorkAsync(locker)) + { Interlocked.Increment(ref successCount); _logger.LogInformation("LockTask2 Success"); } }); - var lockTask3 = Task.Run(async () => { - if (await DoLockedWorkAsync(locker)) { + var lockTask3 = Task.Run(async () => + { + if (await DoLockedWorkAsync(locker)) + { Interlocked.Increment(ref successCount); _logger.LogInformation("LockTask3 Success"); } }); - var lockTask4 = Task.Run(async () => { - if (await DoLockedWorkAsync(locker)) { + var lockTask4 = Task.Run(async () => + { + if (await DoLockedWorkAsync(locker)) + { Interlocked.Increment(ref successCount); _logger.LogInformation("LockTask4 Success"); } @@ -231,22 +256,25 @@ public virtual async Task LockOneAtATimeAsync() { await Task.WhenAll(lockTask1, lockTask2, lockTask3, lockTask4); Assert.Equal(1, successCount); - await Task.Run(async () => { + await Task.Run(async () => + { if (await DoLockedWorkAsync(locker)) Interlocked.Increment(ref successCount); }); Assert.Equal(2, successCount); } - private Task DoLockedWorkAsync(ILockProvider locker) { + private Task DoLockedWorkAsync(ILockProvider locker) + { return locker.TryUsingAsync("DoLockedWork", async () => await SystemClock.SleepAsync(500), TimeSpan.FromMinutes(1), TimeSpan.Zero); } - public virtual async Task WillThrottleCallsAsync() { + public virtual async Task WillThrottleCallsAsync() + { Log.MinimumLevel = LogLevel.Trace; Log.SetLogLevel(LogLevel.Information); Log.SetLogLevel(LogLevel.Trace); - + const int allowedLocks = 25; var period = TimeSpan.FromSeconds(2); @@ -259,9 +287,10 @@ public virtual async Task WillThrottleCallsAsync() { // sleep until start of throttling period while (SystemClock.UtcNow.Ticks % period.Ticks < TimeSpan.TicksPerMillisecond * 100) Thread.Sleep(10); - + var sw = Stopwatch.StartNew(); - for (int i = 1; i <= allowedLocks; i++) { + for (int i = 1; i <= allowedLocks; i++) + { _logger.LogInformation("Allowed Locks: {Id}", i); var l = await locker.AcquireAsync(lockName); Assert.NotNull(l); diff --git a/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs b/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs index 1f580d0b0..3819ae95e 100644 --- a/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs +++ b/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs @@ -4,40 +4,48 @@ using System.Threading; using System.Threading.Tasks; using Exceptionless; -using Foundatio.Tests.Extensions; -using Foundatio.Xunit; +using Foundatio.AsyncEx; using Foundatio.Messaging; +using Foundatio.Tests.Extensions; +using Foundatio.Tests.Metrics; using Foundatio.Utility; -using Xunit; -using Foundatio.AsyncEx; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; +using Xunit; using Xunit.Abstractions; -using Foundatio.Tests.Metrics; -namespace Foundatio.Tests.Messaging { - public abstract class MessageBusTestBase : TestWithLoggingBase { - protected MessageBusTestBase(ITestOutputHelper output) : base(output) { +namespace Foundatio.Tests.Messaging +{ + public abstract class MessageBusTestBase : TestWithLoggingBase + { + protected MessageBusTestBase(ITestOutputHelper output) : base(output) + { Log.SetLogLevel(LogLevel.Debug); } - protected virtual IMessageBus GetMessageBus(Func config = null) { + protected virtual IMessageBus GetMessageBus(Func config = null) + { return null; } - protected virtual Task CleanupMessageBusAsync(IMessageBus messageBus) { + protected virtual Task CleanupMessageBusAsync(IMessageBus messageBus) + { messageBus?.Dispose(); return Task.CompletedTask; } - public virtual async Task CanUseMessageOptionsAsync() { + public virtual async Task CanUseMessageOptionsAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { - using var listener = new ActivityListener { + try + { + using var listener = new ActivityListener + { ShouldListenTo = s => s.Name == FoundatioDiagnostics.ActivitySource.Name, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = activity => _logger.LogInformation("Start: " + activity.DisplayName), @@ -50,7 +58,8 @@ public virtual async Task CanUseMessageOptionsAsync() { Assert.Equal(Activity.Current, activity); var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync>(msg => { + await messageBus.SubscribeAsync>(msg => + { _logger.LogTrace("Got message"); Assert.Equal("Hello", msg.Body.Data); @@ -65,10 +74,12 @@ await messageBus.SubscribeAsync>(msg => { }); await SystemClock.SleepAsync(1000); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello", Items = { { "Test", "Test" } } - }, new MessageOptions { + }, new MessageOptions + { Properties = new Dictionary { { "hey", "now" } } @@ -77,19 +88,24 @@ await messageBus.PublishAsync(new SimpleMessageA { await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSendMessageAsync() { + public virtual async Task CanSendMessageAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { _logger.LogTrace("Got message"); Assert.Equal("Hello", msg.Data); Assert.True(msg.Items.ContainsKey("Test")); @@ -98,7 +114,8 @@ await messageBus.SubscribeAsync(msg => { }); await SystemClock.SleepAsync(100); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello", Items = { { "Test", "Test" } } }); @@ -106,19 +123,24 @@ await messageBus.PublishAsync(new SimpleMessageA { await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanHandleNullMessageAsync() { + public virtual async Task CanHandleNullMessageAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { countdown.Signal(); throw new Exception(); }); @@ -129,19 +151,24 @@ await messageBus.SubscribeAsync(msg => { await countdown.WaitAsync(TimeSpan.FromSeconds(1)); Assert.Equal(1, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSendDerivedMessageAsync() { + public virtual async Task CanSendDerivedMessageAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { _logger.LogTrace("Got message"); Assert.Equal("Hello", msg.Data); countdown.Signal(); @@ -149,28 +176,35 @@ await messageBus.SubscribeAsync(msg => { }); await SystemClock.SleepAsync(100); - await messageBus.PublishAsync(new DerivedSimpleMessageA { + await messageBus.PublishAsync(new DerivedSimpleMessageA + { Data = "Hello" }); _logger.LogTrace("Published one..."); await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSendMappedMessageAsync() { - var messageBus = GetMessageBus(b => { + public virtual async Task CanSendMappedMessageAsync() + { + var messageBus = GetMessageBus(b => + { b.MessageTypeMappings.Add(nameof(SimpleMessageA), typeof(SimpleMessageA)); return b; }); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { _logger.LogTrace("Got message"); Assert.Equal("Hello", msg.Data); countdown.Signal(); @@ -178,28 +212,34 @@ await messageBus.SubscribeAsync(msg => { }); await SystemClock.SleepAsync(100); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); _logger.LogTrace("Published one..."); await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSendDelayedMessageAsync() { + public virtual async Task CanSendDelayedMessageAsync() + { const int numConcurrentMessages = 1000; var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(numConcurrentMessages); int messages = 0; - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { if (++messages % 50 == 0) if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Total Processed {Messages} messages", messages); @@ -208,8 +248,10 @@ await messageBus.SubscribeAsync(msg => { }); var sw = Stopwatch.StartNew(); - await Run.InParallelAsync(numConcurrentMessages, async i => { - await messageBus.PublishAsync(new SimpleMessageA { + await Run.InParallelAsync(numConcurrentMessages, async i => + { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello", Count = i }, new MessageOptions { DeliveryDelay = TimeSpan.FromMilliseconds(RandomData.GetInt(0, 100)) }); @@ -223,21 +265,27 @@ await messageBus.PublishAsync(new SimpleMessageA { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Processed {Processed} in {Duration:g}", numConcurrentMessages - countdown.CurrentCount, sw.Elapsed); Assert.Equal(0, countdown.CurrentCount); Assert.InRange(sw.Elapsed.TotalMilliseconds, 50, 30000); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSubscribeConcurrentlyAsync() { + public virtual async Task CanSubscribeConcurrentlyAsync() + { const int iterations = 100; var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(iterations * 10); - await Run.InParallelAsync(10, i => { - return messageBus.SubscribeAsync(msg => { + await Run.InParallelAsync(10, i => + { + return messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); @@ -247,23 +295,28 @@ await Run.InParallelAsync(10, i => { await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); } - finally { + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanReceiveMessagesConcurrentlyAsync() { + public virtual async Task CanReceiveMessagesConcurrentlyAsync() + { const int iterations = 100; var messageBus = GetMessageBus(); if (messageBus == null) return; var messageBuses = new List(10); - try { + try + { var countdown = new AsyncCountdownEvent(iterations * 10); - await Run.InParallelAsync(10, async i => { + await Run.InParallelAsync(10, async i => + { var bus = GetMessageBus(); - await bus.SubscribeAsync(msg => { + await bus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); @@ -271,13 +324,16 @@ await bus.SubscribeAsync(msg => { messageBuses.Add(bus); }); var subscribe = Run.InParallelAsync(iterations, - i => { + i => + { SystemClock.Sleep(RandomData.GetInt(0, 10)); return messageBuses.Random().SubscribeAsync(msg => Task.CompletedTask); }); - var publish = Run.InParallelAsync(iterations + 3, i => { - return i switch { + var publish = Run.InParallelAsync(iterations + 3, i => + { + return i switch + { 1 => messageBus.PublishAsync(new DerivedSimpleMessageA { Data = "Hello" }), 2 => messageBus.PublishAsync(new Derived2SimpleMessageA { Data = "Hello" }), 3 => messageBus.PublishAsync(new Derived3SimpleMessageA { Data = "Hello" }), @@ -298,7 +354,9 @@ await bus.SubscribeAsync(msg => { await Task.WhenAll(subscribe, publish); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { foreach (var mb in messageBuses) await CleanupMessageBusAsync(mb); @@ -306,218 +364,276 @@ await bus.SubscribeAsync(msg => { } } - public virtual async Task CanSendMessageToMultipleSubscribersAsync() { + public virtual async Task CanSendMessageToMultipleSubscribersAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(3); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanTolerateSubscriberFailureAsync() { + public virtual async Task CanTolerateSubscriberFailureAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(4); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { throw new Exception(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task WillOnlyReceiveSubscribedMessageTypeAsync() { + public virtual async Task WillOnlyReceiveSubscribedMessageTypeAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Fail("Received wrong message type"); }); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task WillReceiveDerivedMessageTypesAsync() { + public virtual async Task WillReceiveDerivedMessageTypesAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(2); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageB { + await messageBus.PublishAsync(new SimpleMessageB + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageC { + await messageBus.PublishAsync(new SimpleMessageC + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSubscribeToRawMessagesAsync() { + public virtual async Task CanSubscribeToRawMessagesAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(3); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.True(msg.Type.Contains(nameof(SimpleMessageA)) || msg.Type.Contains(nameof(SimpleMessageB)) || msg.Type.Contains(nameof(SimpleMessageC))); countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageB { + await messageBus.PublishAsync(new SimpleMessageB + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageC { + await messageBus.PublishAsync(new SimpleMessageC + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanSubscribeToAllMessageTypesAsync() { + public virtual async Task CanSubscribeToAllMessageTypesAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(3); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { countdown.Signal(); }); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageB { + await messageBus.PublishAsync(new SimpleMessageB + { Data = "Hello" }); - await messageBus.PublishAsync(new SimpleMessageC { + await messageBus.PublishAsync(new SimpleMessageC + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task WontKeepMessagesWithNoSubscribersAsync() { + public virtual async Task WontKeepMessagesWithNoSubscribersAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(1); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); await SystemClock.SleepAsync(100); - await messageBus.SubscribeAsync(msg => { + await messageBus.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); - + await countdown.WaitAsync(TimeSpan.FromMilliseconds(100)); Assert.Equal(1, countdown.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanCancelSubscriptionAsync() { + public virtual async Task CanCancelSubscriptionAsync() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - try { + try + { var countdown = new AsyncCountdownEvent(2); long messageCount = 0; var cancellationTokenSource = new CancellationTokenSource(); - await messageBus.SubscribeAsync(async msg => { + await messageBus.SubscribeAsync(async msg => + { _logger.LogTrace("SimpleAMessage received"); Interlocked.Increment(ref messageCount); await cancellationTokenSource.CancelAsync(); @@ -526,7 +642,8 @@ await messageBus.SubscribeAsync(async msg => { await messageBus.SubscribeAsync(msg => countdown.Signal()); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); @@ -535,39 +652,48 @@ await messageBus.PublishAsync(new SimpleMessageA { Assert.Equal(1, messageCount); countdown = new AsyncCountdownEvent(1); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); await countdown.WaitAsync(TimeSpan.FromSeconds(2)); Assert.Equal(0, countdown.CurrentCount); Assert.Equal(1, messageCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus); } } - public virtual async Task CanReceiveFromMultipleSubscribersAsync() { + public virtual async Task CanReceiveFromMultipleSubscribersAsync() + { var messageBus1 = GetMessageBus(); if (messageBus1 == null) return; - try { + try + { var countdown1 = new AsyncCountdownEvent(1); - await messageBus1.SubscribeAsync(msg => { + await messageBus1.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown1.Signal(); }); var messageBus2 = GetMessageBus(); - try { + try + { var countdown2 = new AsyncCountdownEvent(1); - await messageBus2.SubscribeAsync(msg => { + await messageBus2.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown2.Signal(); }); - await messageBus1.PublishAsync(new SimpleMessageA { + await messageBus1.PublishAsync(new SimpleMessageA + { Data = "Hello" }); @@ -575,20 +701,25 @@ await messageBus1.PublishAsync(new SimpleMessageA { Assert.Equal(0, countdown1.CurrentCount); await countdown2.WaitAsync(TimeSpan.FromSeconds(20)); Assert.Equal(0, countdown2.CurrentCount); - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus2); } - } finally { + } + finally + { await CleanupMessageBusAsync(messageBus1); } } - public virtual void CanDisposeWithNoSubscribersOrPublishers() { + public virtual void CanDisposeWithNoSubscribersOrPublishers() + { var messageBus = GetMessageBus(); if (messageBus == null) return; - using (messageBus) {} + using (messageBus) { } } } } diff --git a/src/Foundatio.TestHarness/Messaging/Samples.cs b/src/Foundatio.TestHarness/Messaging/Samples.cs index cd3a4b5ce..e47d9f494 100644 --- a/src/Foundatio.TestHarness/Messaging/Samples.cs +++ b/src/Foundatio.TestHarness/Messaging/Samples.cs @@ -2,14 +2,17 @@ using Foundatio.Tests.Messaging; using Foundatio.Utility; -namespace Foundatio.Tests.Messaging { - public class SimpleMessageA : ISimpleMessage { - public SimpleMessageA() { +namespace Foundatio.Tests.Messaging +{ + public class SimpleMessageA : ISimpleMessage + { + public SimpleMessageA() + { Items = new DataDictionary(); } public string Data { get; set; } public int Count { get; set; } - + public IDictionary Items { get; set; } } @@ -25,27 +28,33 @@ public class Derived9SimpleMessageA : SimpleMessageA { } public class Derived10SimpleMessageA : SimpleMessageA { } public class NeverPublishedMessage { } - public class SimpleMessageB : ISimpleMessage { + public class SimpleMessageB : ISimpleMessage + { public string Data { get; set; } } - public class SimpleMessageC { + public class SimpleMessageC + { public string Data { get; set; } } - public interface ISimpleMessage { + public interface ISimpleMessage + { string Data { get; set; } } } -namespace Foundatio.Tests.MessagingAlt { - public class SimpleMessageA : ISimpleMessage { - public SimpleMessageA() { +namespace Foundatio.Tests.MessagingAlt +{ + public class SimpleMessageA : ISimpleMessage + { + public SimpleMessageA() + { Items = new DataDictionary(); } public string Data { get; set; } public int Count { get; set; } - + public IDictionary Items { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs b/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs index 65aa88a36..de1fc20b4 100644 --- a/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs +++ b/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Concurrent; -using System.Diagnostics.Metrics; using System.Collections.Generic; -using System.Linq; -using System.Diagnostics; using System.Collections.Immutable; -using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using Foundatio.AsyncEx; using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Metrics { - public class DiagnosticsMetricsCollector : IDisposable { +namespace Foundatio.Tests.Metrics +{ + public class DiagnosticsMetricsCollector : IDisposable + { private readonly MeterListener _meterListener = new(); private readonly ConcurrentQueue> _byteMeasurements = new(); private readonly ConcurrentQueue> _shortMeasurements = new(); @@ -27,58 +29,67 @@ public class DiagnosticsMetricsCollector : IDisposable { public DiagnosticsMetricsCollector(string metricNameOrPrefix, ILogger logger, int maxMeasurementCountPerType = 1000) : this(n => n.StartsWith(metricNameOrPrefix), logger, maxMeasurementCountPerType) { } - public DiagnosticsMetricsCollector(Func shouldCollect, ILogger logger, int maxMeasurementCount = 1000) { + public DiagnosticsMetricsCollector(Func shouldCollect, ILogger logger, int maxMeasurementCount = 1000) + { _logger = logger; _maxMeasurementCountPerType = maxMeasurementCount; - _meterListener.InstrumentPublished = (instrument, listener) => { + _meterListener.InstrumentPublished = (instrument, listener) => + { if (shouldCollect(instrument.Meter.Name)) listener.EnableMeasurementEvents(instrument); }; - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _byteMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_byteMeasurements.Count > _maxMeasurementCountPerType) _byteMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _shortMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_shortMeasurements.Count > _maxMeasurementCountPerType) _shortMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _intMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_intMeasurements.Count > _maxMeasurementCountPerType) _intMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _longMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_longMeasurements.Count > _maxMeasurementCountPerType) _longMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _floatMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_floatMeasurements.Count > _maxMeasurementCountPerType) _floatMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _doubleMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_doubleMeasurements.Count > _maxMeasurementCountPerType) _doubleMeasurements.TryDequeue(out _); _measurementEvent.Set(); }); - _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => { + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { _decimalMeasurements.Enqueue(new RecordedMeasurement(instrument, measurement, ref tags, state)); if (_decimalMeasurements.Count > _maxMeasurementCountPerType) _decimalMeasurements.TryDequeue(out _); @@ -88,148 +99,217 @@ public DiagnosticsMetricsCollector(Func shouldCollect, ILogger log _meterListener.Start(); } - public void RecordObservableInstruments() { + public void RecordObservableInstruments() + { _meterListener.RecordObservableInstruments(); } - public IReadOnlyCollection> GetMeasurements(string name = null) where T : struct { - if (typeof(T) == typeof(byte)) { + public IReadOnlyCollection> GetMeasurements(string name = null) where T : struct + { + if (typeof(T) == typeof(byte)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_byteMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_byteMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(short)) { + } + else if (typeof(T) == typeof(short)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_shortMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_shortMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(int)) { + } + else if (typeof(T) == typeof(int)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_intMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_intMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(long)) { + } + else if (typeof(T) == typeof(long)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_longMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_longMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(float)) { + } + else if (typeof(T) == typeof(float)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_floatMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_floatMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(double)) { + } + else if (typeof(T) == typeof(double)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_doubleMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_doubleMeasurements).Where(m => m.Name == name)); - } else if (typeof(T) == typeof(decimal)) { + } + else if (typeof(T) == typeof(decimal)) + { if (name == null) return ImmutableList.CreateRange((IEnumerable>)_decimalMeasurements); else return ImmutableList.CreateRange(((IEnumerable>)_decimalMeasurements).Where(m => m.Name == name)); - } else { + } + else + { return ImmutableList.Create>(); } // byte, short, int, long, float, double, decimal } - public int GetCount(string name) where T : struct { + public int GetCount(string name) where T : struct + { return GetMeasurements().Count(m => m.Name == name); } - public double GetSum(string name) where T : struct { - if (typeof(T) == typeof(byte)) { + public double GetSum(string name) where T : struct + { + if (typeof(T) == typeof(byte)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(short)) { + } + else if (typeof(T) == typeof(short)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(int)) { + } + else if (typeof(T) == typeof(int)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(long)) { + } + else if (typeof(T) == typeof(long)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(float)) { + } + else if (typeof(T) == typeof(float)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(double)) { + } + else if (typeof(T) == typeof(double)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => m.Value); - } else if (typeof(T) == typeof(decimal)) { + } + else if (typeof(T) == typeof(decimal)) + { var measurements = GetMeasurements(name); return measurements.Sum(m => (double)m.Value); - } else { + } + else + { return 0; } } - public double GetAvg(string name) where T : struct { - if (typeof(T) == typeof(byte)) { + public double GetAvg(string name) where T : struct + { + if (typeof(T) == typeof(byte)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(short)) { + } + else if (typeof(T) == typeof(short)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(int)) { + } + else if (typeof(T) == typeof(int)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(long)) { + } + else if (typeof(T) == typeof(long)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(float)) { + } + else if (typeof(T) == typeof(float)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(double)) { + } + else if (typeof(T) == typeof(double)) + { var measurements = GetMeasurements(name); return measurements.Average(m => m.Value); - } else if (typeof(T) == typeof(decimal)) { + } + else if (typeof(T) == typeof(decimal)) + { var measurements = GetMeasurements(name); return measurements.Average(m => (double)m.Value); - } else { + } + else + { return 0; } } - public double GetMax(string name) where T : struct { - if (typeof(T) == typeof(byte)) { + public double GetMax(string name) where T : struct + { + if (typeof(T) == typeof(byte)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(short)) { + } + else if (typeof(T) == typeof(short)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(int)) { + } + else if (typeof(T) == typeof(int)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(long)) { + } + else if (typeof(T) == typeof(long)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(float)) { + } + else if (typeof(T) == typeof(float)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(double)) { + } + else if (typeof(T) == typeof(double)) + { var measurements = GetMeasurements(name); return measurements.Max(m => m.Value); - } else if (typeof(T) == typeof(decimal)) { + } + else if (typeof(T) == typeof(decimal)) + { var measurements = GetMeasurements(name); return measurements.Max(m => (double)m.Value); - } else { + } + else + { return 0; } } - public async Task WaitForCounterAsync(string statName, long count = 1, TimeSpan? timeout = null) where T : struct { + public async Task WaitForCounterAsync(string statName, long count = 1, TimeSpan? timeout = null) where T : struct + { using var cancellationTokenSource = timeout.ToCancellationTokenSource(TimeSpan.FromMinutes(1)); return await WaitForCounterAsync(statName, () => Task.CompletedTask, count, cancellationTokenSource.Token).AnyContext(); } - public async Task WaitForCounterAsync(string name, Func work, long count = 1, CancellationToken cancellationToken = default) where T : struct { + public async Task WaitForCounterAsync(string name, Func work, long count = 1, CancellationToken cancellationToken = default) where T : struct + { if (count <= 0) return true; - if (cancellationToken == default) { + if (cancellationToken == default) + { using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1)); cancellationToken = cancellationTokenSource.Token; } @@ -245,10 +325,13 @@ public async Task WaitForCounterAsync(string name, Func work, lon _logger.LogTrace("Wait: count={Count}", count); currentCount = (int)GetSum(name); - while (!cancellationToken.IsCancellationRequested && currentCount < targetCount) { - try { + while (!cancellationToken.IsCancellationRequested && currentCount < targetCount) + { + try + { await _measurementEvent.WaitAsync(cancellationToken); - } catch (OperationCanceledException) { } + } + catch (OperationCanceledException) { } currentCount = (int)GetSum(name); _logger.LogTrace("Got new measurement: count={CurrentCount} expected={Count}", currentCount, targetCount); } @@ -258,15 +341,18 @@ public async Task WaitForCounterAsync(string name, Func work, lon return currentCount >= targetCount; } - public void Dispose() { + public void Dispose() + { GC.SuppressFinalize(this); _meterListener?.Dispose(); } } [DebuggerDisplay("{Name}={Value}")] - public struct RecordedMeasurement where T : struct { - public RecordedMeasurement(Instrument instrument, T value, ref ReadOnlySpan> tags, object state) { + public struct RecordedMeasurement where T : struct + { + public RecordedMeasurement(Instrument instrument, T value, ref ReadOnlySpan> tags, object state) + { Instrument = instrument; Name = Instrument.Name; Value = value; @@ -283,4 +369,4 @@ public RecordedMeasurement(Instrument instrument, T value, ref ReadOnlySpan Tags { get; } public object State { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs b/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs index 85e0db40b..ca0454a13 100644 --- a/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs +++ b/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs @@ -2,25 +2,28 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Foundatio.Xunit; using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Tests.Queue; using Foundatio.Utility; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; #pragma warning disable AsyncFixer04 // A disposable object used in a fire & forget async call -namespace Foundatio.Tests.Metrics { - public abstract class MetricsClientTestBase : TestWithLoggingBase { - public MetricsClientTestBase(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Metrics +{ + public abstract class MetricsClientTestBase : TestWithLoggingBase + { + public MetricsClientTestBase(ITestOutputHelper output) : base(output) { } public abstract IMetricsClient GetMetricsClient(bool buffered = false); - public virtual async Task CanSetGaugesAsync() { + public virtual async Task CanSetGaugesAsync() + { using var metrics = GetMetricsClient(); - + if (metrics is not IMetricsClientStats stats) return; @@ -34,7 +37,8 @@ public virtual async Task CanSetGaugesAsync() { Assert.Equal(20d, (await stats.GetGaugeStatsAsync("mygauge")).Last); } - public virtual async Task CanIncrementCounterAsync() { + public virtual async Task CanIncrementCounterAsync() + { using var metrics = GetMetricsClient(); if (metrics is not IMetricsClientStats stats) @@ -57,22 +61,25 @@ public virtual async Task CanIncrementCounterAsync() { _logger.LogInformation((await stats.GetCounterStatsAsync("c1")).ToString()); } - private Task AssertCounterAsync(IMetricsClientStats client, string name, long expected) { - return Run.WithRetriesAsync(async () => { + private Task AssertCounterAsync(IMetricsClientStats client, string name, long expected) + { + return Run.WithRetriesAsync(async () => + { long actual = await client.GetCounterCountAsync(name, SystemClock.UtcNow.Subtract(TimeSpan.FromHours(1))); Assert.Equal(expected, actual); }, 8, logger: _logger); } - public virtual async Task CanGetBufferedQueueMetricsAsync() { + public virtual async Task CanGetBufferedQueueMetricsAsync() + { using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - + if (metrics is not IMetricsClientStats stats) return; using var behavior = new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(25), loggerFactory: Log); using var queue = new InMemoryQueue(new InMemoryQueueOptions { Behaviors = new[] { behavior }, LoggerFactory = Log }); - + await queue.EnqueueAsync(new SimpleWorkItem { Id = 1, Data = "1" }); await SystemClock.SleepAsync(50); var entry = await queue.DequeueAsync(TimeSpan.Zero); @@ -91,9 +98,10 @@ public virtual async Task CanGetBufferedQueueMetricsAsync() { Assert.InRange(queueStats.ProcessTime.AverageDuration, 10, 250); } - public virtual async Task CanIncrementBufferedCounterAsync() { + public virtual async Task CanIncrementBufferedCounterAsync() + { using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - + if (metrics is not IMetricsClientStats stats) return; @@ -134,14 +142,16 @@ public virtual async Task CanIncrementBufferedCounterAsync() { } #pragma warning disable 4014 - public virtual async Task CanWaitForCounterAsync() { + public virtual async Task CanWaitForCounterAsync() + { const string CounterName = "Test"; using var metrics = GetMetricsClient() as CacheBucketMetricsClientBase; - + if (metrics is not IMetricsClientStats stats) return; - Task.Run(async () => { + Task.Run(async () => + { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); metrics.Counter(CounterName); }); @@ -151,7 +161,8 @@ public virtual async Task CanWaitForCounterAsync() { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); Assert.True(await task, $"Expected at least 1 count within 500 ms... Took: {sw.Elapsed:g}"); - Task.Run(async () => { + Task.Run(async () => + { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); metrics.Counter(CounterName); }); @@ -161,7 +172,8 @@ public virtual async Task CanWaitForCounterAsync() { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); Assert.True(await task, $"Expected at least 2 count within 500 ms... Took: {sw.Elapsed:g}"); - Task.Run(async () => { + Task.Run(async () => + { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); metrics.Counter(CounterName, 2); }); @@ -171,9 +183,11 @@ public virtual async Task CanWaitForCounterAsync() { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); Assert.True(await task, $"Expected at least 4 count within 500 ms... Took: {sw.Elapsed:g}"); - using (var timeoutCancellationTokenSource = new CancellationTokenSource(500)) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource(500)) + { sw.Restart(); - task = metrics.WaitForCounterAsync(CounterName, () => { + task = metrics.WaitForCounterAsync(CounterName, () => + { metrics.Counter(CounterName); return Task.CompletedTask; }, cancellationToken: timeoutCancellationTokenSource.Token); @@ -186,9 +200,10 @@ public virtual async Task CanWaitForCounterAsync() { } #pragma warning restore 4014 - public virtual async Task CanSendBufferedMetricsAsync() { + public virtual async Task CanSendBufferedMetricsAsync() + { using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - + if (metrics is not IMetricsClientStats stats) return; @@ -200,4 +215,4 @@ public virtual async Task CanSendBufferedMetricsAsync() { Assert.Equal(100, counter.Count); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Queue/QueueTestBase.cs b/src/Foundatio.TestHarness/Queue/QueueTestBase.cs index 6659bc661..98f97d0ce 100644 --- a/src/Foundatio.TestHarness/Queue/QueueTestBase.cs +++ b/src/Foundatio.TestHarness/Queue/QueueTestBase.cs @@ -1,67 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Exceptionless; using Foundatio.AsyncEx; using Foundatio.Caching; using Foundatio.Jobs; using Foundatio.Lock; -using Foundatio.Xunit; using Foundatio.Messaging; using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Tests.Extensions; +using Foundatio.Tests.Metrics; using Foundatio.Utility; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -using Foundatio.Tests.Metrics; #pragma warning disable CS4014 -namespace Foundatio.Tests.Queue { - public abstract class QueueTestBase : TestWithLoggingBase, IDisposable { - protected QueueTestBase(ITestOutputHelper output) : base(output) { +namespace Foundatio.Tests.Queue +{ + public abstract class QueueTestBase : TestWithLoggingBase, IDisposable + { + protected QueueTestBase(ITestOutputHelper output) : base(output) + { Log.SetLogLevel(LogLevel.Debug); Log.SetLogLevel(LogLevel.Debug); Log.SetLogLevel>(LogLevel.Debug); Log.SetLogLevel(LogLevel.Debug); } - protected virtual IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) { + protected virtual IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) + { return null; } - protected virtual async Task CleanupQueueAsync(IQueue queue) { + protected virtual async Task CleanupQueueAsync(IQueue queue) + { if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error cleaning up queue"); - } finally { + } + finally + { queue.Dispose(); } } - + protected bool _assertStats = true; - public virtual async Task CanQueueAndDequeueWorkItemAsync() { + public virtual async Task CanQueueAndDequeueWorkItemAsync() + { var queue = GetQueue(); if (queue == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", SubMetricName = "myitem" }); @@ -78,7 +91,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.True(workItem.IsCompleted); metricsCollector.RecordObservableInstruments(); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); @@ -95,23 +109,28 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.dequeued")); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.completed")); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanQueueAndDequeueWorkItemWithDelayAsync() { + public virtual async Task CanQueueAndDequeueWorkItemWithDelayAsync() + { var queue = GetQueue(); if (queue == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }, new QueueEntryOptions { DeliveryDelay = TimeSpan.FromSeconds(1) }); Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); @@ -128,8 +147,9 @@ await queue.EnqueueAsync(new SimpleWorkItem { await workItem.CompleteAsync(); Assert.False(workItem.IsAbandoned); Assert.True(workItem.IsCompleted); - - if (_assertStats) { + + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); @@ -143,20 +163,25 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.working")); Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.deadletter")); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanUseQueueOptionsAsync() { + public virtual async Task CanUseQueueOptionsAsync() + { var queue = GetQueue(retryDelay: TimeSpan.Zero); if (queue == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { - using var listener = new ActivityListener { + try + { + using var listener = new ActivityListener + { ShouldListenTo = s => s.Name == "Foundatio", Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = activity => _logger.LogInformation("Start: " + activity.DisplayName), @@ -170,9 +195,11 @@ public virtual async Task CanUseQueueOptionsAsync() { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" - }, new QueueEntryOptions { + }, new QueueEntryOptions + { CorrelationId = "123+456", Properties = new Dictionary { { "hey", "now" } @@ -186,7 +213,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal("123+456", workItem.CorrelationId); Assert.Single(workItem.Properties); Assert.Contains(workItem.Properties, i => i.Key == "hey" && i.Value.ToString() == "now"); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Dequeued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); } @@ -196,7 +224,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.False(workItem.IsCompleted); await Task.Delay(100); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Abandoned); Assert.Equal(0, stats.Completed); @@ -215,37 +244,45 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(2, workItem.Attempts); Assert.Single(workItem.Properties); Assert.Contains(workItem.Properties, i => i.Key == "hey" && i.Value.ToString() == "now"); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanDiscardDuplicateQueueEntriesAsync() { + public virtual async Task CanDiscardDuplicateQueueEntriesAsync() + { var queue = GetQueue(); if (queue == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); queue.AttachBehavior(new DuplicateDetectionQueueBehavior(new InMemoryCacheClient(), Log)); - - await queue.EnqueueAsync(new SimpleWorkItem { + + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", UniqueIdentifier = "123" }); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", UniqueIdentifier = "123" }); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } @@ -253,16 +290,19 @@ await queue.EnqueueAsync(new SimpleWorkItem { var workItem = await queue.DequeueAsync(TimeSpan.Zero); Assert.NotNull(workItem); Assert.Equal("Hello", workItem.Value.Data); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Dequeued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); } - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", UniqueIdentifier = "123" }); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(2, (await queue.GetQueueStatsAsync()).Enqueued); Assert.Equal(2, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } @@ -271,7 +311,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.False(workItem.IsAbandoned); Assert.True(workItem.IsCompleted); var stats = await queue.GetQueueStatsAsync(); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, stats.Completed); Assert.Equal(1, stats.Queued); @@ -286,31 +327,37 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.deadletter")); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual Task VerifyRetryAttemptsAsync() { + public virtual Task VerifyRetryAttemptsAsync() + { const int retryCount = 2; - var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.Zero, new []{ 1 }); + var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.Zero, new[] { 1 }); if (queue == null) return Task.CompletedTask; return VerifyRetryAttemptsImplAsync(queue, retryCount, TimeSpan.FromSeconds(10)); } - public virtual Task VerifyDelayedRetryAttemptsAsync() { + public virtual Task VerifyDelayedRetryAttemptsAsync() + { const int retryCount = 2; - var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), new []{ 1 }); + var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), new[] { 1 }); if (queue == null) return Task.CompletedTask; return VerifyRetryAttemptsImplAsync(queue, retryCount, TimeSpan.FromSeconds(30)); } - private async Task VerifyRetryAttemptsImplAsync(IQueue queue, int retryCount, TimeSpan waitTime) { - try { + private async Task VerifyRetryAttemptsImplAsync(IQueue queue, int retryCount, TimeSpan waitTime) + { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -318,28 +365,31 @@ private async Task VerifyRetryAttemptsImplAsync(IQueue queue, in var countdown = new AsyncCountdownEvent(retryCount + 1); int attempts = 0; - await queue.StartWorkingAsync(async w => { + await queue.StartWorkingAsync(async w => + { Interlocked.Increment(ref attempts); _logger.LogInformation("Starting Attempt {Attempt} to work on queue item", attempts); Assert.Equal("Hello", w.Value.Data); - var queueEntryMetadata = (IQueueEntryMetadata) w; + var queueEntryMetadata = (IQueueEntryMetadata)w; Assert.Equal(attempts, queueEntryMetadata.Attempts); await w.AbandonAsync(); countdown.Signal(); - + _logger.LogInformation("Finished Attempt {Attempt} to work on queue item, Metadata Attempts: {MetadataAttempts}", attempts, queueEntryMetadata.Attempts); }); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); await countdown.WaitAsync(waitTime); Assert.Equal(0, countdown.CurrentCount); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(retryCount + 1, attempts); Assert.Equal(0, stats.Completed); @@ -355,7 +405,9 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.count")); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } @@ -364,21 +416,25 @@ await queue.EnqueueAsync(new SimpleWorkItem { /// When a cancelled token is passed into Dequeue, it will only try to dequeue one time and then exit. /// /// - public virtual async Task CanDequeueWithCancelledTokenAsync() { + public virtual async Task CanDequeueWithCancelledTokenAsync() + { var queue = GetQueue(); if (queue == null) return; using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); - if (_assertStats) { + if (_assertStats) + { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } @@ -391,25 +447,30 @@ await queue.EnqueueAsync(new SimpleWorkItem { // TODO: We should verify that only one retry occurred. await workItem.CompleteAsync(); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanDequeueEfficientlyAsync() { + public virtual async Task CanDequeueEfficientlyAsync() + { const int iterations = 100; var queue = GetQueue(runQueueMaintenance: false); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Initialize queue to create more accurate metrics" }); @@ -420,9 +481,11 @@ public virtual async Task CanDequeueEfficientlyAsync() { using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); queue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); - _ = Task.Run(async () => { + _ = Task.Run(async () => + { _logger.LogTrace("Starting enqueue loop"); - for (int index = 0; index < iterations; index++) { + for (int index = 0; index < iterations; index++) + { await SystemClock.SleepAsync(RandomData.GetInt(10, 30)); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); } @@ -430,7 +493,8 @@ public virtual async Task CanDequeueEfficientlyAsync() { }); _logger.LogTrace("Starting dequeue loop"); - for (int index = 0; index < iterations; index++) { + for (int index = 0; index < iterations; index++) + { var item = await queue.DequeueAsync(TimeSpan.FromSeconds(3)); Assert.NotNull(item); await item.CompleteAsync(); @@ -442,24 +506,28 @@ public virtual async Task CanDequeueEfficientlyAsync() { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("AverageDuration: {AverageDuration}", timing.AverageDuration); Assert.InRange(timing.AverageDuration, 0, 75); Assert.InRange(metricsCollector.GetAvg("foundatio.simpleworkitem.queuetime"), 0, 75); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanResumeDequeueEfficientlyAsync() { + public virtual async Task CanResumeDequeueEfficientlyAsync() + { const int iterations = 10; var queue = GetQueue(runQueueMaintenance: false); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - + for (int index = 0; index < iterations; index++) await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); @@ -467,7 +535,8 @@ public virtual async Task CanResumeDequeueEfficientlyAsync() { secondQueue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); _logger.LogTrace("Starting dequeue loop"); - for (int index = 0; index < iterations; index++) { + for (int index = 0; index < iterations; index++) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("[{Index}] Calling Dequeue", index); var item = await secondQueue.DequeueAsync(TimeSpan.FromSeconds(3)); Assert.NotNull(item); @@ -478,25 +547,31 @@ public virtual async Task CanResumeDequeueEfficientlyAsync() { var timing = await metrics.GetTimerStatsAsync("simpleworkitem.queuetime"); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("TotalDuration: {TotalDuration} AverageDuration: {AverageDuration}", timing.TotalDuration, timing.AverageDuration); Assert.InRange(timing.AverageDuration, 0, 75); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanQueueAndDequeueMultipleWorkItemsAsync() { + public virtual async Task CanQueueAndDequeueMultipleWorkItemsAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); const int workItemCount = 25; - for (int i = 0; i < workItemCount; i++) { - await queue.EnqueueAsync(new SimpleWorkItem { + for (int i = 0; i < workItemCount; i++) + { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); } @@ -505,7 +580,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.Equal(workItemCount, (await queue.GetQueueStatsAsync()).Queued); var sw = Stopwatch.StartNew(); - for (int i = 0; i < workItemCount; i++) { + for (int i = 0; i < workItemCount; i++) + { var workItem = await queue.DequeueAsync(TimeSpan.FromSeconds(5)); Assert.NotNull(workItem); Assert.Equal("Hello", workItem.Value.Data); @@ -515,23 +591,28 @@ await queue.EnqueueAsync(new SimpleWorkItem { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Time {Elapsed:g}", sw.Elapsed); Assert.InRange(sw.Elapsed.TotalSeconds, 0, 5); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(workItemCount, stats.Dequeued); Assert.Equal(workItemCount, stats.Completed); Assert.Equal(0, stats.Queued); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task WillNotWaitForItemAsync() { + public virtual async Task WillNotWaitForItemAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -542,18 +623,21 @@ public virtual async Task WillNotWaitForItemAsync() { Assert.Null(workItem); Assert.InRange(sw.Elapsed.TotalMilliseconds, 0, 100); } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task WillWaitForItemAsync() { + public virtual async Task WillWaitForItemAsync() + { Log.MinimumLevel = LogLevel.Trace; var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -564,9 +648,11 @@ public virtual async Task WillWaitForItemAsync() { Assert.Null(workItem); Assert.InRange(sw.Elapsed, TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(5000)); - _ = Task.Run(async () => { + _ = Task.Run(async () => + { await SystemClock.SleepAsync(500); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); }); @@ -580,23 +666,28 @@ await queue.EnqueueAsync(new SimpleWorkItem { await workItem.CompleteAsync(); } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task DequeueWaitWillGetSignaledAsync() { + public virtual async Task DequeueWaitWillGetSignaledAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - _ = Task.Run(async () => { + _ = Task.Run(async () => + { await SystemClock.SleepAsync(250); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); }); @@ -608,70 +699,82 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.NotNull(workItem); Assert.InRange(sw.Elapsed.TotalSeconds, 0, 2); } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanUseQueueWorkerAsync() { + public virtual async Task CanUseQueueWorkerAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); var resetEvent = new AsyncManualResetEvent(false); - await queue.StartWorkingAsync(async w => { + await queue.StartWorkingAsync(async w => + { Assert.Equal("Hello", w.Value.Data); await w.CompleteAsync(); resetEvent.Set(); }); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); await resetEvent.WaitAsync(); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); Assert.Equal(0, stats.Errors); } } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanHandleErrorInWorkerAsync() { + public virtual async Task CanHandleErrorInWorkerAsync() + { var queue = GetQueue(retries: 0); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { Buffered = false, LoggerFactory = Log }); - + queue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); - await queue.StartWorkingAsync(w => { + await queue.StartWorkingAsync(w => + { _logger.LogDebug("WorkAction"); Assert.Equal("Hello", w.Value.Data); throw new Exception(); }); var resetEvent = new AsyncManualResetEvent(false); - using (queue.Abandoned.AddSyncHandler((o, args) => resetEvent.Set())) { + using (queue.Abandoned.AddSyncHandler((o, args) => resetEvent.Set())) + { await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); await resetEvent.WaitAsync(TimeSpan.FromSeconds(200)); await SystemClock.SleepAsync(100); // give time for the stats to reflect the changes. - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Completed: {Completed} Errors: {Errors} Deadletter: {Deadletter} Working: {Working} ", stats.Completed, stats.Errors, stats.Deadletter, stats.Working); @@ -680,33 +783,40 @@ await queue.StartWorkingAsync(w => { Assert.Equal(1, stats.Deadletter); } } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task WorkItemsWillTimeoutAsync() { + public virtual async Task WorkItemsWillTimeoutAsync() + { Log.MinimumLevel = LogLevel.Trace; Log.SetLogLevel("Foundatio.Queues.RedisQueue", LogLevel.Trace); var queue = GetQueue(retryDelay: TimeSpan.Zero, workItemTimeout: TimeSpan.FromMilliseconds(50)); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); var workItem = await queue.DequeueAsync(TimeSpan.Zero); Assert.NotNull(workItem); Assert.Equal("Hello", workItem.Value.Data); - + var sw = Stopwatch.StartNew(); - if (_assertStats) { + if (_assertStats) + { // wait for the entry to be auto abandoned - do { + do + { var stats = await queue.GetQueueStatsAsync(); if (stats.Abandoned > 0) break; @@ -726,21 +836,26 @@ await queue.EnqueueAsync(new SimpleWorkItem { await workItem.CompleteAsync(); if (_assertStats) Assert.Equal(0, (await queue.GetQueueStatsAsync()).Queued); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task WorkItemsWillGetMovedToDeadletterAsync() { + public virtual async Task WorkItemsWillGetMovedToDeadletterAsync() + { var queue = GetQueue(retryDelay: TimeSpan.Zero); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); var workItem = await queue.DequeueAsync(TimeSpan.Zero); @@ -758,56 +873,68 @@ await queue.EnqueueAsync(new SimpleWorkItem { await workItem.AbandonAsync(); - if (_assertStats) { + if (_assertStats) + { // work item should be moved to deadletter _queue after retries. var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Deadletter); Assert.Equal(2, stats.Abandoned); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanAutoCompleteWorkerAsync() { + public virtual async Task CanAutoCompleteWorkerAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); var resetEvent = new AsyncManualResetEvent(false); - await queue.StartWorkingAsync(w => { + await queue.StartWorkingAsync(w => + { Assert.Equal("Hello", w.Value.Data); return Task.CompletedTask; }, true); - using (queue.Completed.AddSyncHandler((s, e) => { resetEvent.Set(); })) { + using (queue.Completed.AddSyncHandler((s, e) => { resetEvent.Set(); })) + { await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); await resetEvent.WaitAsync(TimeSpan.FromSeconds(2)); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(0, stats.Queued); Assert.Equal(0, stats.Errors); Assert.Equal(1, stats.Completed); } } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanHaveMultipleQueueInstancesAsync() { + public virtual async Task CanHaveMultipleQueueInstancesAsync() + { var queue = GetQueue(retries: 0, retryDelay: TimeSpan.Zero); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -817,16 +944,20 @@ public virtual async Task CanHaveMultipleQueueInstancesAsync() { var info = new WorkInfo(); var workers = new List> { queue }; - try { - for (int i = 0; i < workerCount; i++) { + try + { + for (int i = 0; i < workerCount; i++) + { var q = GetQueue(retries: 0, retryDelay: TimeSpan.Zero); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Queue Id: {Id}, I: {Instance}", q.QueueId, i); await q.StartWorkingAsync(w => DoWorkAsync(w, countdown, info)); workers.Add(q); } - await Run.InParallelAsync(workItemCount, async i => { - string id = await queue.EnqueueAsync(new SimpleWorkItem { + await Run.InParallelAsync(workItemCount, async i => + { + string id = await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", Id = i }); @@ -835,13 +966,14 @@ await Run.InParallelAsync(workItemCount, async i => { await countdown.WaitAsync(); await SystemClock.SleepAsync(50); - + if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Work Info Stats: Completed: {Completed} Abandoned: {Abandoned} Error: {Errors}", info.CompletedCount, info.AbandonCount, info.ErrorCount); Assert.Equal(workItemCount, info.CompletedCount + info.AbandonCount + info.ErrorCount); // In memory queue doesn't share state. - if (queue.GetType() == typeof(InMemoryQueue)) { + if (queue.GetType() == typeof(InMemoryQueue)) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(0, stats.Working); Assert.Equal(0, stats.Timeouts); @@ -851,9 +983,12 @@ await Run.InParallelAsync(workItemCount, async i => { Assert.Equal(info.ErrorCount, stats.Errors); Assert.Equal(info.AbandonCount, stats.Abandoned - info.ErrorCount); Assert.Equal(info.AbandonCount + stats.Errors, stats.Deadletter); - } else if (_assertStats) { + } + else if (_assertStats) + { var workerStats = new List(); - for (int i = 0; i < workers.Count; i++) { + for (int i = 0; i < workers.Count; i++) + { var stats = await workers[i].GetQueueStatsAsync(); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Worker#{Id} Working: {Working} Completed: {Completed} Abandoned: {Abandoned} Error: {Errors} Deadletter: {Deadletter}", i, stats.Working, stats.Completed, stats.Abandoned, stats.Errors, stats.Deadletter); @@ -867,26 +1002,32 @@ await Run.InParallelAsync(workItemCount, async i => { //Expected: 260 //Actual: 125 } - } finally { + } + finally + { foreach (var q in workers) await CleanupQueueAsync(q); } } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanDelayRetryAsync() { + public virtual async Task CanDelayRetryAsync() + { var queue = GetQueue(workItemTimeout: TimeSpan.FromMilliseconds(500), retryDelay: TimeSpan.FromSeconds(1)); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); @@ -904,29 +1045,34 @@ await queue.EnqueueAsync(new SimpleWorkItem { Assert.NotNull(workItem); Assert.True(elapsed > TimeSpan.FromSeconds(.95)); await workItem.CompleteAsync(); - + if (_assertStats) Assert.Equal(0, (await queue.GetQueueStatsAsync()).Queued); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanRunWorkItemWithMetricsAsync() { + public virtual async Task CanRunWorkItemWithMetricsAsync() + { int completedCount = 0; using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { Buffered = false, LoggerFactory = Log }); - + var behavior = new MetricsQueueBehavior(metrics, "metric", TimeSpan.FromMilliseconds(100), loggerFactory: Log); var options = new InMemoryQueueOptions { Behaviors = new[] { behavior }, LoggerFactory = Log }; using var queue = new InMemoryQueue(options); - - Task Handler(object sender, CompletedEventArgs e) { + + Task Handler(object sender, CompletedEventArgs e) + { completedCount++; return Task.CompletedTask; } - using (queue.Completed.AddHandler(Handler)) { + using (queue.Completed.AddHandler(Handler)) + { _logger.LogTrace("Before enqueue"); await queue.EnqueueAsync(new SimpleWorkItem { Id = 1, Data = "Testing" }); await queue.EnqueueAsync(new SimpleWorkItem { Id = 2, Data = "Testing" }); @@ -974,7 +1120,8 @@ Task Handler(object sender, CompletedEventArgs e) { processTiming = await metrics.GetTimerStatsAsync("metric.workitemdata.processtime"); Assert.Equal(3, processTiming.Count); - if (_assertStats) { + if (_assertStats) + { var queueStats = await metrics.GetQueueStatsAsync("metric.workitemdata"); Assert.Equal(3, queueStats.Enqueued.Count); Assert.Equal(3, queueStats.Dequeued.Count); @@ -992,7 +1139,8 @@ Task Handler(object sender, CompletedEventArgs e) { } } - public virtual async Task CanRenewLockAsync() { + public virtual async Task CanRenewLockAsync() + { Log.SetLogLevel>(LogLevel.Trace); // Need large value to reproduce this test @@ -1004,11 +1152,13 @@ public virtual async Task CanRenewLockAsync() { if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - await queue.EnqueueAsync(new SimpleWorkItem { + await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello" }); var entry = await queue.DequeueAsync(TimeSpan.Zero); @@ -1027,20 +1177,24 @@ await queue.EnqueueAsync(new SimpleWorkItem { var nullWorkItem = await queue.DequeueAsync(TimeSpan.Zero); Assert.Null(nullWorkItem); await entry.CompleteAsync(); - + if (_assertStats) Assert.Equal(0, (await queue.GetQueueStatsAsync()).Queued); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanAbandonQueueEntryOnceAsync() { + public virtual async Task CanAbandonQueueEntryOnceAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -1059,7 +1213,8 @@ public virtual async Task CanAbandonQueueEntryOnceAsync() { await Assert.ThrowsAnyAsync(() => workItem.CompleteAsync()); await Assert.ThrowsAnyAsync(() => workItem.CompleteAsync()); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Abandoned); Assert.Equal(0, stats.Completed); @@ -1086,17 +1241,20 @@ public virtual async Task CanAbandonQueueEntryOnceAsync() { await Assert.ThrowsAnyAsync(() => queue.AbandonAsync(workItem)); await Assert.ThrowsAnyAsync(() => queue.CompleteAsync(workItem)); } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanCompleteQueueEntryOnceAsync() { + public virtual async Task CanCompleteQueueEntryOnceAsync() + { var queue = GetQueue(); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); @@ -1112,7 +1270,8 @@ public virtual async Task CanCompleteQueueEntryOnceAsync() { await Assert.ThrowsAnyAsync(() => workItem.AbandonAsync()); await Assert.ThrowsAnyAsync(() => workItem.AbandonAsync()); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(0, stats.Abandoned); Assert.Equal(1, stats.Completed); @@ -1128,35 +1287,40 @@ public virtual async Task CanCompleteQueueEntryOnceAsync() { if (workItem is QueueEntry queueEntry) Assert.Equal(1, queueEntry.Attempts); } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanDequeueWithLockingAsync() { + public virtual async Task CanDequeueWithLockingAsync() + { using var cache = new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = Log }); using var messageBus = new InMemoryMessageBus(new InMemoryMessageBusOptions { LoggerFactory = Log }); - + var distributedLock = new CacheLockProvider(cache, messageBus, Log); await CanDequeueWithLockingImpAsync(distributedLock); } - protected async Task CanDequeueWithLockingImpAsync(CacheLockProvider distributedLock) { + protected async Task CanDequeueWithLockingImpAsync(CacheLockProvider distributedLock) + { var queue = GetQueue(retryDelay: TimeSpan.Zero, retries: 0); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); Log.MinimumLevel = LogLevel.Trace; using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { Buffered = false, LoggerFactory = Log }); - + queue.AttachBehavior(new MetricsQueueBehavior(metrics, loggerFactory: Log)); var resetEvent = new AsyncAutoResetEvent(); - await queue.StartWorkingAsync(async w => { + await queue.StartWorkingAsync(async w => + { _logger.LogInformation("Acquiring distributed lock in work item"); var l = await distributedLock.AcquireAsync("test"); Assert.NotNull(l); @@ -1172,31 +1336,37 @@ await queue.StartWorkingAsync(async w => { await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); await resetEvent.WaitAsync(TimeSpan.FromSeconds(5)); - if (_assertStats) { + if (_assertStats) + { await SystemClock.SleepAsync(1); var stats = await queue.GetQueueStatsAsync(); _logger.LogInformation("Completed: {Completed} Errors: {Errors} Deadletter: {Deadletter} Working: {Working} ", stats.Completed, stats.Errors, stats.Deadletter, stats.Working); Assert.Equal(1, stats.Completed); } - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanHaveMultipleQueueInstancesWithLockingAsync() { + public virtual async Task CanHaveMultipleQueueInstancesWithLockingAsync() + { using var cache = new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = Log }); using var messageBus = new InMemoryMessageBus(new InMemoryMessageBusOptions { LoggerFactory = Log }); - + var distributedLock = new CacheLockProvider(cache, messageBus, Log); await CanHaveMultipleQueueInstancesWithLockingImplAsync(distributedLock); } - protected async Task CanHaveMultipleQueueInstancesWithLockingImplAsync(CacheLockProvider distributedLock) { + protected async Task CanHaveMultipleQueueInstancesWithLockingImplAsync(CacheLockProvider distributedLock) + { var queue = GetQueue(retries: 0, retryDelay: TimeSpan.Zero); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); @@ -1206,11 +1376,14 @@ protected async Task CanHaveMultipleQueueInstancesWithLockingImplAsync(CacheLock var info = new WorkInfo(); var workers = new List> { queue }; - try { - for (int i = 0; i < workerCount; i++) { + try + { + for (int i = 0; i < workerCount; i++) + { var q = GetQueue(retries: 0, retryDelay: TimeSpan.Zero); int instanceCount = i; - await q.StartWorkingAsync(async w => { + await q.StartWorkingAsync(async w => + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("[{Instance}] Acquiring distributed lock in work item: {Id}", instanceCount, w.Id); var l = await distributedLock.AcquireAsync("test"); @@ -1231,8 +1404,10 @@ await q.StartWorkingAsync(async w => { workers.Add(q); } - await Run.InParallelAsync(workItemCount, async i => { - string id = await queue.EnqueueAsync(new SimpleWorkItem { + await Run.InParallelAsync(workItemCount, async i => + { + string id = await queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello", Id = i }); @@ -1248,13 +1423,16 @@ await Run.InParallelAsync(workItemCount, async i => { Assert.Equal(workItemCount, info.CompletedCount + info.AbandonCount + info.ErrorCount); // In memory queue doesn't share state. - if (queue.GetType() == typeof(InMemoryQueue)) { + if (queue.GetType() == typeof(InMemoryQueue)) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(info.CompletedCount, stats.Completed); } - else { + else + { var workerStats = new List(); - for (int i = 0; i < workers.Count; i++) { + for (int i = 0; i < workers.Count; i++) + { var stats = await workers[i].GetQueueStatsAsync(); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Worker#{Id} Working: {Working} Completed: {Completed} Abandoned: {Abandoned} Error: {Errors} Deadletter: {Deadletter}", i, stats.Working, stats.Completed, stats.Abandoned, stats.Errors, stats.Deadletter); @@ -1264,46 +1442,56 @@ await Run.InParallelAsync(workItemCount, async i => { Assert.Equal(info.CompletedCount, workerStats.Sum(s => s.Completed)); } } - finally { + finally + { foreach (var q in workers) await CleanupQueueAsync(q); } } - finally { + finally + { await CleanupQueueAsync(queue); } } - protected async Task DoWorkAsync(IQueueEntry w, AsyncCountdownEvent countdown, WorkInfo info) { + protected async Task DoWorkAsync(IQueueEntry w, AsyncCountdownEvent countdown, WorkInfo info) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Starting: {Id}", w.Value.Id); Assert.Equal("Hello", w.Value.Data); - try { + try + { // randomly complete, abandon or blowup. - if (RandomData.GetBool()) { + if (RandomData.GetBool()) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Completing: {Id}", w.Value.Id); await w.CompleteAsync(); info.IncrementCompletedCount(); } - else if (RandomData.GetBool()) { + else if (RandomData.GetBool()) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Abandoning: {Id}", w.Value.Id); await w.AbandonAsync(); info.IncrementAbandonCount(); } - else { + else + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Erroring: {Id}", w.Value.Id); info.IncrementErrorCount(); throw new Exception(); } } - finally { + finally + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Signal {CurrentCount}", countdown.CurrentCount); countdown.Signal(); } } - protected async Task AssertEmptyQueueAsync(IQueue queue) { - if (_assertStats) { + protected async Task AssertEmptyQueueAsync(IQueue queue) + { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(0, stats.Abandoned); Assert.Equal(0, stats.Completed); @@ -1317,18 +1505,22 @@ protected async Task AssertEmptyQueueAsync(IQueue queue) { } } - public virtual async Task MaintainJobNotAbandon_NotWorkTimeOutEntry() { + public virtual async Task MaintainJobNotAbandon_NotWorkTimeOutEntry() + { var queue = GetQueue(retries: 0, workItemTimeout: TimeSpan.FromMilliseconds(100), retryDelay: TimeSpan.Zero); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - queue.EnqueueAsync(new SimpleWorkItem { + queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello World", Id = 1 }); - queue.EnqueueAsync(new SimpleWorkItem { + queue.EnqueueAsync(new SimpleWorkItem + { Data = "Hello World", Id = 2 }); @@ -1349,36 +1541,43 @@ public virtual async Task MaintainJobNotAbandon_NotWorkTimeOutEntry() { Assert.True(dequeuedQueueItem.IsCompleted); Assert.False(dequeuedQueueItem.IsAbandoned); - if (_assertStats) { + if (_assertStats) + { var stats = await queue.GetQueueStatsAsync(); Assert.Equal(0, stats.Working); Assert.Equal(0, stats.Abandoned); Assert.Equal(2, stats.Completed); } } - finally { + finally + { await CleanupQueueAsync(queue); } } - public virtual async Task CanHandleAutoAbandonInWorker() { + public virtual async Task CanHandleAutoAbandonInWorker() + { // create queue with short work item timeout so it will be auto abandoned var queue = GetQueue(workItemTimeout: TimeSpan.FromMilliseconds(100)); if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); var successEvent = new AsyncAutoResetEvent(); var errorEvent = new AsyncAutoResetEvent(); - await queue.StartWorkingAsync(async (item) => { - if (item.Value.Data == "Delay") { + await queue.StartWorkingAsync(async (item) => + { + if (item.Value.Data == "Delay") + { // wait for queue item to get auto abandoned var stats = await queue.GetQueueStatsAsync(); var sw = Stopwatch.StartNew(); - do { + do + { if (stats.Abandoned > 0) break; @@ -1388,9 +1587,12 @@ await queue.StartWorkingAsync(async (item) => { Assert.Equal(1, stats.Abandoned); } - try { + try + { await item.CompleteAsync(); - } catch { + } + catch + { errorEvent.Set(); throw; } @@ -1400,15 +1602,18 @@ await queue.StartWorkingAsync(async (item) => { await queue.EnqueueAsync(new SimpleWorkItem() { Data = "Delay" }); await queue.EnqueueAsync(new SimpleWorkItem() { Data = "No Delay" }); - + await errorEvent.WaitAsync(TimeSpan.FromSeconds(10)); await successEvent.WaitAsync(TimeSpan.FromSeconds(10)); - } finally { + } + finally + { await CleanupQueueAsync(queue); } } - public virtual void Dispose() { + public virtual void Dispose() + { var queue = GetQueue(); if (queue == null) return; @@ -1420,7 +1625,8 @@ public virtual void Dispose() { } } - public class WorkInfo { + public class WorkInfo + { private int _abandonCount; private int _errorCount; private int _completedCount; @@ -1429,16 +1635,19 @@ public class WorkInfo { public int ErrorCount => _errorCount; public int CompletedCount => _completedCount; - public void IncrementAbandonCount() { + public void IncrementAbandonCount() + { Interlocked.Increment(ref _abandonCount); } - public void IncrementErrorCount() { + public void IncrementErrorCount() + { Interlocked.Increment(ref _errorCount); } - public void IncrementCompletedCount() { + public void IncrementCompletedCount() + { Interlocked.Increment(ref _completedCount); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Queue/Samples.cs b/src/Foundatio.TestHarness/Queue/Samples.cs index c0e7914c1..81a0757d1 100644 --- a/src/Foundatio.TestHarness/Queue/Samples.cs +++ b/src/Foundatio.TestHarness/Queue/Samples.cs @@ -1,8 +1,10 @@ using Foundatio.Metrics; using Foundatio.Queues; -namespace Foundatio.Tests.Queue { - public class SimpleWorkItem : IHaveSubMetricName, IHaveUniqueIdentifier { +namespace Foundatio.Tests.Queue +{ + public class SimpleWorkItem : IHaveSubMetricName, IHaveUniqueIdentifier + { public string Data { get; set; } public int Id { get; set; } public string UniqueIdentifier { get; set; } diff --git a/src/Foundatio.TestHarness/Serializer/SerializerTestsBase.cs b/src/Foundatio.TestHarness/Serializer/SerializerTestsBase.cs index 3f98aa427..707cfe685 100644 --- a/src/Foundatio.TestHarness/Serializer/SerializerTestsBase.cs +++ b/src/Foundatio.TestHarness/Serializer/SerializerTestsBase.cs @@ -1,26 +1,31 @@ using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; -using Foundatio.Xunit; using Foundatio.Serializer; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public abstract class SerializerTestsBase : TestWithLoggingBase { - protected SerializerTestsBase(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Serializer +{ + public abstract class SerializerTestsBase : TestWithLoggingBase + { + protected SerializerTestsBase(ITestOutputHelper output) : base(output) { } - protected virtual ISerializer GetSerializer() { + protected virtual ISerializer GetSerializer() + { return null; } - public virtual void CanRoundTripBytes() { + public virtual void CanRoundTripBytes() + { var serializer = GetSerializer(); if (serializer == null) return; - - var model = new SerializeModel { + + var model = new SerializeModel + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, @@ -42,12 +47,14 @@ public virtual void CanRoundTripBytes() { Assert.Equal(1, ((dynamic)model.ObjectProperty).IntProperty); } - public virtual void CanRoundTripString() { + public virtual void CanRoundTripString() + { var serializer = GetSerializer(); if (serializer == null) return; - - var model = new SerializeModel { + + var model = new SerializeModel + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, @@ -64,7 +71,8 @@ public virtual void CanRoundTripString() { Assert.Equal(1, ((dynamic)model.ObjectProperty).IntProperty); } - public virtual void CanHandlePrimitiveTypes() { + public virtual void CanHandlePrimitiveTypes() + { var serializer = GetSerializer(); if (serializer == null) return; @@ -79,9 +87,11 @@ public virtual void CanHandlePrimitiveTypes() { [MemoryDiagnoser] [ShortRunJob] - public abstract class SerializerBenchmarkBase { + public abstract class SerializerBenchmarkBase + { private ISerializer _serializer; - private readonly SerializeModel _data = new() { + private readonly SerializeModel _data = new() + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, @@ -93,32 +103,37 @@ public abstract class SerializerBenchmarkBase { protected abstract ISerializer GetSerializer(); [GlobalSetup] - public void Setup() { + public void Setup() + { _serializer = GetSerializer(); _serializedData = _serializer.SerializeToBytes(_data); } [Benchmark] - public byte[] Serialize() { + public byte[] Serialize() + { return _serializer.SerializeToBytes(_data); } - + [Benchmark] - public SerializeModel Deserialize() { + public SerializeModel Deserialize() + { return _serializer.Deserialize(_serializedData); } [Benchmark] - public SerializeModel RoundTrip() { + public SerializeModel RoundTrip() + { byte[] serializedData = _serializer.SerializeToBytes(_data); return _serializer.Deserialize(serializedData); } } - public class SerializeModel { + public class SerializeModel + { public int IntProperty { get; set; } public string StringProperty { get; set; } public List ListProperty { get; set; } public object ObjectProperty { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs index 43a0cfab2..d13813561 100644 --- a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs +++ b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs @@ -14,34 +14,41 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Storage { - public abstract class FileStorageTestsBase : TestWithLoggingBase { - protected FileStorageTestsBase(ITestOutputHelper output) : base(output) {} - - protected virtual IFileStorage GetStorage() { +namespace Foundatio.Tests.Storage +{ + public abstract class FileStorageTestsBase : TestWithLoggingBase + { + protected FileStorageTestsBase(ITestOutputHelper output) : base(output) { } + + protected virtual IFileStorage GetStorage() + { return null; } - public virtual async Task CanGetEmptyFileListOnMissingDirectoryAsync() { + public virtual async Task CanGetEmptyFileListOnMissingDirectoryAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { Assert.Empty(await storage.GetFileListAsync(Guid.NewGuid() + "\\*")); } } - public virtual async Task CanGetFileListForSingleFolderAsync() { + public virtual async Task CanGetFileListForSingleFolderAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"archived\archived.txt", "archived"); await storage.SaveFileAsync(@"q\new.txt", "new"); await storage.SaveFileAsync(@"long/path/in/here/1.hey.stuff-2.json", "archived"); @@ -58,14 +65,16 @@ public virtual async Task CanGetFileListForSingleFolderAsync() { } } - public virtual async Task CanGetFileListForSingleFileAsync() { + public virtual async Task CanGetFileListForSingleFileAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"archived\archived.txt", "archived"); await storage.SaveFileAsync(@"archived\archived.csv", "archived"); await storage.SaveFileAsync(@"q\new.txt", "new"); @@ -75,14 +84,16 @@ public virtual async Task CanGetFileListForSingleFileAsync() { } } - public virtual async Task CanGetPagedFileListForSingleFolderAsync() { + public virtual async Task CanGetPagedFileListForSingleFolderAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { var result = await storage.GetPagedFileListAsync(1); Assert.False(result.HasMore); Assert.Empty(result.Files); @@ -112,22 +123,24 @@ public virtual async Task CanGetPagedFileListForSingleFolderAsync() { Assert.Single((await storage.GetPagedFileListAsync(1)).Files); Assert.Single((await storage.GetPagedFileListAsync(2, @"long\path\in\here\*stuff*.json")).Files); - Assert.Single((await storage.GetPagedFileListAsync(2,@"archived\*")).Files); + Assert.Single((await storage.GetPagedFileListAsync(2, @"archived\*")).Files); Assert.Equal("archived", await storage.GetFileContentsAsync(@"archived\archived.txt")); - Assert.Single((await storage.GetPagedFileListAsync(2,@"q\*")).Files); + Assert.Single((await storage.GetPagedFileListAsync(2, @"q\*")).Files); Assert.Equal("new", await storage.GetFileContentsAsync(@"q\new.txt")); } } - public virtual async Task CanGetFileInfoAsync() { + public virtual async Task CanGetFileInfoAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { var fileInfo = await storage.GetFileInfoAsync(Guid.NewGuid().ToString()); Assert.Null(fileInfo); @@ -157,27 +170,31 @@ public virtual async Task CanGetFileInfoAsync() { } } - public virtual async Task CanGetNonExistentFileInfoAsync() { + public virtual async Task CanGetNonExistentFileInfoAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await Assert.ThrowsAnyAsync(() => storage.GetFileInfoAsync(null)); Assert.Null(await storage.GetFileInfoAsync(Guid.NewGuid().ToString())); } } - public virtual async Task CanManageFilesAsync() { + public virtual async Task CanManageFilesAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync("test.txt", "test"); var file = (await storage.GetFileListAsync()).Single(); Assert.NotNull(file); @@ -191,14 +208,16 @@ public virtual async Task CanManageFilesAsync() { } } - public virtual async Task CanRenameFilesAsync() { + public virtual async Task CanRenameFilesAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { Assert.True(await storage.SaveFileAsync("test.txt", "test")); Assert.True(await storage.RenameFileAsync("test.txt", @"archive\new.txt")); Assert.Equal("test", await storage.GetFileContentsAsync(@"archive\new.txt")); @@ -211,10 +230,12 @@ public virtual async Task CanRenameFilesAsync() { } } - protected virtual string GetTestFilePath() { + protected virtual string GetTestFilePath() + { var currentDirectory = new DirectoryInfo(PathHelper.ExpandPath(@"|DataDirectory|\")); var currentFilePath = Path.Combine(currentDirectory.FullName, "README.md"); - while (!File.Exists(currentFilePath) && currentDirectory.Parent != null) { + while (!File.Exists(currentFilePath) && currentDirectory.Parent != null) + { currentDirectory = currentDirectory.Parent; currentFilePath = Path.Combine(currentDirectory.FullName, "README.md"); } @@ -225,7 +246,8 @@ protected virtual string GetTestFilePath() { throw new ApplicationException("Unable to find test README.md file in path hierarchy."); } - public virtual async Task CanSaveFilesAsync() { + public virtual async Task CanSaveFilesAsync() + { var storage = GetStorage(); if (storage == null) return; @@ -233,10 +255,12 @@ public virtual async Task CanSaveFilesAsync() { await ResetAsync(storage); string readmeFile = GetTestFilePath(); - using (storage) { + using (storage) + { Assert.False(await storage.ExistsAsync("Foundatio.Tests.csproj")); - await using (var stream = new NonSeekableStream(File.Open(readmeFile, FileMode.Open, FileAccess.Read))) { + await using (var stream = new NonSeekableStream(File.Open(readmeFile, FileMode.Open, FileAccess.Read))) + { bool result = await storage.SaveFileAsync("Foundatio.Tests.csproj", stream); Assert.True(result); } @@ -244,21 +268,24 @@ public virtual async Task CanSaveFilesAsync() { Assert.Single(await storage.GetFileListAsync()); Assert.True(await storage.ExistsAsync("Foundatio.Tests.csproj")); - await using (var stream = await storage.GetFileStreamAsync("Foundatio.Tests.csproj")) { + await using (var stream = await storage.GetFileStreamAsync("Foundatio.Tests.csproj")) + { string result = await new StreamReader(stream).ReadToEndAsync(); Assert.Equal(File.ReadAllText(readmeFile), result); } } } - public virtual async Task CanDeleteEntireFolderAsync() { + public virtual async Task CanDeleteEntireFolderAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"x\hello.txt", "hello"); await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); Assert.Equal(2, (await storage.GetFileListAsync()).Count); @@ -268,14 +295,16 @@ public virtual async Task CanDeleteEntireFolderAsync() { } } - public virtual async Task CanDeleteEntireFolderWithWildcardAsync() { + public virtual async Task CanDeleteEntireFolderWithWildcardAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"x\hello.txt", "hello"); await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); Assert.Equal(2, (await storage.GetFileListAsync()).Count); @@ -288,18 +317,22 @@ public virtual async Task CanDeleteEntireFolderWithWildcardAsync() { Assert.Empty(await storage.GetFileListAsync()); } } - - public virtual async Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + + public virtual async Task CanDeleteFolderWithMultiFolderWildcardsAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { const int filesPerMonth = 5; - for (int year = 2020; year <= 2021; year++) { - for (int month = 1; month <= 12; month++) { + for (int year = 2020; year <= 2021; year++) + { + for (int month = 1; month <= 12; month++) + { for (int index = 0; index < filesPerMonth; index++) await storage.SaveFileAsync($"archive\\year-{year}\\month-{month:00}\\file-{index:00}.txt", "hello"); } @@ -319,14 +352,16 @@ public virtual async Task CanDeleteFolderWithMultiFolderWildcardsAsync() { } } - public virtual async Task CanDeleteSpecificFilesAsync() { + public virtual async Task CanDeleteSpecificFilesAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"x\hello.txt", "hello"); await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); await storage.SaveFileAsync(@"x\nested\hello.txt", "nested hello"); @@ -345,14 +380,16 @@ public virtual async Task CanDeleteSpecificFilesAsync() { } } - public virtual async Task CanDeleteNestedFolderAsync() { + public virtual async Task CanDeleteNestedFolderAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"x\hello.txt", "hello"); await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); await storage.SaveFileAsync(@"x\nested\hello.txt", "nested hello"); @@ -371,14 +408,16 @@ public virtual async Task CanDeleteNestedFolderAsync() { } } - public virtual async Task CanDeleteSpecificFilesInNestedFolderAsync() { + public virtual async Task CanDeleteSpecificFilesInNestedFolderAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { await storage.SaveFileAsync(@"x\hello.txt", "hello"); await storage.SaveFileAsync(@"x\world.csv", "world"); await storage.SaveFileAsync(@"x\nested\world.csv", "nested world"); @@ -401,18 +440,21 @@ public virtual async Task CanDeleteSpecificFilesInNestedFolderAsync() { } } - public virtual async Task CanRoundTripSeekableStreamAsync() { + public virtual async Task CanRoundTripSeekableStreamAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { const string path = "user.xml"; var element = XElement.Parse("Blake"); - using (var memoryStream = new MemoryStream()) { + using (var memoryStream = new MemoryStream()) + { _logger.LogTrace("Saving xml to stream with position {Position}.", memoryStream.Position); element.Save(memoryStream, SaveOptions.DisableFormatting); @@ -428,18 +470,22 @@ public virtual async Task CanRoundTripSeekableStreamAsync() { } } - public virtual async Task WillRespectStreamOffsetAsync() { + public virtual async Task WillRespectStreamOffsetAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { string path = "blake.txt"; - using (var memoryStream = new MemoryStream()) { + using (var memoryStream = new MemoryStream()) + { long offset; - await using (var writer = new StreamWriter(memoryStream, Encoding.UTF8, 1024, true)) { + await using (var writer = new StreamWriter(memoryStream, Encoding.UTF8, 1024, true)) + { writer.AutoFlush = true; await writer.WriteAsync("Eric"); offset = memoryStream.Position; @@ -455,7 +501,8 @@ public virtual async Task WillRespectStreamOffsetAsync() { } } - public virtual async Task WillWriteStreamContentAsync() { + public virtual async Task WillWriteStreamContentAsync() + { const string testContent = "test"; const string path = "created.txt"; @@ -466,9 +513,11 @@ public virtual async Task WillWriteStreamContentAsync() { await ResetAsync(storage); - using (storage) { + using (storage) + { - using (var writer = new StreamWriter(await storage.GetFileStreamAsync(path, StreamMode.Write), Encoding.UTF8, 1024, false)) { + using (var writer = new StreamWriter(await storage.GetFileStreamAsync(path, StreamMode.Write), Encoding.UTF8, 1024, false)) + { await writer.WriteAsync(testContent); } @@ -478,7 +527,8 @@ public virtual async Task WillWriteStreamContentAsync() { } } - protected virtual async Task ResetAsync(IFileStorage storage) { + protected virtual async Task ResetAsync(IFileStorage storage) + { if (storage == null) return; @@ -488,22 +538,26 @@ protected virtual async Task ResetAsync(IFileStorage storage) { Assert.Empty(await storage.GetFileListAsync(limit: 10000)); } - public virtual async Task CanConcurrentlyManageFilesAsync() { + public virtual async Task CanConcurrentlyManageFilesAsync() + { var storage = GetStorage(); if (storage == null) return; await ResetAsync(storage); - using (storage) { + using (storage) + { const string queueFolder = "q"; var queueItems = new BlockingCollection(); var info = await storage.GetFileInfoAsync("nope"); Assert.Null(info); - await Run.InParallelAsync(10, async i => { - var ev = new PostInfo { + await Run.InParallelAsync(10, async i => + { + var ev = new PostInfo + { ApiVersion = 2, CharSet = "utf8", ContentEncoding = "application/json", @@ -520,24 +574,29 @@ await Run.InParallelAsync(10, async i => { Assert.Equal(10, (await storage.GetFileListAsync()).Count); - await Run.InParallelAsync(10, async i => { + await Run.InParallelAsync(10, async i => + { string path = Path.Combine(queueFolder, queueItems.Random() + ".json"); var eventPost = await storage.GetEventPostAndSetActiveAsync(Path.Combine(queueFolder, RandomData.GetInt(0, 25) + ".json"), _logger); if (eventPost == null) return; - if (RandomData.GetBool()) { + if (RandomData.GetBool()) + { await storage.CompleteEventPostAsync(path, eventPost.ProjectId, SystemClock.UtcNow, true, _logger); - } else + } + else await storage.SetNotActiveAsync(path, _logger); }); } } - public virtual void CanUseDataDirectory() { + public virtual void CanUseDataDirectory() + { const string DATA_DIRECTORY_QUEUE_FOLDER = @"|DataDirectory|\Queue"; - var storage = new FolderFileStorage(new FolderFileStorageOptions { + var storage = new FolderFileStorage(new FolderFileStorageOptions + { Folder = DATA_DIRECTORY_QUEUE_FOLDER }); Assert.NotNull(storage.Folder); @@ -546,7 +605,8 @@ public virtual void CanUseDataDirectory() { } } - public class PostInfo { + public class PostInfo + { public int ApiVersion { get; set; } public string CharSet { get; set; } public string ContentEncoding { get; set; } @@ -557,17 +617,22 @@ public class PostInfo { public string UserAgent { get; set; } } - public static class StorageExtensions { - public static async Task GetEventPostAndSetActiveAsync(this IFileStorage storage, string path, ILogger logger = null) { + public static class StorageExtensions + { + public static async Task GetEventPostAndSetActiveAsync(this IFileStorage storage, string path, ILogger logger = null) + { PostInfo eventPostInfo = null; - try { + try + { eventPostInfo = await storage.GetObjectAsync(path); if (eventPostInfo == null) return null; if (!await storage.ExistsAsync(path + ".x") && !await storage.SaveFileAsync(path + ".x", String.Empty)) return null; - } catch (Exception ex) { + } + catch (Exception ex) + { if (logger != null && logger.IsEnabled(LogLevel.Error)) logger.LogError(ex, "Error retrieving event post data {Path}: {Message}", path, ex.Message); return null; @@ -576,10 +641,14 @@ public static async Task GetEventPostAndSetActiveAsync(this IFileStora return eventPostInfo; } - public static async Task SetNotActiveAsync(this IFileStorage storage, string path, ILogger logger = null) { - try { + public static async Task SetNotActiveAsync(this IFileStorage storage, string path, ILogger logger = null) + { + try + { return await storage.DeleteFileAsync(path + ".x"); - } catch (Exception ex) { + } + catch (Exception ex) + { if (logger != null && logger.IsEnabled(LogLevel.Error)) logger.LogError(ex, "Error deleting work marker {Path}: {Message}", path, ex.Message); } @@ -587,22 +656,29 @@ public static async Task SetNotActiveAsync(this IFileStorage storage, stri return false; } - public static async Task CompleteEventPostAsync(this IFileStorage storage, string path, string projectId, DateTime created, bool shouldArchive = true, ILogger logger = null) { + public static async Task CompleteEventPostAsync(this IFileStorage storage, string path, string projectId, DateTime created, bool shouldArchive = true, ILogger logger = null) + { // don't move files that are already in the archive if (path.StartsWith("archive")) return true; string archivePath = $"archive\\{projectId}\\{created.ToString("yy\\\\MM\\\\dd")}\\{Path.GetFileName(path)}"; - try { - if (shouldArchive) { + try + { + if (shouldArchive) + { if (!await storage.RenameFileAsync(path, archivePath)) return false; - } else { + } + else + { if (!await storage.DeleteFileAsync(path)) return false; } - } catch (Exception ex) { + } + catch (Exception ex) + { if (logger != null && logger.IsEnabled(LogLevel.Error)) logger?.LogError(ex, "Error archiving event post data {Path}: {Message}", path, ex.Message); return false; @@ -613,4 +689,4 @@ public static async Task CompleteEventPostAsync(this IFileStorage storage, return true; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.TestHarness/Utility/BenchmarkToJson.cs b/src/Foundatio.TestHarness/Utility/BenchmarkToJson.cs index 39c27db9c..f7b0b05ca 100644 --- a/src/Foundatio.TestHarness/Utility/BenchmarkToJson.cs +++ b/src/Foundatio.TestHarness/Utility/BenchmarkToJson.cs @@ -4,34 +4,42 @@ using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; -namespace Foundatio.TestHarness.Utility { - public class StringBenchmarkLogger : ILogger { +namespace Foundatio.TestHarness.Utility +{ + public class StringBenchmarkLogger : ILogger + { private readonly StringBuilder _buffer = new(); public string Id => Guid.NewGuid().ToString(); public int Priority => 1; - - public void Write(LogKind logKind, string text) { + + public void Write(LogKind logKind, string text) + { _buffer.Append(text); } - public void WriteLine() { + public void WriteLine() + { _buffer.AppendLine(); } - public void WriteLine(LogKind logKind, string text) { + public void WriteLine(LogKind logKind, string text) + { _buffer.AppendLine(text); } - public override string ToString() { + public override string ToString() + { return _buffer.ToString(); } - public void Flush() {} + public void Flush() { } } - public static class BenchmarkSummaryExtensions { - public static string ToJson(this Summary summary, bool indentJson = true) { + public static class BenchmarkSummaryExtensions + { + public static string ToJson(this Summary summary, bool indentJson = true) + { var exporter = new JsonExporter(indentJson: indentJson); var logger = new StringBenchmarkLogger(); exporter.ExportToLog(summary, logger); diff --git a/src/Foundatio.TestHarness/Utility/Configuration.cs b/src/Foundatio.TestHarness/Utility/Configuration.cs index 013accfeb..ad528ab1b 100644 --- a/src/Foundatio.TestHarness/Utility/Configuration.cs +++ b/src/Foundatio.TestHarness/Utility/Configuration.cs @@ -2,10 +2,13 @@ using System.Reflection; using Microsoft.Extensions.Configuration; -namespace Foundatio.Tests.Utility { - public static class Configuration { +namespace Foundatio.Tests.Utility +{ + public static class Configuration + { private static readonly IConfiguration _configuration; - static Configuration() { + static Configuration() + { _configuration = new ConfigurationBuilder() .SetBasePath(GetBasePath()) .AddJsonFile("appsettings.json", true, true) @@ -13,18 +16,22 @@ static Configuration() { .Build(); } - public static IConfigurationSection GetSection(string name) { + public static IConfigurationSection GetSection(string name) + { return _configuration.GetSection(name); } - public static string GetConnectionString(string name) { + public static string GetConnectionString(string name) + { return _configuration.GetConnectionString(name); } - private static string GetBasePath() { + private static string GetBasePath() + { string basePath = Path.GetDirectoryName(typeof(Configuration).GetTypeInfo().Assembly.Location); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 5; i++) + { if (File.Exists(Path.Combine(basePath, "appsettings.json"))) return Path.GetFullPath(basePath); diff --git a/src/Foundatio.TestHarness/Utility/NonSeekableStream.cs b/src/Foundatio.TestHarness/Utility/NonSeekableStream.cs index 02940f733..1507f4dff 100644 --- a/src/Foundatio.TestHarness/Utility/NonSeekableStream.cs +++ b/src/Foundatio.TestHarness/Utility/NonSeekableStream.cs @@ -1,11 +1,14 @@ using System; using System.IO; -namespace Foundatio.Tests.Utility { - public class NonSeekableStream : Stream { +namespace Foundatio.Tests.Utility +{ + public class NonSeekableStream : Stream + { private readonly Stream _stream; - public NonSeekableStream(Stream stream) { + public NonSeekableStream(Stream stream) + { _stream = stream; } @@ -15,41 +18,49 @@ public NonSeekableStream(Stream stream) { public override bool CanWrite => _stream.CanWrite; - public override void Flush() { + public override void Flush() + { _stream.Flush(); } public override long Length => throw new NotSupportedException(); - public override long Position { + public override long Position + { get => _stream.Position; set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) { + public override int Read(byte[] buffer, int offset, int count) + { return _stream.Read(buffer, offset, count); } - public override long Seek(long offset, SeekOrigin origin) { + public override long Seek(long offset, SeekOrigin origin) + { throw new NotImplementedException(); } - public override void SetLength(long value) { + public override void SetLength(long value) + { throw new NotSupportedException(); } - public override void Write(byte[] buffer, int offset, int count) { + public override void Write(byte[] buffer, int offset, int count) + { _stream.Write(buffer, offset, count); } - public override void Close() { + public override void Close() + { _stream.Close(); base.Close(); } - protected override void Dispose(bool disposing) { + protected override void Dispose(bool disposing) + { _stream.Dispose(); base.Dispose(disposing); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Utf8Json/Utf8JsonSerializer.cs b/src/Foundatio.Utf8Json/Utf8JsonSerializer.cs index 3a5e2773b..6db6775fb 100644 --- a/src/Foundatio.Utf8Json/Utf8JsonSerializer.cs +++ b/src/Foundatio.Utf8Json/Utf8JsonSerializer.cs @@ -3,20 +3,25 @@ using Utf8Json; using Utf8Json.Resolvers; -namespace Foundatio.Serializer { - public class Utf8JsonSerializer : ITextSerializer { +namespace Foundatio.Serializer +{ + public class Utf8JsonSerializer : ITextSerializer + { private readonly IJsonFormatterResolver _formatterResolver; - public Utf8JsonSerializer(IJsonFormatterResolver resolver = null) { + public Utf8JsonSerializer(IJsonFormatterResolver resolver = null) + { _formatterResolver = resolver ?? StandardResolver.Default; } - public void Serialize(object data, Stream output) { + public void Serialize(object data, Stream output) + { JsonSerializer.NonGeneric.Serialize(data.GetType(), output, data, _formatterResolver); } - public object Deserialize(Stream input, Type objectType) { + public object Deserialize(Stream input, Type objectType) + { return JsonSerializer.NonGeneric.Deserialize(objectType, input, _formatterResolver); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Xunit/Logging/LogEntry.cs b/src/Foundatio.Xunit/Logging/LogEntry.cs index 30cbf3ad7..32a7ff7db 100644 --- a/src/Foundatio.Xunit/Logging/LogEntry.cs +++ b/src/Foundatio.Xunit/Logging/LogEntry.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; -namespace Foundatio.Xunit { - public class LogEntry { +namespace Foundatio.Xunit +{ + public class LogEntry + { public DateTime Date { get; set; } public string CategoryName { get; set; } public LogLevel LogLevel { get; set; } @@ -16,13 +18,16 @@ public class LogEntry { public string Message => Formatter(State, Exception); - public override string ToString() { + public override string ToString() + { return String.Concat("", Date.ToString("mm:ss.fffff"), " ", LogLevel.ToString().Substring(0, 1).ToUpper(), ":", CategoryName, " - ", Message); } - public string ToString(bool useFullCategory) { + public string ToString(bool useFullCategory) + { string category = CategoryName; - if (!useFullCategory) { + if (!useFullCategory) + { int lastDot = category.LastIndexOf('.'); if (lastDot >= 0) category = category.Substring(lastDot + 1); @@ -31,4 +36,4 @@ public string ToString(bool useFullCategory) { return String.Concat("", Date.ToString("mm:ss.fffff"), " ", LogLevel.ToString().Substring(0, 1).ToUpper(), ":", category, " - ", Message); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Xunit/Logging/TestLogger.cs b/src/Foundatio.Xunit/Logging/TestLogger.cs index 2c867051c..a9c530d50 100644 --- a/src/Foundatio.Xunit/Logging/TestLogger.cs +++ b/src/Foundatio.Xunit/Logging/TestLogger.cs @@ -6,22 +6,27 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Xunit { - internal class TestLogger : ILogger { +namespace Foundatio.Xunit +{ + internal class TestLogger : ILogger + { private readonly TestLoggerFactory _loggerFactory; private readonly string _categoryName; - public TestLogger(string categoryName, TestLoggerFactory loggerFactory) { + public TestLogger(string categoryName, TestLoggerFactory loggerFactory) + { _loggerFactory = loggerFactory; _categoryName = categoryName; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { if (!_loggerFactory.IsEnabled(_categoryName, logLevel)) return; object[] scopes = CurrentScopeStack.Reverse().ToArray(); - var logEntry = new LogEntry { + var logEntry = new LogEntry + { Date = SystemClock.UtcNow, LogLevel = logLevel, EventId = eventId, @@ -32,7 +37,8 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except Scopes = scopes }; - switch (state) { + switch (state) + { //case LogData logData: // logEntry.Properties["CallerMemberName"] = logData.MemberName; // logEntry.Properties["CallerFilePath"] = logData.FilePath; @@ -47,7 +53,8 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except break; } - foreach (object scope in scopes) { + foreach (object scope in scopes) + { if (!(scope is IDictionary scopeData)) continue; @@ -58,42 +65,49 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _loggerFactory.AddLogEntry(logEntry); } - public bool IsEnabled(LogLevel logLevel) { + public bool IsEnabled(LogLevel logLevel) + { return _loggerFactory.IsEnabled(_categoryName, logLevel); } - public IDisposable BeginScope(TState state) { + public IDisposable BeginScope(TState state) + { if (state == null) throw new ArgumentNullException(nameof(state)); return Push(state); } - public IDisposable BeginScope(Func scopeFactory, TState state) { + public IDisposable BeginScope(Func scopeFactory, TState state) + { if (state == null) throw new ArgumentNullException(nameof(state)); return Push(scopeFactory(state)); } - + private static readonly AsyncLocal _currentScopeStack = new(); - private sealed class Wrapper { + private sealed class Wrapper + { public ImmutableStack Value { get; set; } } - private static ImmutableStack CurrentScopeStack { + private static ImmutableStack CurrentScopeStack + { get => _currentScopeStack.Value?.Value ?? ImmutableStack.Create(); set => _currentScopeStack.Value = new Wrapper { Value = value }; } - private static IDisposable Push(object state) { + private static IDisposable Push(object state) + { CurrentScopeStack = CurrentScopeStack.Push(state); return new DisposableAction(Pop); } - private static void Pop() { + private static void Pop() + { CurrentScopeStack = CurrentScopeStack.Pop(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Xunit/Logging/TestLoggerFactory.cs b/src/Foundatio.Xunit/Logging/TestLoggerFactory.cs index 67ce85008..57c8b16dc 100644 --- a/src/Foundatio.Xunit/Logging/TestLoggerFactory.cs +++ b/src/Foundatio.Xunit/Logging/TestLoggerFactory.cs @@ -5,67 +5,79 @@ using Microsoft.Extensions.Logging; using Xunit.Abstractions; -namespace Foundatio.Xunit { - public class TestLoggerFactory : ILoggerFactory { +namespace Foundatio.Xunit +{ + public class TestLoggerFactory : ILoggerFactory + { private readonly Dictionary _logLevels = new(); private readonly Queue _logEntries = new(); private readonly Action _writeLogEntryFunc; - public TestLoggerFactory() { - _writeLogEntryFunc = e => {}; + public TestLoggerFactory() + { + _writeLogEntryFunc = e => { }; } - public TestLoggerFactory(Action writeLogEntryFunc) { + public TestLoggerFactory(Action writeLogEntryFunc) + { _writeLogEntryFunc = writeLogEntryFunc; } - public TestLoggerFactory(ITestOutputHelper output) : this(e => output.WriteLine(e.ToString(false))) {} + public TestLoggerFactory(ITestOutputHelper output) : this(e => output.WriteLine(e.ToString(false))) { } public LogLevel MinimumLevel { get; set; } = LogLevel.Information; public IReadOnlyList LogEntries => _logEntries.ToArray(); public int MaxLogEntriesToStore = 100; public int MaxLogEntriesToWrite = 1000; - internal void AddLogEntry(LogEntry logEntry) { - lock (_logEntries) { + internal void AddLogEntry(LogEntry logEntry) + { + lock (_logEntries) + { _logEntries.Enqueue(logEntry); if (_logEntries.Count > MaxLogEntriesToStore) _logEntries.Dequeue(); } - + if (_writeLogEntryFunc == null || _logEntriesWritten >= MaxLogEntriesToWrite) return; - try { + try + { _writeLogEntryFunc(logEntry); Interlocked.Increment(ref _logEntriesWritten); - } catch (Exception) { } + } + catch (Exception) { } } private int _logEntriesWritten = 0; - public ILogger CreateLogger(string categoryName) { + public ILogger CreateLogger(string categoryName) + { return new TestLogger(categoryName, this); } - public void AddProvider(ILoggerProvider loggerProvider) {} + public void AddProvider(ILoggerProvider loggerProvider) { } - public bool IsEnabled(string category, LogLevel logLevel) { + public bool IsEnabled(string category, LogLevel logLevel) + { if (_logLevels.TryGetValue(category, out var categoryLevel)) return logLevel >= categoryLevel; return logLevel >= MinimumLevel; } - public void SetLogLevel(string category, LogLevel minLogLevel) { + public void SetLogLevel(string category, LogLevel minLogLevel) + { _logLevels[category] = minLogLevel; } - public void SetLogLevel(LogLevel minLogLevel) { + public void SetLogLevel(LogLevel minLogLevel) + { SetLogLevel(TypeHelper.GetTypeDisplayName(typeof(T)), minLogLevel); } - public void Dispose() {} + public void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Xunit/Logging/TestWithLoggingBase.cs b/src/Foundatio.Xunit/Logging/TestWithLoggingBase.cs index 931e79c47..f037f30c2 100644 --- a/src/Foundatio.Xunit/Logging/TestWithLoggingBase.cs +++ b/src/Foundatio.Xunit/Logging/TestWithLoggingBase.cs @@ -1,15 +1,18 @@ using Microsoft.Extensions.Logging; using Xunit.Abstractions; -namespace Foundatio.Xunit { - public abstract class TestWithLoggingBase { +namespace Foundatio.Xunit +{ + public abstract class TestWithLoggingBase + { protected readonly ILogger _logger; - protected TestWithLoggingBase(ITestOutputHelper output) { + protected TestWithLoggingBase(ITestOutputHelper output) + { Log = new TestLoggerFactory(output); _logger = Log.CreateLogger(GetType()); } protected TestLoggerFactory Log { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Xunit/Retry/DelayedMessageBus.cs b/src/Foundatio.Xunit/Retry/DelayedMessageBus.cs index bbcafbdc4..b30092bb8 100644 --- a/src/Foundatio.Xunit/Retry/DelayedMessageBus.cs +++ b/src/Foundatio.Xunit/Retry/DelayedMessageBus.cs @@ -2,20 +2,24 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Foundatio.Xunit { +namespace Foundatio.Xunit +{ /// /// Used to capture messages to potentially be forwarded later. Messages are forwarded by /// disposing of the message bus. /// - public class DelayedMessageBus : IMessageBus { + public class DelayedMessageBus : IMessageBus + { private readonly IMessageBus innerBus; private readonly List messages = new(); - public DelayedMessageBus(IMessageBus innerBus) { + public DelayedMessageBus(IMessageBus innerBus) + { this.innerBus = innerBus; } - public bool QueueMessage(IMessageSinkMessage message) { + public bool QueueMessage(IMessageSinkMessage message) + { lock (messages) messages.Add(message); @@ -24,7 +28,8 @@ public bool QueueMessage(IMessageSinkMessage message) { return true; } - public void Dispose() { + public void Dispose() + { foreach (var message in messages) innerBus.QueueMessage(message); } diff --git a/src/Foundatio.Xunit/Retry/RetryAttribute.cs b/src/Foundatio.Xunit/Retry/RetryAttribute.cs index b6b625d34..d7f8f3997 100644 --- a/src/Foundatio.Xunit/Retry/RetryAttribute.cs +++ b/src/Foundatio.Xunit/Retry/RetryAttribute.cs @@ -1,13 +1,16 @@ using Xunit; using Xunit.Sdk; -namespace Foundatio.Xunit { +namespace Foundatio.Xunit +{ /// /// Works just like [Fact] except that failures are retried (by default, 3 times). /// [XunitTestCaseDiscoverer("Foundatio.Xunit.RetryFactDiscoverer", "Foundatio.TestHarness")] - public class RetryFactAttribute : FactAttribute { - public RetryFactAttribute(int maxRetries = 3) { + public class RetryFactAttribute : FactAttribute + { + public RetryFactAttribute(int maxRetries = 3) + { MaxRetries = maxRetries; } diff --git a/src/Foundatio.Xunit/Retry/RetryFactDiscoverer.cs b/src/Foundatio.Xunit/Retry/RetryFactDiscoverer.cs index 741f0a072..5e31ed09c 100644 --- a/src/Foundatio.Xunit/Retry/RetryFactDiscoverer.cs +++ b/src/Foundatio.Xunit/Retry/RetryFactDiscoverer.cs @@ -2,15 +2,19 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Foundatio.Xunit { - public class RetryFactDiscoverer : IXunitTestCaseDiscoverer { +namespace Foundatio.Xunit +{ + public class RetryFactDiscoverer : IXunitTestCaseDiscoverer + { readonly IMessageSink diagnosticMessageSink; - public RetryFactDiscoverer(IMessageSink diagnosticMessageSink) { + public RetryFactDiscoverer(IMessageSink diagnosticMessageSink) + { this.diagnosticMessageSink = diagnosticMessageSink; } - public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { var maxRetries = factAttribute.GetNamedArgument("MaxRetries"); if (maxRetries < 1) maxRetries = 3; diff --git a/src/Foundatio.Xunit/Retry/RetryTestCase.cs b/src/Foundatio.Xunit/Retry/RetryTestCase.cs index c4947d03b..696c551c0 100644 --- a/src/Foundatio.Xunit/Retry/RetryTestCase.cs +++ b/src/Foundatio.Xunit/Retry/RetryTestCase.cs @@ -5,9 +5,11 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Foundatio.Xunit { +namespace Foundatio.Xunit +{ [Serializable] - public class RetryTestCase : XunitTestCase { + public class RetryTestCase : XunitTestCase + { private int maxRetries; [EditorBrowsable(EditorBrowsableState.Never)] @@ -15,10 +17,11 @@ public class RetryTestCase : XunitTestCase { public RetryTestCase() { } public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, TestMethodDisplayOptions testMethodDisplayOptions, ITestMethod testMethod, int maxRetries) - : base(diagnosticMessageSink, testMethodDisplay, testMethodDisplayOptions, testMethod, testMethodArguments: null) { + : base(diagnosticMessageSink, testMethodDisplay, testMethodDisplayOptions, testMethod, testMethodArguments: null) + { this.maxRetries = maxRetries; } - + // This method is called by the xUnit test framework classes to run the test case. We will do the // loop here, forwarding on to the implementation in XunitTestCase to do the heavy lifting. We will // continue to re-run the test until the aggregator has an error (meaning that some internal error @@ -27,16 +30,19 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) { + CancellationTokenSource cancellationTokenSource) + { var runCount = 0; - while (true) { + while (true) + { // This is really the only tricky bit: we need to capture and delay messages (since those will // contain run status) until we know we've decided to accept the final result; var delayedMessageBus = new DelayedMessageBus(messageBus); - + var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) { + if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) + { delayedMessageBus.Dispose(); // Sends all the delayed messages return summary; } @@ -45,13 +51,15 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi } } - public override void Serialize(IXunitSerializationInfo data) { + public override void Serialize(IXunitSerializationInfo data) + { base.Serialize(data); data.AddValue("MaxRetries", maxRetries); } - public override void Deserialize(IXunitSerializationInfo data) { + public override void Deserialize(IXunitSerializationInfo data) + { base.Deserialize(data); maxRetries = data.GetValue("MaxRetries"); diff --git a/src/Foundatio.Xunit/Retry/RetryTheoryAttribute.cs b/src/Foundatio.Xunit/Retry/RetryTheoryAttribute.cs index a80044290..6af4e038c 100644 --- a/src/Foundatio.Xunit/Retry/RetryTheoryAttribute.cs +++ b/src/Foundatio.Xunit/Retry/RetryTheoryAttribute.cs @@ -1,13 +1,16 @@ using Xunit; using Xunit.Sdk; -namespace Foundatio.Xunit { +namespace Foundatio.Xunit +{ /// /// Works just like [Fact] except that failures are retried (by default, 3 times). /// [XunitTestCaseDiscoverer("Foundatio.Xunit.RetryTheoryDiscoverer", "Foundatio.TestHarness")] - public class RetryTheoryAttribute : TheoryAttribute { - public RetryTheoryAttribute(int maxRetries = 3) { + public class RetryTheoryAttribute : TheoryAttribute + { + public RetryTheoryAttribute(int maxRetries = 3) + { MaxRetries = maxRetries; } diff --git a/src/Foundatio.Xunit/Retry/RetryTheoryDiscoverer.cs b/src/Foundatio.Xunit/Retry/RetryTheoryDiscoverer.cs index 92ebd277f..f9d8fc69f 100644 --- a/src/Foundatio.Xunit/Retry/RetryTheoryDiscoverer.cs +++ b/src/Foundatio.Xunit/Retry/RetryTheoryDiscoverer.cs @@ -2,15 +2,19 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Foundatio.Xunit { - public class RetryTheoryDiscoverer : IXunitTestCaseDiscoverer { +namespace Foundatio.Xunit +{ + public class RetryTheoryDiscoverer : IXunitTestCaseDiscoverer + { readonly IMessageSink diagnosticMessageSink; - public RetryTheoryDiscoverer(IMessageSink diagnosticMessageSink) { + public RetryTheoryDiscoverer(IMessageSink diagnosticMessageSink) + { this.diagnosticMessageSink = diagnosticMessageSink; } - public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { var maxRetries = factAttribute.GetNamedArgument("MaxRetries"); if (maxRetries < 1) maxRetries = 3; diff --git a/src/Foundatio.Xunit/Retry/RetryTheoryTestCase.cs b/src/Foundatio.Xunit/Retry/RetryTheoryTestCase.cs index 7c401fcf1..0d04b03c3 100644 --- a/src/Foundatio.Xunit/Retry/RetryTheoryTestCase.cs +++ b/src/Foundatio.Xunit/Retry/RetryTheoryTestCase.cs @@ -5,9 +5,11 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Foundatio.Xunit { +namespace Foundatio.Xunit +{ [Serializable] - public class RetryTheoryTestCase : XunitTheoryTestCase { + public class RetryTheoryTestCase : XunitTheoryTestCase + { private int maxRetries; [EditorBrowsable(EditorBrowsableState.Never)] @@ -15,7 +17,8 @@ public class RetryTheoryTestCase : XunitTheoryTestCase { public RetryTheoryTestCase() { } public RetryTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, TestMethodDisplayOptions testMethodDisplayOptions, ITestMethod testMethod, int maxRetries) - : base(diagnosticMessageSink, testMethodDisplay, testMethodDisplayOptions, testMethod) { + : base(diagnosticMessageSink, testMethodDisplay, testMethodDisplayOptions, testMethod) + { this.maxRetries = maxRetries; } @@ -27,16 +30,19 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) { + CancellationTokenSource cancellationTokenSource) + { var runCount = 0; - while (true) { + while (true) + { // This is really the only tricky bit: we need to capture and delay messages (since those will // contain run status) until we know we've decided to accept the final result; var delayedMessageBus = new DelayedMessageBus(messageBus); var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) { + if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) + { delayedMessageBus.Dispose(); // Sends all the delayed messages return summary; } @@ -45,13 +51,15 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi } } - public override void Serialize(IXunitSerializationInfo data) { + public override void Serialize(IXunitSerializationInfo data) + { base.Serialize(data); data.AddValue("MaxRetries", maxRetries); } - public override void Deserialize(IXunitSerializationInfo data) { + public override void Deserialize(IXunitSerializationInfo data) + { base.Deserialize(data); maxRetries = data.GetValue("MaxRetries"); diff --git a/src/Foundatio/Caching/CacheValue.cs b/src/Foundatio/Caching/CacheValue.cs index 181c7682b..6631e9fb0 100644 --- a/src/Foundatio/Caching/CacheValue.cs +++ b/src/Foundatio/Caching/CacheValue.cs @@ -1,6 +1,9 @@ -namespace Foundatio.Caching { - public class CacheValue { - public CacheValue(T value, bool hasValue) { +namespace Foundatio.Caching +{ + public class CacheValue + { + public CacheValue(T value, bool hasValue) + { Value = value; HasValue = hasValue; } @@ -15,8 +18,9 @@ public CacheValue(T value, bool hasValue) { public static CacheValue NoValue { get; } = new CacheValue(default, false); - public override string ToString() { + public override string ToString() + { return Value?.ToString() ?? ""; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Caching/HybridCacheClient.cs b/src/Foundatio/Caching/HybridCacheClient.cs index cc4132a29..7513f9c76 100644 --- a/src/Foundatio/Caching/HybridCacheClient.cs +++ b/src/Foundatio/Caching/HybridCacheClient.cs @@ -3,15 +3,17 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Messaging; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Caching { +namespace Foundatio.Caching +{ public interface IHybridCacheClient : ICacheClient { } - public class HybridCacheClient : IHybridCacheClient { + public class HybridCacheClient : IHybridCacheClient + { protected readonly ICacheClient _distributedCache; protected readonly IMessageBus _messageBus; private readonly string _cacheId = Guid.NewGuid().ToString("N"); @@ -20,7 +22,8 @@ public class HybridCacheClient : IHybridCacheClient { private long _localCacheHits; private long _invalidateCacheCalls; - public HybridCacheClient(ICacheClient distributedCacheClient, IMessageBus messageBus, InMemoryCacheClientOptions localCacheOptions = null, ILoggerFactory loggerFactory = null) { + public HybridCacheClient(ICacheClient distributedCacheClient, IMessageBus messageBus, InMemoryCacheClientOptions localCacheOptions = null, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _distributedCache = distributedCacheClient; _messageBus = messageBus; @@ -35,7 +38,8 @@ public HybridCacheClient(ICacheClient distributedCacheClient, IMessageBus messag public long LocalCacheHits => _localCacheHits; public long InvalidateCacheCalls => _invalidateCacheCalls; - private Task OnLocalCacheItemExpiredAsync(object sender, ItemExpiredEventArgs args) { + private Task OnLocalCacheItemExpiredAsync(object sender, ItemExpiredEventArgs args) + { if (!args.SendNotification) return Task.CompletedTask; @@ -43,21 +47,25 @@ private Task OnLocalCacheItemExpiredAsync(object sender, ItemExpiredEventArgs ar return _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { args.Key }, Expired = true }); } - private Task OnRemoteCacheItemExpiredAsync(InvalidateCache message) { + private Task OnRemoteCacheItemExpiredAsync(InvalidateCache message) + { if (!String.IsNullOrEmpty(message.CacheId) && String.Equals(_cacheId, message.CacheId)) return Task.CompletedTask; if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Invalidating local cache from remote: id={CacheId} expired={Expired} keys={Keys}", message.CacheId, message.Expired, String.Join(",", message.Keys ?? new string[] { })); Interlocked.Increment(ref _invalidateCacheCalls); - if (message.FlushAll) { + if (message.FlushAll) + { _logger.LogTrace("Flushed local cache"); return _localCache.RemoveAllAsync(); } - if (message.Keys != null && message.Keys.Length > 0) { + if (message.Keys != null && message.Keys.Length > 0) + { var tasks = new List(message.Keys.Length); var keysToRemove = new List(message.Keys.Length); - foreach (string key in message.Keys) { + foreach (string key in message.Keys) + { if (message.Expired) _localCache.RemoveExpiredKey(key, false); else if (key.EndsWith("*")) @@ -76,39 +84,45 @@ private Task OnRemoteCacheItemExpiredAsync(InvalidateCache message) { return Task.CompletedTask; } - public async Task RemoveAsync(string key) { - var removed = await _distributedCache.RemoveAsync(key).AnyContext(); + public async Task RemoveAsync(string key) + { + var removed = await _distributedCache.RemoveAsync(key).AnyContext(); await _localCache.RemoveAsync(key).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return removed; } - public async Task RemoveIfEqualAsync(string key, T expected) { - var removed = await _distributedCache.RemoveAsync(key).AnyContext(); + public async Task RemoveIfEqualAsync(string key, T expected) + { + var removed = await _distributedCache.RemoveAsync(key).AnyContext(); await _localCache.RemoveAsync(key).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return removed; } - public async Task RemoveAllAsync(IEnumerable keys = null) { + public async Task RemoveAllAsync(IEnumerable keys = null) + { var items = keys?.ToArray(); bool flushAll = items == null || items.Length == 0; - var removed = await _distributedCache.RemoveAllAsync(items).AnyContext(); + var removed = await _distributedCache.RemoveAllAsync(items).AnyContext(); await _localCache.RemoveAllAsync(items).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, FlushAll = flushAll, Keys = items }).AnyContext(); return removed; } - public async Task RemoveByPrefixAsync(string prefix) { + public async Task RemoveByPrefixAsync(string prefix) + { var removed = await _distributedCache.RemoveByPrefixAsync(prefix).AnyContext(); await _localCache.RemoveByPrefixAsync(prefix).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { prefix + "*" } }).AnyContext(); return removed; } - public async Task> GetAsync(string key) { + public async Task> GetAsync(string key) + { var cacheValue = await _localCache.GetAsync(key).AnyContext(); - if (cacheValue.HasValue) { + if (cacheValue.HasValue) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Local cache hit: {Key}", key); Interlocked.Increment(ref _localCacheHits); return cacheValue; @@ -116,7 +130,8 @@ public async Task> GetAsync(string key) { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Local cache miss: {Key}", key); cacheValue = await _distributedCache.GetAsync(key).AnyContext(); - if (cacheValue.HasValue) { + if (cacheValue.HasValue) + { var expiration = await _distributedCache.GetExpirationAsync(key).AnyContext(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Setting Local cache key: {Key} with expiration: {Expiration}", key, expiration); @@ -127,11 +142,13 @@ public async Task> GetAsync(string key) { return cacheValue.HasValue ? cacheValue : CacheValue.NoValue; } - public Task>> GetAllAsync(IEnumerable keys) { + public Task>> GetAllAsync(IEnumerable keys) + { return _distributedCache.GetAllAsync(keys); } - public async Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { + public async Task AddAsync(string key, T value, TimeSpan? expiresIn = null) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Adding key {Key} to local cache with expiration: {Expiration}", key, expiresIn); bool added = await _distributedCache.AddAsync(key, value, expiresIn).AnyContext(); if (added) @@ -140,16 +157,18 @@ public async Task AddAsync(string key, T value, TimeSpan? expiresIn = n return added; } - public async Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { + public async Task SetAsync(string key, T value, TimeSpan? expiresIn = null) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Setting key {Key} to local cache with expiration: {Expiration}", key, expiresIn); await _localCache.SetAsync(key, value, expiresIn).AnyContext(); var set = await _distributedCache.SetAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); - + return set; } - public async Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) { + public async Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) + { if (values == null || values.Count == 0) return 0; @@ -160,83 +179,98 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e return set; } - public async Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) { + public async Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) + { await _localCache.ReplaceAsync(key, value, expiresIn).AnyContext(); bool replaced = await _distributedCache.ReplaceAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return replaced; } - public async Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) { + public async Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) + { await _localCache.ReplaceAsync(key, value, expiresIn).AnyContext(); bool replaced = await _distributedCache.ReplaceAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return replaced; } - public async Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) + { double incremented = await _distributedCache.IncrementAsync(key, amount, expiresIn).AnyContext(); await _localCache.ReplaceAsync(key, incremented, expiresIn); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return incremented; } - public async Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) + { long incremented = await _distributedCache.IncrementAsync(key, amount, expiresIn).AnyContext(); await _localCache.ReplaceAsync(key, incremented, expiresIn); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return incremented; } - public Task ExistsAsync(string key) { + public Task ExistsAsync(string key) + { return _distributedCache.ExistsAsync(key); } - public Task GetExpirationAsync(string key) { + public Task GetExpirationAsync(string key) + { return _distributedCache.GetExpirationAsync(key); } - public async Task SetExpirationAsync(string key, TimeSpan expiresIn) { + public async Task SetExpirationAsync(string key, TimeSpan expiresIn) + { await _localCache.SetExpirationAsync(key, expiresIn).AnyContext(); await _distributedCache.SetExpirationAsync(key, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); } - public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) { + public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) + { await _localCache.RemoveAsync(key).AnyContext(); double difference = await _distributedCache.SetIfHigherAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return difference; } - public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { + public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) + { await _localCache.RemoveAsync(key).AnyContext(); long difference = await _distributedCache.SetIfHigherAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return difference; } - public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { + public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) + { await _localCache.RemoveAsync(key).AnyContext(); double difference = await _distributedCache.SetIfLowerAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return difference; } - public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { + public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) + { await _localCache.RemoveAsync(key).AnyContext(); long difference = await _distributedCache.SetIfLowerAsync(key, value, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return difference; } - public async Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { - if (values is string stringValue) { + public async Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { + if (values is string stringValue) + { await _localCache.ListAddAsync(key, stringValue, expiresIn).AnyContext(); long set = await _distributedCache.ListAddAsync(key, stringValue, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return set; - } else { + } + else + { var items = values?.ToArray(); await _localCache.ListAddAsync(key, items, expiresIn).AnyContext(); long set = await _distributedCache.ListAddAsync(key, items, expiresIn).AnyContext(); @@ -245,13 +279,17 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS } } - public async Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { - if (values is string stringValue) { + public async Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { + if (values is string stringValue) + { await _localCache.ListRemoveAsync(key, stringValue, expiresIn).AnyContext(); long removed = await _distributedCache.ListRemoveAsync(key, stringValue, expiresIn).AnyContext(); await _messageBus.PublishAsync(new InvalidateCache { CacheId = _cacheId, Keys = new[] { key } }).AnyContext(); return removed; - } else { + } + else + { var items = values?.ToArray(); await _localCache.ListRemoveAsync(key, items, expiresIn).AnyContext(); long removed = await _distributedCache.ListRemoveAsync(key, items, expiresIn).AnyContext(); @@ -260,9 +298,11 @@ public async Task ListRemoveAsync(string key, IEnumerable values, Ti } } - public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) { + public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) + { var cacheValue = await _localCache.GetListAsync(key, page, pageSize).AnyContext(); - if (cacheValue.HasValue) { + if (cacheValue.HasValue) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Local cache hit: {Key}", key); Interlocked.Increment(ref _localCacheHits); return cacheValue; @@ -270,7 +310,8 @@ public async Task>> GetListAsync(string key, int? p if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Local cache miss: {Key}", key); cacheValue = await _distributedCache.GetListAsync(key, page, pageSize).AnyContext(); - if (cacheValue.HasValue) { + if (cacheValue.HasValue) + { var expiration = await _distributedCache.GetExpirationAsync(key).AnyContext(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Setting Local cache key: {Key} with expiration: {Expiration}", key, expiration); @@ -281,14 +322,16 @@ public async Task>> GetListAsync(string key, int? p return cacheValue.HasValue ? cacheValue : CacheValue>.NoValue; } - public virtual void Dispose() { + public virtual void Dispose() + { _localCache.ItemExpired.RemoveHandler(OnLocalCacheItemExpiredAsync); _localCache.Dispose(); // TODO: unsubscribe handler from messagebus. } - public class InvalidateCache { + public class InvalidateCache + { public string CacheId { get; set; } public string[] Keys { get; set; } public bool FlushAll { get; set; } diff --git a/src/Foundatio/Caching/ICacheClient.cs b/src/Foundatio/Caching/ICacheClient.cs index e445cd969..51ecf8360 100644 --- a/src/Foundatio/Caching/ICacheClient.cs +++ b/src/Foundatio/Caching/ICacheClient.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Foundatio.Caching { - public interface ICacheClient : IDisposable { +namespace Foundatio.Caching +{ + public interface ICacheClient : IDisposable + { Task RemoveAsync(string key); Task RemoveIfEqualAsync(string key, T expected); Task RemoveAllAsync(IEnumerable keys = null); diff --git a/src/Foundatio/Caching/InMemoryCacheClient.cs b/src/Foundatio/Caching/InMemoryCacheClient.cs index 8cc54a9cd..3e4cca051 100644 --- a/src/Foundatio/Caching/InMemoryCacheClient.cs +++ b/src/Foundatio/Caching/InMemoryCacheClient.cs @@ -10,8 +10,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Caching { - public class InMemoryCacheClient : ICacheClient { +namespace Foundatio.Caching +{ + public class InMemoryCacheClient : ICacheClient + { private readonly ConcurrentDictionary _memory; private bool _shouldClone; private bool _shouldThrowOnSerializationErrors; @@ -22,9 +24,10 @@ public class InMemoryCacheClient : ICacheClient { private readonly ILogger _logger; private readonly object _lock = new(); - public InMemoryCacheClient() : this(o => o) {} + public InMemoryCacheClient() : this(o => o) { } - public InMemoryCacheClient(InMemoryCacheClientOptions options = null) { + public InMemoryCacheClient(InMemoryCacheClientOptions options = null) + { if (options == null) options = new InMemoryCacheClientOptions(); _shouldClone = options.CloneValues; @@ -46,11 +49,13 @@ public InMemoryCacheClient(Builder _hits; public long Misses => _misses; - public override string ToString() { + public override string ToString() + { return $"Count: {Count} Calls: {Calls} Reads: {Reads} Writes: {Writes} Hits: {Hits} Misses: {Misses}"; } - public void ResetStats() { + public void ResetStats() + { _writes = 0; _hits = 0; _misses = 0; @@ -58,12 +63,15 @@ public void ResetStats() { public AsyncEvent ItemExpired { get; } = new AsyncEvent(); - private void OnItemExpired(string key, bool sendNotification = true) { + private void OnItemExpired(string key, bool sendNotification = true) + { if (ItemExpired == null) return; - Task.Factory.StartNew(state => { - var args = new ItemExpiredEventArgs { + Task.Factory.StartNew(state => + { + var args = new ItemExpiredEventArgs + { Client = this, Key = key, SendNotification = sendNotification @@ -73,8 +81,10 @@ private void OnItemExpired(string key, bool sendNotification = true) { }, this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } - public ICollection Keys { - get { + public ICollection Keys + { + get + { return _memory.ToArray() .OrderBy(kvp => kvp.Value.LastAccessTicks) .ThenBy(kvp => kvp.Value.InstanceNumber) @@ -83,8 +93,10 @@ public ICollection Keys { } } - public ICollection> Items { - get { + public ICollection> Items + { + get + { return _memory.ToArray() .OrderBy(kvp => kvp.Value.LastAccessTicks) .ThenBy(kvp => kvp.Value.InstanceNumber) @@ -93,7 +105,8 @@ public ICollection> Items { } } - public Task RemoveAsync(string key) { + public Task RemoveAsync(string key) + { if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); @@ -101,7 +114,8 @@ public Task RemoveAsync(string key) { return Task.FromResult(_memory.TryRemove(key, out _)); } - public async Task RemoveIfEqualAsync(string key, T expected) { + public async Task RemoveIfEqualAsync(string key, T expected) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); @@ -109,35 +123,40 @@ public async Task RemoveIfEqualAsync(string key, T expected) { _logger.LogTrace("RemoveIfEqualAsync Key: {Key} Expected: {Expected}", key, expected); bool wasExpectedValue = false; - bool success = _memory.TryUpdate(key, (k, e) => { + bool success = _memory.TryUpdate(key, (k, e) => + { var currentValue = e.GetValue(); - if (currentValue.Equals(expected)) { + if (currentValue.Equals(expected)) + { e.ExpiresAt = DateTime.MinValue; wasExpectedValue = true; } - + return e; }); - + success = success && wasExpectedValue; await StartMaintenanceAsync().AnyContext(); - + if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("RemoveIfEqualAsync Key: {Key} Expected: {Expected} Success: {Success}", key, expected, success); return success; } - public Task RemoveAllAsync(IEnumerable keys = null) { - if (keys == null) { + public Task RemoveAllAsync(IEnumerable keys = null) + { + if (keys == null) + { int count = _memory.Count; _memory.Clear(); return Task.FromResult(count); } int removed = 0; - foreach (string key in keys) { + foreach (string key in keys) + { if (String.IsNullOrEmpty(key)) continue; @@ -149,14 +168,18 @@ public Task RemoveAllAsync(IEnumerable keys = null) { return Task.FromResult(removed); } - public Task RemoveByPrefixAsync(string prefix) { + public Task RemoveByPrefixAsync(string prefix) + { var keysToRemove = new List(); var regex = new Regex(String.Concat(prefix, "*").Replace("*", ".*").Replace("?", ".+")); - try { + try + { foreach (string key in _memory.Keys.ToList()) if (regex.IsMatch(key)) keysToRemove.Add(key); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error trying to remove items from cache with this {Prefix} prefix", prefix); } @@ -164,23 +187,27 @@ public Task RemoveByPrefixAsync(string prefix) { return RemoveAllAsync(keysToRemove); } - internal void RemoveExpiredKey(string key, bool sendNotification = true) { + internal void RemoveExpiredKey(string key, bool sendNotification = true) + { _logger.LogDebug("Removing expired cache entry {Key}", key); if (_memory.TryRemove(key, out _)) OnItemExpired(key, sendNotification); } - public Task> GetAsync(string key) { + public Task> GetAsync(string key) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (!_memory.TryGetValue(key, out var cacheEntry)) { + if (!_memory.TryGetValue(key, out var cacheEntry)) + { Interlocked.Increment(ref _misses); return Task.FromResult(CacheValue.NoValue); } - if (cacheEntry.ExpiresAt < SystemClock.UtcNow) { + if (cacheEntry.ExpiresAt < SystemClock.UtcNow) + { RemoveExpiredKey(key); Interlocked.Increment(ref _misses); return Task.FromResult(CacheValue.NoValue); @@ -188,21 +215,25 @@ public Task> GetAsync(string key) { Interlocked.Increment(ref _hits); - try { + try + { var value = cacheEntry.GetValue(); return Task.FromResult(new CacheValue(value, true)); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Unable to deserialize value {Value} to type {TypeFullName}", cacheEntry.Value, typeof(T).FullName); - + if (_shouldThrowOnSerializationErrors) throw; - + return Task.FromResult(CacheValue.NoValue); } } - public async Task>> GetAllAsync(IEnumerable keys) { + public async Task>> GetAllAsync(IEnumerable keys) + { var map = new Dictionary>(); foreach (string key in keys) @@ -211,7 +242,8 @@ public async Task>> GetAllAsync(IEnumerable return map; } - public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); @@ -219,7 +251,8 @@ public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { return SetInternalAsync(key, new CacheEntry(value, expiresAt, _shouldClone), true); } - public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); @@ -227,11 +260,13 @@ public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { return SetInternalAsync(key, new CacheEntry(value, expiresAt, _shouldClone)); } - public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) { + public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } @@ -240,18 +275,24 @@ public async Task SetIfHigherAsync(string key, double value, TimeSpan? e double difference = value; var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => { + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => + { double? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } - if (currentValue.HasValue && currentValue.Value < value) { + if (currentValue.HasValue && currentValue.Value < value) + { difference = value - currentValue.Value; entry.Value = value; - } else + } + else difference = 0; if (expiresIn.HasValue) @@ -265,11 +306,13 @@ public async Task SetIfHigherAsync(string key, double value, TimeSpan? e return difference; } - public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { + public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } @@ -278,18 +321,24 @@ public async Task SetIfHigherAsync(string key, long value, TimeSpan? expir long difference = value; var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => { + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => + { long? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } - if (currentValue.HasValue && currentValue.Value < value) { + if (currentValue.HasValue && currentValue.Value < value) + { difference = value - currentValue.Value; entry.Value = value; - } else + } + else difference = 0; if (expiresIn.HasValue) @@ -303,11 +352,13 @@ public async Task SetIfHigherAsync(string key, long value, TimeSpan? expir return difference; } - public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { + public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } @@ -316,18 +367,24 @@ public async Task SetIfLowerAsync(string key, double value, TimeSpan? ex double difference = value; var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => { + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => + { double? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } - if (currentValue.HasValue && currentValue.Value > value) { + if (currentValue.HasValue && currentValue.Value > value) + { difference = currentValue.Value - value; entry.Value = value; - } else + } + else difference = 0; if (expiresIn.HasValue) @@ -341,29 +398,37 @@ public async Task SetIfLowerAsync(string key, double value, TimeSpan? ex return difference; } - public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { + public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } long difference = value; var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => { + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (k, entry) => + { long? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } - if (currentValue.HasValue && currentValue.Value > value) { + if (currentValue.HasValue && currentValue.Value > value) + { difference = currentValue.Value - value; entry.Value = value; - } else + } + else difference = 0; if (expiresIn.HasValue) @@ -377,7 +442,8 @@ public async Task SetIfLowerAsync(string key, long value, TimeSpan? expire return difference; } - public async Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public async Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); @@ -385,17 +451,20 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS throw new ArgumentNullException(nameof(values)); var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - if (expiresAt < SystemClock.UtcNow) { + if (expiresAt < SystemClock.UtcNow) + { RemoveExpiredKey(key); return default; } Interlocked.Increment(ref _writes); - if (values is string stringValue) { + if (values is string stringValue) + { var items = new HashSet(new[] { stringValue }); var entry = new CacheEntry(items, expiresAt, _shouldClone); - _memory.AddOrUpdate(key, entry, (k, cacheEntry) => { + _memory.AddOrUpdate(key, entry, (k, cacheEntry) => + { if (!(cacheEntry.Value is ICollection collection)) throw new InvalidOperationException($"Unable to add value for key: {key}. Cache value does not contain a set"); @@ -411,10 +480,13 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS await StartMaintenanceAsync().AnyContext(); return items.Count; - } else { + } + else + { var items = new HashSet(values); var entry = new CacheEntry(items, expiresAt, _shouldClone); - _memory.AddOrUpdate(key, entry, (k, cacheEntry) => { + _memory.AddOrUpdate(key, entry, (k, cacheEntry) => + { if (!(cacheEntry.Value is ICollection collection)) throw new InvalidOperationException($"Unable to add value for key: {key}. Cache value does not contain a set"); @@ -433,7 +505,8 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS } } - public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); @@ -441,17 +514,21 @@ public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan throw new ArgumentNullException(nameof(values)); var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - if (expiresAt < SystemClock.UtcNow) { + if (expiresAt < SystemClock.UtcNow) + { RemoveExpiredKey(key); return default; } Interlocked.Increment(ref _writes); - if (values is string stringValue) { + if (values is string stringValue) + { var items = new HashSet(new[] { stringValue }); - _memory.TryUpdate(key, (k, cacheEntry) => { - if (cacheEntry.Value is ICollection collection && collection.Count > 0) { + _memory.TryUpdate(key, (k, cacheEntry) => + { + if (cacheEntry.Value is ICollection collection && collection.Count > 0) + { foreach (var value in items) collection.Remove(value); @@ -466,10 +543,14 @@ public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan }); return Task.FromResult(items.Count); - } else { + } + else + { var items = new HashSet(values); - _memory.TryUpdate(key, (k, cacheEntry) => { - if (cacheEntry.Value is ICollection collection && collection.Count > 0) { + _memory.TryUpdate(key, (k, cacheEntry) => + { + if (cacheEntry.Value is ICollection collection && collection.Count > 0) + { foreach (var value in items) collection.Remove(value); @@ -487,37 +568,44 @@ public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan } } - public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) { + public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); var list = await GetAsync>(key); if (!list.HasValue || !page.HasValue) return list; - + int skip = (page.Value - 1) * pageSize; var pagedItems = list.Value.Skip(skip).Take(pageSize).ToArray(); return new CacheValue>(pagedItems, true); } - private async Task SetInternalAsync(string key, CacheEntry entry, bool addOnly = false) { + private async Task SetInternalAsync(string key, CacheEntry entry, bool addOnly = false) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "SetInternalAsync: Key cannot be null or empty"); - if (entry.ExpiresAt < SystemClock.UtcNow) { + if (entry.ExpiresAt < SystemClock.UtcNow) + { RemoveExpiredKey(key); return false; } Interlocked.Increment(ref _writes); - if (addOnly) { - if (!_memory.TryAdd(key, entry)) { - + if (addOnly) + { + if (!_memory.TryAdd(key, entry)) + { + // check to see if existing entry is expired bool updated = false; - _memory.TryUpdate(key, (key, existingEntry) => { - if (existingEntry.ExpiresAt < SystemClock.UtcNow) { + _memory.TryUpdate(key, (key, existingEntry) => + { + if (existingEntry.ExpiresAt < SystemClock.UtcNow) + { updated = true; return entry; } @@ -530,7 +618,9 @@ private async Task SetInternalAsync(string key, CacheEntry entry, bool add } if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Added cache key: {Key}", key); - } else { + } + else + { _memory.AddOrUpdate(key, entry, (k, cacheEntry) => entry); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Set cache key: {Key}", key); } @@ -540,7 +630,8 @@ private async Task SetInternalAsync(string key, CacheEntry entry, bool add return true; } - public async Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) { + public async Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) + { if (values == null || values.Count == 0) return 0; @@ -552,7 +643,8 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e return results.Count(r => r); } - public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); @@ -562,7 +654,8 @@ public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = nul return SetAsync(key, value, expiresIn); } - public async Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) { + public async Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); @@ -573,16 +666,18 @@ public async Task ReplaceIfEqualAsync(string key, T value, T expected, var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; bool wasExpectedValue = false; - bool success = _memory.TryUpdate(key, (k, cacheEntry) => { + bool success = _memory.TryUpdate(key, (k, cacheEntry) => + { var currentValue = cacheEntry.GetValue(); - if (currentValue.Equals(expected)) { + if (currentValue.Equals(expected)) + { cacheEntry.Value = value; wasExpectedValue = true; if (expiresIn.HasValue) cacheEntry.ExpiresAt = expiresAt; } - + return cacheEntry; }); @@ -595,11 +690,13 @@ public async Task ReplaceIfEqualAsync(string key, T value, T expected, return success; } - public async Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } @@ -607,11 +704,15 @@ public async Task IncrementAsync(string key, double amount, TimeSpan? ex Interlocked.Increment(ref _writes); var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (k, entry) => { + var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (k, entry) => + { double? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } @@ -631,11 +732,13 @@ public async Task IncrementAsync(string key, double amount, TimeSpan? ex return result.GetValue(); } - public async Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { RemoveExpiredKey(key); return -1; } @@ -643,11 +746,15 @@ public async Task IncrementAsync(string key, long amount, TimeSpan? expire Interlocked.Increment(ref _writes); var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (k, entry) => { + var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (k, entry) => + { long? currentValue = null; - try { + try + { currentValue = entry.GetValue(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Unable to increment value, expected integer type"); } @@ -667,11 +774,13 @@ public async Task IncrementAsync(string key, long amount, TimeSpan? expire return result.GetValue(); } - public Task ExistsAsync(string key) { + public Task ExistsAsync(string key) + { if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); - if (!_memory.TryGetValue(key, out var cacheEntry)) { + if (!_memory.TryGetValue(key, out var cacheEntry)) + { Interlocked.Increment(ref _misses); return Task.FromResult(false); } @@ -683,11 +792,13 @@ public Task ExistsAsync(string key) { return Task.FromResult(true); } - public Task GetExpirationAsync(string key) { + public Task GetExpirationAsync(string key) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - if (!_memory.TryGetValue(key, out var value) || value.ExpiresAt == DateTime.MaxValue) { + if (!_memory.TryGetValue(key, out var value) || value.ExpiresAt == DateTime.MaxValue) + { Interlocked.Increment(ref _misses); return Task.FromResult(null); } @@ -701,18 +812,21 @@ public Task ExistsAsync(string key) { return Task.FromResult(null); } - public async Task SetExpirationAsync(string key, TimeSpan expiresIn) { + public async Task SetExpirationAsync(string key, TimeSpan expiresIn) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); var expiresAt = SystemClock.UtcNow.SafeAdd(expiresIn); - if (expiresAt < SystemClock.UtcNow) { + if (expiresAt < SystemClock.UtcNow) + { RemoveExpiredKey(key); return; } Interlocked.Increment(ref _writes); - if (_memory.TryGetValue(key, out var value)) { + if (_memory.TryGetValue(key, out var value)) + { value.ExpiresAt = expiresAt; await StartMaintenanceAsync().AnyContext(); } @@ -720,28 +834,33 @@ public async Task SetExpirationAsync(string key, TimeSpan expiresIn) { private DateTimeOffset _lastMaintenance; - private async Task StartMaintenanceAsync(bool compactImmediately = false) { + private async Task StartMaintenanceAsync(bool compactImmediately = false) + { var now = SystemClock.UtcNow; if (compactImmediately) await CompactAsync().AnyContext(); - if (TimeSpan.FromMilliseconds(100) < now - _lastMaintenance) { + if (TimeSpan.FromMilliseconds(100) < now - _lastMaintenance) + { _lastMaintenance = now; _ = Task.Run(DoMaintenanceAsync); } } - private Task CompactAsync() { + private Task CompactAsync() + { if (!_maxItems.HasValue || _memory.Count <= _maxItems) return Task.CompletedTask; string expiredKey = null; - lock (_lock) { + lock (_lock) + { if (_memory.Count <= _maxItems) return Task.CompletedTask; (string Key, long LastAccessTicks, long InstanceNumber) oldest = (null, Int64.MaxValue, 0); - foreach (var kvp in _memory) { + foreach (var kvp in _memory) + { if (kvp.Value.LastAccessTicks < oldest.LastAccessTicks || (kvp.Value.LastAccessTicks == oldest.LastAccessTicks && kvp.Value.InstanceNumber < oldest.InstanceNumber)) oldest = (kvp.Key, kvp.Value.LastAccessTicks, kvp.Value.InstanceNumber); @@ -759,30 +878,37 @@ private Task CompactAsync() { return Task.CompletedTask; } - private async Task DoMaintenanceAsync() { + private async Task DoMaintenanceAsync() + { _logger.LogTrace("DoMaintenance"); var utcNow = SystemClock.UtcNow.AddMilliseconds(50); - try { - foreach (var kvp in _memory.ToArray()) { + try + { + foreach (var kvp in _memory.ToArray()) + { var expiresAt = kvp.Value.ExpiresAt; if (expiresAt <= utcNow) RemoveExpiredKey(kvp.Key); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error trying to find expired cache items"); } await CompactAsync().AnyContext(); } - public void Dispose() { + public void Dispose() + { _memory.Clear(); ItemExpired?.Dispose(); } - private class CacheEntry { + private class CacheEntry + { private object _cacheValue; private static long _instanceCount; private readonly bool _shouldClone; @@ -790,7 +916,8 @@ private class CacheEntry { private long _usageCount; #endif - public CacheEntry(object value, DateTime expiresAt, bool shouldClone = true) { + public CacheEntry(object value, DateTime expiresAt, bool shouldClone = true) + { _shouldClone = shouldClone && TypeRequiresCloning(value?.GetType()); Value = value; ExpiresAt = expiresAt; @@ -806,22 +933,26 @@ public CacheEntry(object value, DateTime expiresAt, bool shouldClone = true) { internal long UsageCount => _usageCount; #endif - internal object Value { - get { + internal object Value + { + get + { LastAccessTicks = SystemClock.UtcNow.Ticks; #if DEBUG Interlocked.Increment(ref _usageCount); #endif return _shouldClone ? _cacheValue.DeepClone() : _cacheValue; } - set { + set + { _cacheValue = _shouldClone ? value.DeepClone() : value; LastAccessTicks = SystemClock.UtcNow.Ticks; LastModifiedTicks = SystemClock.UtcNow.Ticks; } } - public T GetValue() { + public T GetValue() + { object val = Value; var t = typeof(T); @@ -834,7 +965,8 @@ public T GetValue() { return (T)val; } - private bool TypeRequiresCloning(Type t) { + private bool TypeRequiresCloning(Type t) + { if (t == null) return true; @@ -852,9 +984,10 @@ private bool TypeRequiresCloning(Type t) { } } - public class ItemExpiredEventArgs : EventArgs { + public class ItemExpiredEventArgs : EventArgs + { public InMemoryCacheClient Client { get; set; } public string Key { get; set; } public bool SendNotification { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Caching/InMemoryCacheClientOptions.cs b/src/Foundatio/Caching/InMemoryCacheClientOptions.cs index 2211798e9..e6c7b3993 100644 --- a/src/Foundatio/Caching/InMemoryCacheClientOptions.cs +++ b/src/Foundatio/Caching/InMemoryCacheClientOptions.cs @@ -1,35 +1,41 @@ -namespace Foundatio.Caching { - public class InMemoryCacheClientOptions : SharedOptions { +namespace Foundatio.Caching +{ + public class InMemoryCacheClientOptions : SharedOptions + { /// /// The maximum number of items to store in the cache /// public int? MaxItems { get; set; } = 1000; - + /// /// Whether or not values should be cloned during get and set to make sure that any cache entry changes are isolated /// public bool CloneValues { get; set; } = false; - + /// /// Whether or not an error when deserializing a cache value should result in an exception being thrown or if it should just return an empty cache value /// public bool ShouldThrowOnSerializationError { get; set; } = true; } - public class InMemoryCacheClientOptionsBuilder : SharedOptionsBuilder { - public InMemoryCacheClientOptionsBuilder MaxItems(int? maxItems) { + public class InMemoryCacheClientOptionsBuilder : SharedOptionsBuilder + { + public InMemoryCacheClientOptionsBuilder MaxItems(int? maxItems) + { Target.MaxItems = maxItems; return this; } - public InMemoryCacheClientOptionsBuilder CloneValues(bool cloneValues) { + public InMemoryCacheClientOptionsBuilder CloneValues(bool cloneValues) + { Target.CloneValues = cloneValues; return this; } - public InMemoryCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) { + public InMemoryCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) + { Target.ShouldThrowOnSerializationError = shouldThrow; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Caching/NullCacheClient.cs b/src/Foundatio/Caching/NullCacheClient.cs index 1cfe76ab1..340dd5b20 100644 --- a/src/Foundatio/Caching/NullCacheClient.cs +++ b/src/Foundatio/Caching/NullCacheClient.cs @@ -4,164 +4,191 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Caching { - public class NullCacheClient : ICacheClient { +namespace Foundatio.Caching +{ + public class NullCacheClient : ICacheClient + { public static readonly NullCacheClient Instance = new(); private long _writes; private long _reads; - + public long Calls => _writes + _reads; public long Writes => _writes; public long Reads => _reads; - public override string ToString() { + public override string ToString() + { return $"Calls: {Calls} Reads: {Reads} Writes: {Writes}"; } - public void ResetStats() { + public void ResetStats() + { _writes = 0; _reads = 0; } - public Task RemoveAsync(string key) { + public Task RemoveAsync(string key) + { Interlocked.Increment(ref _writes); - + return Task.FromResult(false); } - public Task RemoveIfEqualAsync(string key, T expected) { + public Task RemoveIfEqualAsync(string key, T expected) + { Interlocked.Increment(ref _writes); return Task.FromResult(false); } - public Task RemoveAllAsync(IEnumerable keys = null) { + public Task RemoveAllAsync(IEnumerable keys = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(0); } - public Task RemoveByPrefixAsync(string prefix) { + public Task RemoveByPrefixAsync(string prefix) + { Interlocked.Increment(ref _writes); return Task.FromResult(0); } - public Task> GetAsync(string key) { + public Task> GetAsync(string key) + { Interlocked.Increment(ref _reads); return Task.FromResult(CacheValue.NoValue); } - public Task>> GetAllAsync(IEnumerable keys) { + public Task>> GetAllAsync(IEnumerable keys) + { Interlocked.Increment(ref _reads); return Task.FromResult>>(keys.ToDictionary(k => k, k => CacheValue.NoValue)); } - public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(true); } - public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(true); } - public Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) { + public Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(0); } - public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(true); } - public Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) { + public Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(true); } - public Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) { + public Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(amount); } - public Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) { + public Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(amount); } - public Task ExistsAsync(string key) { + public Task ExistsAsync(string key) + { Interlocked.Increment(ref _reads); - + return Task.FromResult(false); } - public Task GetExpirationAsync(string key) { + public Task GetExpirationAsync(string key) + { Interlocked.Increment(ref _reads); return Task.FromResult(null); } - public Task SetExpirationAsync(string key, TimeSpan expiresIn) { + public Task SetExpirationAsync(string key, TimeSpan expiresIn) + { Interlocked.Increment(ref _writes); return Task.FromResult(0); } - public Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) { + public Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(value); } - public Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { + public Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(value); } - public Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { + public Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(value); } - public Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { + public Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(value); } - public Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(default(long)); } - public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { Interlocked.Increment(ref _writes); return Task.FromResult(default(long)); } - public Task>> GetListAsync(string key, int? page = null, int pageSize = 100) { + public Task>> GetListAsync(string key, int? page = null, int pageSize = 100) + { Interlocked.Increment(ref _reads); return Task.FromResult(CacheValue>.NoValue); } - public void Dispose() {} + public void Dispose() { } } } diff --git a/src/Foundatio/Caching/ScopedCacheClient.cs b/src/Foundatio/Caching/ScopedCacheClient.cs index 22fe68243..fac234bce 100644 --- a/src/Foundatio/Caching/ScopedCacheClient.cs +++ b/src/Foundatio/Caching/ScopedCacheClient.cs @@ -4,17 +4,21 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Caching { - public class ScopedHybridCacheClient : ScopedCacheClient, IHybridCacheClient { - public ScopedHybridCacheClient(IHybridCacheClient client, string scope = null) : base(client, scope) {} +namespace Foundatio.Caching +{ + public class ScopedHybridCacheClient : ScopedCacheClient, IHybridCacheClient + { + public ScopedHybridCacheClient(IHybridCacheClient client, string scope = null) : base(client, scope) { } } - public class ScopedCacheClient : ICacheClient { + public class ScopedCacheClient : ICacheClient + { private string _keyPrefix; private bool _isLocked; private readonly object _lock = new(); - public ScopedCacheClient(ICacheClient client, string scope = null) { + public ScopedCacheClient(ICacheClient client, string scope = null) + { UnscopedCache = client ?? new NullCacheClient(); _isLocked = scope != null; Scope = !String.IsNullOrWhiteSpace(scope) ? scope.Trim() : null; @@ -26,11 +30,13 @@ public ScopedCacheClient(ICacheClient client, string scope = null) { public string Scope { get; private set; } - public void SetScope(string scope) { + public void SetScope(string scope) + { if (_isLocked) throw new InvalidOperationException("Scope can't be changed after it has been set"); - lock (_lock) { + lock (_lock) + { if (_isLocked) throw new InvalidOperationException("Scope can't be changed after it has been set"); @@ -40,114 +46,140 @@ public void SetScope(string scope) { } } - protected string GetUnscopedCacheKey(string key) { + protected string GetUnscopedCacheKey(string key) + { return String.Concat(_keyPrefix, key); } - protected IEnumerable GetUnscopedCacheKeys(IEnumerable keys) { + protected IEnumerable GetUnscopedCacheKeys(IEnumerable keys) + { return keys?.Select(GetUnscopedCacheKey); } - protected string GetScopedCacheKey(string unscopedKey) { + protected string GetScopedCacheKey(string unscopedKey) + { return unscopedKey?.Substring(_keyPrefix.Length); } - public Task RemoveAsync(string key) { + public Task RemoveAsync(string key) + { return UnscopedCache.RemoveAsync(GetUnscopedCacheKey(key)); } - public Task RemoveIfEqualAsync(string key, T expected) { + public Task RemoveIfEqualAsync(string key, T expected) + { return UnscopedCache.RemoveIfEqualAsync(GetUnscopedCacheKey(key), expected); } - public Task RemoveAllAsync(IEnumerable keys = null) { + public Task RemoveAllAsync(IEnumerable keys = null) + { if (keys == null) return RemoveByPrefixAsync(String.Empty); return UnscopedCache.RemoveAllAsync(GetUnscopedCacheKeys(keys)); } - public Task RemoveByPrefixAsync(string prefix) { + public Task RemoveByPrefixAsync(string prefix) + { return UnscopedCache.RemoveByPrefixAsync(GetUnscopedCacheKey(prefix)); } - public Task> GetAsync(string key) { + public Task> GetAsync(string key) + { return UnscopedCache.GetAsync(GetUnscopedCacheKey(key)); } - public async Task>> GetAllAsync(IEnumerable keys) { + public async Task>> GetAllAsync(IEnumerable keys) + { var scopedDictionary = await UnscopedCache.GetAllAsync(GetUnscopedCacheKeys(keys)).AnyContext(); return scopedDictionary.ToDictionary(kvp => GetScopedCacheKey(kvp.Key), kvp => kvp.Value); } - public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) + { return UnscopedCache.AddAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) + { return UnscopedCache.SetAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) { + public Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) + { return UnscopedCache.SetAllAsync(values?.ToDictionary(kvp => GetUnscopedCacheKey(kvp.Key), kvp => kvp.Value), expiresIn); } - public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) + { return UnscopedCache.ReplaceAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) { + public Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) + { return UnscopedCache.ReplaceIfEqualAsync(GetUnscopedCacheKey(key), value, expected, expiresIn); } - public Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) { + public Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null) + { return UnscopedCache.IncrementAsync(GetUnscopedCacheKey(key), amount, expiresIn); } - public Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) { + public Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null) + { return UnscopedCache.IncrementAsync(GetUnscopedCacheKey(key), amount, expiresIn); } - - public Task ExistsAsync(string key) { + + public Task ExistsAsync(string key) + { return UnscopedCache.ExistsAsync(GetUnscopedCacheKey(key)); } - public Task GetExpirationAsync(string key) { + public Task GetExpirationAsync(string key) + { return UnscopedCache.GetExpirationAsync(GetUnscopedCacheKey(key)); } - public Task SetExpirationAsync(string key, TimeSpan expiresIn) { + public Task SetExpirationAsync(string key, TimeSpan expiresIn) + { return UnscopedCache.SetExpirationAsync(GetUnscopedCacheKey(key), expiresIn); } - public Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) { + public Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) + { return UnscopedCache.SetIfHigherAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { + public Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) + { return UnscopedCache.SetIfHigherAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { + public Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) + { return UnscopedCache.SetIfLowerAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { + public Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) + { return UnscopedCache.SetIfLowerAsync(GetUnscopedCacheKey(key), value, expiresIn); } - public Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { return UnscopedCache.ListAddAsync(GetUnscopedCacheKey(key), values, expiresIn); } - public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { return UnscopedCache.ListRemoveAsync(GetUnscopedCacheKey(key), values, expiresIn); } - public Task>> GetListAsync(string key, int? page = null, int pageSize = 100) { + public Task>> GetListAsync(string key, int? page = null, int pageSize = 100) + { return UnscopedCache.GetListAsync(GetUnscopedCacheKey(key), page, pageSize); } - public void Dispose() {} + public void Dispose() { } } } diff --git a/src/Foundatio/DeepCloner/DeepClonerExtensions.cs b/src/Foundatio/DeepCloner/DeepClonerExtensions.cs index 40249e061..06237fbca 100644 --- a/src/Foundatio/DeepCloner/DeepClonerExtensions.cs +++ b/src/Foundatio/DeepCloner/DeepClonerExtensions.cs @@ -5,73 +5,73 @@ namespace Foundatio.Force.DeepCloner { - /// - /// Extensions for object cloning - /// - internal static class DeepClonerExtensions - { - /// - /// Performs deep (full) copy of object and related graph - /// - public static T DeepClone(this T obj) - { - return DeepClonerGenerator.CloneObject(obj); - } + /// + /// Extensions for object cloning + /// + internal static class DeepClonerExtensions + { + /// + /// Performs deep (full) copy of object and related graph + /// + public static T DeepClone(this T obj) + { + return DeepClonerGenerator.CloneObject(obj); + } - /// - /// Performs deep (full) copy of object and related graph to existing object - /// - /// existing filled object - /// Method is valid only for classes, classes should be descendants in reality, not in declaration - public static TTo DeepCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom - { - return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, true); - } + /// + /// Performs deep (full) copy of object and related graph to existing object + /// + /// existing filled object + /// Method is valid only for classes, classes should be descendants in reality, not in declaration + public static TTo DeepCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom + { + return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, true); + } - /// - /// Performs shallow copy of object to existing object - /// - /// existing filled object - /// Method is valid only for classes, classes should be descendants in reality, not in declaration - public static TTo ShallowCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom - { - return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, false); - } + /// + /// Performs shallow copy of object to existing object + /// + /// existing filled object + /// Method is valid only for classes, classes should be descendants in reality, not in declaration + public static TTo ShallowCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom + { + return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, false); + } - /// - /// Performs shallow (only new object returned, without cloning of dependencies) copy of object - /// - public static T ShallowClone(this T obj) - { - return ShallowClonerGenerator.CloneObject(obj); - } + /// + /// Performs shallow (only new object returned, without cloning of dependencies) copy of object + /// + public static T ShallowClone(this T obj) + { + return ShallowClonerGenerator.CloneObject(obj); + } - static DeepClonerExtensions() - { - if (!PermissionCheck()) - { - throw new SecurityException("DeepCloner should have enough permissions to run. Grant FullTrust or Reflection permission"); - } - } + static DeepClonerExtensions() + { + if (!PermissionCheck()) + { + throw new SecurityException("DeepCloner should have enough permissions to run. Grant FullTrust or Reflection permission"); + } + } - private static bool PermissionCheck() - { - // best way to check required permission: execute something and receive exception - // .net security policy is weird for normal usage - try - { - new object().ShallowClone(); - } - catch (VerificationException) - { - return false; - } - catch (MemberAccessException) - { - return false; - } - - return true; - } - } + private static bool PermissionCheck() + { + // best way to check required permission: execute something and receive exception + // .net security policy is weird for normal usage + try + { + new object().ShallowClone(); + } + catch (VerificationException) + { + return false; + } + catch (MemberAccessException) + { + return false; + } + + return true; + } + } } diff --git a/src/Foundatio/DeepCloner/Helpers/ClonerToExprGenerator.cs b/src/Foundatio/DeepCloner/Helpers/ClonerToExprGenerator.cs index 017c390a4..c46572ec2 100644 --- a/src/Foundatio/DeepCloner/Helpers/ClonerToExprGenerator.cs +++ b/src/Foundatio/DeepCloner/Helpers/ClonerToExprGenerator.cs @@ -8,287 +8,287 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class ClonerToExprGenerator - { - internal static object GenerateClonerInternal(Type realType, bool isDeepClone) - { - if (realType.IsValueType()) - throw new InvalidOperationException("Operation is valid only for reference types"); - return GenerateProcessMethod(realType, isDeepClone); - } - - private static object GenerateProcessMethod(Type type, bool isDeepClone) - { - if (type.IsArray) - { - return GenerateProcessArrayMethod(type, isDeepClone); - } - - var methodType = typeof(object); - - var expressionList = new List(); - - ParameterExpression from = Expression.Parameter(methodType); - var fromLocal = from; - var to = Expression.Parameter(methodType); - var toLocal = to; - var state = Expression.Parameter(typeof(DeepCloneState)); - - // if (!type.IsValueType()) - { - fromLocal = Expression.Variable(type); - toLocal = Expression.Variable(type); - // fromLocal = (T)from - expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); - expressionList.Add(Expression.Assign(toLocal, Expression.Convert(to, type))); - - if (isDeepClone) - { - // added from -> to binding to ensure reference loop handling - // structs cannot loop here - // state.AddKnownRef(from, to) - expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, to)); - } - } - - List fi = new List(); - var tp = type; - do - { + internal static class ClonerToExprGenerator + { + internal static object GenerateClonerInternal(Type realType, bool isDeepClone) + { + if (realType.IsValueType()) + throw new InvalidOperationException("Operation is valid only for reference types"); + return GenerateProcessMethod(realType, isDeepClone); + } + + private static object GenerateProcessMethod(Type type, bool isDeepClone) + { + if (type.IsArray) + { + return GenerateProcessArrayMethod(type, isDeepClone); + } + + var methodType = typeof(object); + + var expressionList = new List(); + + ParameterExpression from = Expression.Parameter(methodType); + var fromLocal = from; + var to = Expression.Parameter(methodType); + var toLocal = to; + var state = Expression.Parameter(typeof(DeepCloneState)); + + // if (!type.IsValueType()) + { + fromLocal = Expression.Variable(type); + toLocal = Expression.Variable(type); + // fromLocal = (T)from + expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); + expressionList.Add(Expression.Assign(toLocal, Expression.Convert(to, type))); + + if (isDeepClone) + { + // added from -> to binding to ensure reference loop handling + // structs cannot loop here + // state.AddKnownRef(from, to) + expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, to)); + } + } + + List fi = new List(); + var tp = type; + do + { #if !NETCORE // don't do anything with this dark magic! if (tp == typeof(ContextBoundObject)) break; #else - if (tp.Name == "ContextBoundObject") break; + if (tp.Name == "ContextBoundObject") break; #endif - fi.AddRange(tp.GetDeclaredFields()); - tp = tp.BaseType(); - } - while (tp != null); - - foreach (var fieldInfo in fi) - { - if (isDeepClone && !DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) - { - var methodInfo = fieldInfo.FieldType.IsValueType() - ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") - .MakeGenericMethod(fieldInfo.FieldType) - : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); - - var get = Expression.Field(fromLocal, fieldInfo); - - // toLocal.Field = Clone...Internal(fromLocal.Field) - var call = (Expression) Expression.Call(methodInfo, get, state); - if (!fieldInfo.FieldType.IsValueType()) - call = Expression.Convert(call, fieldInfo.FieldType); - - // should handle specially - // todo: think about optimization, but it rare case - if (fieldInfo.IsInitOnly) - { - // var setMethod = fieldInfo.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) }); - // expressionList.Add(Expression.Call(Expression.Constant(fieldInfo), setMethod, toLocal, call)); - var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); - expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), - Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); - } - else - { - expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); - } - } - else - { - expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), Expression.Field(fromLocal, fieldInfo))); - } - } - - expressionList.Add(Expression.Convert(toLocal, methodType)); - - var funcType = typeof(Func<,,,>).MakeGenericType(methodType, methodType, typeof(DeepCloneState), methodType); - - var blockParams = new List(); - if (from != fromLocal) blockParams.Add(fromLocal); - if (to != toLocal) blockParams.Add(toLocal); - - return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, to, state).Compile(); - } - - private static object GenerateProcessArrayMethod(Type type, bool isDeep) - { - var elementType = type.GetElementType(); - var rank = type.GetArrayRank(); - - ParameterExpression from = Expression.Parameter(typeof(object)); - ParameterExpression to = Expression.Parameter(typeof(object)); - var state = Expression.Parameter(typeof(DeepCloneState)); - - var funcType = typeof(Func<,,,>).MakeGenericType(typeof(object), typeof(object), typeof(DeepCloneState), typeof(object)); - - if (rank == 1 && type == elementType.MakeArrayType()) - { - if (!isDeep) - { - var callS = Expression.Call( - typeof(ClonerToExprGenerator).GetPrivateStaticMethod("ShallowClone1DimArraySafeInternal") - .MakeGenericMethod(elementType), Expression.Convert(from, type), Expression.Convert(to, type)); - return Expression.Lambda(funcType, callS, from, to, state).Compile(); - } - else - { - var methodName = "Clone1DimArrayClassInternal"; - if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; - else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; - var methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); - var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state); - return Expression.Lambda(funcType, callS, from, to, state).Compile(); - } - } - else - { - // multidim or not zero-based arrays - MethodInfo methodInfo; - if (rank == 2 && type == elementType.MakeArrayType(2)) - methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType); - else - methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal"); - - var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state, Expression.Constant(isDeep)); - return Expression.Lambda(funcType, callS, from, to, state).Compile(); - } - } - - // when we can't use code generation, we can use these methods - internal static T[] ShallowClone1DimArraySafeInternal(T[] objFrom, T[] objTo) - { - var l = Math.Min(objFrom.Length, objTo.Length); - Array.Copy(objFrom, objTo, l); - return objTo; - } - - // when we can't use code generation, we can use these methods - internal static T[] Clone1DimArraySafeInternal(T[] objFrom, T[] objTo, DeepCloneState state) - { - var l = Math.Min(objFrom.Length, objTo.Length); - state.AddKnownRef(objFrom, objTo); - Array.Copy(objFrom, objTo, l); - return objTo; - } - - internal static T[] Clone1DimArrayStructInternal(T[] objFrom, T[] objTo, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (objFrom == null || objTo == null) return null; - var l = Math.Min(objFrom.Length, objTo.Length); - state.AddKnownRef(objFrom, objTo); - var cloner = DeepClonerGenerator.GetClonerForValueType(); - for (var i = 0; i < l; i++) - objTo[i] = cloner(objTo[i], state); - - return objTo; - } - - internal static T[] Clone1DimArrayClassInternal(T[] objFrom, T[] objTo, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (objFrom == null || objTo == null) return null; - var l = Math.Min(objFrom.Length, objTo.Length); - state.AddKnownRef(objFrom, objTo); - for (var i = 0; i < l; i++) - objTo[i] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i], state); - - return objTo; - } - - internal static T[,] Clone2DimArrayInternal(T[,] objFrom, T[,] objTo, DeepCloneState state, bool isDeep) - { - // not null from called method, but will check it anyway - if (objFrom == null || objTo == null) return null; - if (objFrom.GetLowerBound(0) != 0 || objFrom.GetLowerBound(1) != 0 - || objTo.GetLowerBound(0) != 0 || objTo.GetLowerBound(1) != 0) - return (T[,]) CloneAbstractArrayInternal(objFrom, objTo, state, isDeep); - - var l1 = Math.Min(objFrom.GetLength(0), objTo.GetLength(0)); - var l2 = Math.Min(objFrom.GetLength(1), objTo.GetLength(1)); - state.AddKnownRef(objFrom, objTo); - if ((!isDeep || DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) - && objFrom.GetLength(0) == objTo.GetLength(0) - && objFrom.GetLength(1) == objTo.GetLength(1)) - { - Array.Copy(objFrom, objTo, objFrom.Length); - return objTo; - } - - if (!isDeep) - { - for (var i = 0; i < l1; i++) - for (var k = 0; k < l2; k++) - objTo[i, k] = objFrom[i, k]; - return objTo; - } - - if (typeof(T).IsValueType()) - { - var cloner = DeepClonerGenerator.GetClonerForValueType(); - for (var i = 0; i < l1; i++) - for (var k = 0; k < l2; k++) - objTo[i, k] = cloner(objFrom[i, k], state); - } - else - { - for (var i = 0; i < l1; i++) - for (var k = 0; k < l2; k++) - objTo[i, k] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i, k], state); - } - - return objTo; - } - - // rare cases, very slow cloning. currently it's ok - internal static Array CloneAbstractArrayInternal(Array objFrom, Array objTo, DeepCloneState state, bool isDeep) - { - // not null from called method, but will check it anyway - if (objFrom == null || objTo == null) return null; - var rank = objFrom.Rank; - - if (objTo.Rank != rank) - throw new InvalidOperationException("Invalid rank of target array"); - var lowerBoundsFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); - var lowerBoundsTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); - var lengths = Enumerable.Range(0, rank).Select(x => Math.Min(objFrom.GetLength(x), objTo.GetLength(x))).ToArray(); - var idxesFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); - var idxesTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); - - state.AddKnownRef(objFrom, objTo); - - // unable to copy any element - if (lengths.Any(x => x == 0)) - return objTo; - - while (true) - { - if (isDeep) - objTo.SetValue(DeepClonerGenerator.CloneClassInternal(objFrom.GetValue(idxesFrom), state), idxesTo); - else - objTo.SetValue(objFrom.GetValue(idxesFrom), idxesTo); - var ofs = rank - 1; - while (true) - { - idxesFrom[ofs]++; - idxesTo[ofs]++; - if (idxesFrom[ofs] >= lowerBoundsFrom[ofs] + lengths[ofs]) - { - idxesFrom[ofs] = lowerBoundsFrom[ofs]; - idxesTo[ofs] = lowerBoundsTo[ofs]; - ofs--; - if (ofs < 0) return objTo; - } - else - break; - } - } - } - - } -} \ No newline at end of file + fi.AddRange(tp.GetDeclaredFields()); + tp = tp.BaseType(); + } + while (tp != null); + + foreach (var fieldInfo in fi) + { + if (isDeepClone && !DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) + { + var methodInfo = fieldInfo.FieldType.IsValueType() + ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") + .MakeGenericMethod(fieldInfo.FieldType) + : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); + + var get = Expression.Field(fromLocal, fieldInfo); + + // toLocal.Field = Clone...Internal(fromLocal.Field) + var call = (Expression)Expression.Call(methodInfo, get, state); + if (!fieldInfo.FieldType.IsValueType()) + call = Expression.Convert(call, fieldInfo.FieldType); + + // should handle specially + // todo: think about optimization, but it rare case + if (fieldInfo.IsInitOnly) + { + // var setMethod = fieldInfo.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) }); + // expressionList.Add(Expression.Call(Expression.Constant(fieldInfo), setMethod, toLocal, call)); + var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); + expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), + Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); + } + else + { + expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); + } + } + else + { + expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), Expression.Field(fromLocal, fieldInfo))); + } + } + + expressionList.Add(Expression.Convert(toLocal, methodType)); + + var funcType = typeof(Func<,,,>).MakeGenericType(methodType, methodType, typeof(DeepCloneState), methodType); + + var blockParams = new List(); + if (from != fromLocal) blockParams.Add(fromLocal); + if (to != toLocal) blockParams.Add(toLocal); + + return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, to, state).Compile(); + } + + private static object GenerateProcessArrayMethod(Type type, bool isDeep) + { + var elementType = type.GetElementType(); + var rank = type.GetArrayRank(); + + ParameterExpression from = Expression.Parameter(typeof(object)); + ParameterExpression to = Expression.Parameter(typeof(object)); + var state = Expression.Parameter(typeof(DeepCloneState)); + + var funcType = typeof(Func<,,,>).MakeGenericType(typeof(object), typeof(object), typeof(DeepCloneState), typeof(object)); + + if (rank == 1 && type == elementType.MakeArrayType()) + { + if (!isDeep) + { + var callS = Expression.Call( + typeof(ClonerToExprGenerator).GetPrivateStaticMethod("ShallowClone1DimArraySafeInternal") + .MakeGenericMethod(elementType), Expression.Convert(from, type), Expression.Convert(to, type)); + return Expression.Lambda(funcType, callS, from, to, state).Compile(); + } + else + { + var methodName = "Clone1DimArrayClassInternal"; + if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; + else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; + var methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); + var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state); + return Expression.Lambda(funcType, callS, from, to, state).Compile(); + } + } + else + { + // multidim or not zero-based arrays + MethodInfo methodInfo; + if (rank == 2 && type == elementType.MakeArrayType(2)) + methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType); + else + methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal"); + + var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state, Expression.Constant(isDeep)); + return Expression.Lambda(funcType, callS, from, to, state).Compile(); + } + } + + // when we can't use code generation, we can use these methods + internal static T[] ShallowClone1DimArraySafeInternal(T[] objFrom, T[] objTo) + { + var l = Math.Min(objFrom.Length, objTo.Length); + Array.Copy(objFrom, objTo, l); + return objTo; + } + + // when we can't use code generation, we can use these methods + internal static T[] Clone1DimArraySafeInternal(T[] objFrom, T[] objTo, DeepCloneState state) + { + var l = Math.Min(objFrom.Length, objTo.Length); + state.AddKnownRef(objFrom, objTo); + Array.Copy(objFrom, objTo, l); + return objTo; + } + + internal static T[] Clone1DimArrayStructInternal(T[] objFrom, T[] objTo, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (objFrom == null || objTo == null) return null; + var l = Math.Min(objFrom.Length, objTo.Length); + state.AddKnownRef(objFrom, objTo); + var cloner = DeepClonerGenerator.GetClonerForValueType(); + for (var i = 0; i < l; i++) + objTo[i] = cloner(objTo[i], state); + + return objTo; + } + + internal static T[] Clone1DimArrayClassInternal(T[] objFrom, T[] objTo, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (objFrom == null || objTo == null) return null; + var l = Math.Min(objFrom.Length, objTo.Length); + state.AddKnownRef(objFrom, objTo); + for (var i = 0; i < l; i++) + objTo[i] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i], state); + + return objTo; + } + + internal static T[,] Clone2DimArrayInternal(T[,] objFrom, T[,] objTo, DeepCloneState state, bool isDeep) + { + // not null from called method, but will check it anyway + if (objFrom == null || objTo == null) return null; + if (objFrom.GetLowerBound(0) != 0 || objFrom.GetLowerBound(1) != 0 + || objTo.GetLowerBound(0) != 0 || objTo.GetLowerBound(1) != 0) + return (T[,])CloneAbstractArrayInternal(objFrom, objTo, state, isDeep); + + var l1 = Math.Min(objFrom.GetLength(0), objTo.GetLength(0)); + var l2 = Math.Min(objFrom.GetLength(1), objTo.GetLength(1)); + state.AddKnownRef(objFrom, objTo); + if ((!isDeep || DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) + && objFrom.GetLength(0) == objTo.GetLength(0) + && objFrom.GetLength(1) == objTo.GetLength(1)) + { + Array.Copy(objFrom, objTo, objFrom.Length); + return objTo; + } + + if (!isDeep) + { + for (var i = 0; i < l1; i++) + for (var k = 0; k < l2; k++) + objTo[i, k] = objFrom[i, k]; + return objTo; + } + + if (typeof(T).IsValueType()) + { + var cloner = DeepClonerGenerator.GetClonerForValueType(); + for (var i = 0; i < l1; i++) + for (var k = 0; k < l2; k++) + objTo[i, k] = cloner(objFrom[i, k], state); + } + else + { + for (var i = 0; i < l1; i++) + for (var k = 0; k < l2; k++) + objTo[i, k] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i, k], state); + } + + return objTo; + } + + // rare cases, very slow cloning. currently it's ok + internal static Array CloneAbstractArrayInternal(Array objFrom, Array objTo, DeepCloneState state, bool isDeep) + { + // not null from called method, but will check it anyway + if (objFrom == null || objTo == null) return null; + var rank = objFrom.Rank; + + if (objTo.Rank != rank) + throw new InvalidOperationException("Invalid rank of target array"); + var lowerBoundsFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); + var lowerBoundsTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); + var lengths = Enumerable.Range(0, rank).Select(x => Math.Min(objFrom.GetLength(x), objTo.GetLength(x))).ToArray(); + var idxesFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); + var idxesTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); + + state.AddKnownRef(objFrom, objTo); + + // unable to copy any element + if (lengths.Any(x => x == 0)) + return objTo; + + while (true) + { + if (isDeep) + objTo.SetValue(DeepClonerGenerator.CloneClassInternal(objFrom.GetValue(idxesFrom), state), idxesTo); + else + objTo.SetValue(objFrom.GetValue(idxesFrom), idxesTo); + var ofs = rank - 1; + while (true) + { + idxesFrom[ofs]++; + idxesTo[ofs]++; + if (idxesFrom[ofs] >= lowerBoundsFrom[ofs] + lengths[ofs]) + { + idxesFrom[ofs] = lowerBoundsFrom[ofs]; + idxesTo[ofs] = lowerBoundsTo[ofs]; + ofs--; + if (ofs < 0) return objTo; + } + else + break; + } + } + } + + } +} diff --git a/src/Foundatio/DeepCloner/Helpers/DeepCloneState.cs b/src/Foundatio/DeepCloner/Helpers/DeepCloneState.cs index 16a4628f3..2d82c23ba 100644 --- a/src/Foundatio/DeepCloner/Helpers/DeepCloneState.cs +++ b/src/Foundatio/DeepCloner/Helpers/DeepCloneState.cs @@ -5,160 +5,160 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal class DeepCloneState - { - private MiniDictionary _loops; - - private readonly object[] _baseFromTo = new object[6]; - - private int _idx; - - public object GetKnownRef(object from) - { - // this is faster than call Dictionary from begin - // also, small poco objects does not have a lot of references - var baseFromTo = _baseFromTo; - if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3]; - if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4]; - if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5]; - if (_loops == null) - return null; - - return _loops.FindEntry(from); - } - - public void AddKnownRef(object from, object to) - { - if (_idx < 3) - { - _baseFromTo[_idx] = from; - _baseFromTo[_idx + 3] = to; - _idx++; - return; - } - - if (_loops == null) - _loops = new MiniDictionary(); - _loops.Insert(from, to); - } - - private class MiniDictionary - { - private struct Entry - { - public int HashCode; - public int Next; - public object Key; - public object Value; - } - - private int[] _buckets; - private Entry[] _entries; - private int _count; - - - public MiniDictionary() : this(5) - { - } - - public MiniDictionary(int capacity) - { - if (capacity > 0) - Initialize(capacity); - } - - public object FindEntry(object key) - { - if (_buckets != null) - { - var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; - var entries1 = _entries; - for (var i = _buckets[hashCode % _buckets.Length]; i >= 0; i = entries1[i].Next) - { - if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key)) - return entries1[i].Value; - } - } - - return null; - } - - private static readonly int[] _primes = - { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 - }; - - private static int GetPrime(int min) - { - for (var i = 0; i < _primes.Length; i++) - { - var prime = _primes[i]; - if (prime >= min) return prime; - } - - //outside of our predefined table. - //compute the hard way. - for (var i = min | 1; i < int.MaxValue; i += 2) - { - if (IsPrime(i) && (i - 1) % 101 != 0) - return i; - } - - return min; - } - - private static bool IsPrime(int candidate) - { - if ((candidate & 1) != 0) - { - var limit = (int)Math.Sqrt(candidate); - for (var divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - - return true; - } - - return candidate == 2; - } - - private static int ExpandPrime(int oldSize) - { - var newSize = 2 * oldSize; - - if ((uint)newSize > 0x7FEFFFFD && 0x7FEFFFFD > oldSize) - { - return 0x7FEFFFFD; - } - - return GetPrime(newSize); - } - - private void Initialize(int size) - { - _buckets = new int[size]; - for (int i = 0; i < _buckets.Length; i++) - _buckets[i] = -1; - _entries = new Entry[size]; - } - - public void Insert(object key, object value) - { - if (_buckets == null) Initialize(0); - var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; - var targetBucket = hashCode % _buckets.Length; - - var entries1 = _entries; - - // we're always checking for entry before adding new - // so this loop is useless - /*for (var i = _buckets[targetBucket]; i >= 0; i = entries1[i].Next) + internal class DeepCloneState + { + private MiniDictionary _loops; + + private readonly object[] _baseFromTo = new object[6]; + + private int _idx; + + public object GetKnownRef(object from) + { + // this is faster than call Dictionary from begin + // also, small poco objects does not have a lot of references + var baseFromTo = _baseFromTo; + if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3]; + if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4]; + if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5]; + if (_loops == null) + return null; + + return _loops.FindEntry(from); + } + + public void AddKnownRef(object from, object to) + { + if (_idx < 3) + { + _baseFromTo[_idx] = from; + _baseFromTo[_idx + 3] = to; + _idx++; + return; + } + + if (_loops == null) + _loops = new MiniDictionary(); + _loops.Insert(from, to); + } + + private class MiniDictionary + { + private struct Entry + { + public int HashCode; + public int Next; + public object Key; + public object Value; + } + + private int[] _buckets; + private Entry[] _entries; + private int _count; + + + public MiniDictionary() : this(5) + { + } + + public MiniDictionary(int capacity) + { + if (capacity > 0) + Initialize(capacity); + } + + public object FindEntry(object key) + { + if (_buckets != null) + { + var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; + var entries1 = _entries; + for (var i = _buckets[hashCode % _buckets.Length]; i >= 0; i = entries1[i].Next) + { + if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key)) + return entries1[i].Value; + } + } + + return null; + } + + private static readonly int[] _primes = + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + private static int GetPrime(int min) + { + for (var i = 0; i < _primes.Length; i++) + { + var prime = _primes[i]; + if (prime >= min) return prime; + } + + //outside of our predefined table. + //compute the hard way. + for (var i = min | 1; i < int.MaxValue; i += 2) + { + if (IsPrime(i) && (i - 1) % 101 != 0) + return i; + } + + return min; + } + + private static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + var limit = (int)Math.Sqrt(candidate); + for (var divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + + return true; + } + + return candidate == 2; + } + + private static int ExpandPrime(int oldSize) + { + var newSize = 2 * oldSize; + + if ((uint)newSize > 0x7FEFFFFD && 0x7FEFFFFD > oldSize) + { + return 0x7FEFFFFD; + } + + return GetPrime(newSize); + } + + private void Initialize(int size) + { + _buckets = new int[size]; + for (int i = 0; i < _buckets.Length; i++) + _buckets[i] = -1; + _entries = new Entry[size]; + } + + public void Insert(object key, object value) + { + if (_buckets == null) Initialize(0); + var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; + var targetBucket = hashCode % _buckets.Length; + + var entries1 = _entries; + + // we're always checking for entry before adding new + // so this loop is useless + /*for (var i = _buckets[targetBucket]; i >= 0; i = entries1[i].Next) { if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key)) { @@ -167,49 +167,49 @@ public void Insert(object key, object value) } }*/ - if (_count == entries1.Length) - { - Resize(); - entries1 = _entries; - targetBucket = hashCode % _buckets.Length; - } - - var index = _count; - _count++; - - entries1[index].HashCode = hashCode; - entries1[index].Next = _buckets[targetBucket]; - entries1[index].Key = key; - entries1[index].Value = value; - _buckets[targetBucket] = index; - } - - private void Resize() - { - Resize(ExpandPrime(_count)); - } - - private void Resize(int newSize) - { - var newBuckets = new int[newSize]; - for (int i = 0; i < newBuckets.Length; i++) - newBuckets[i] = -1; - var newEntries = new Entry[newSize]; - Array.Copy(_entries, 0, newEntries, 0, _count); - - for (var i = 0; i < _count; i++) - { - if (newEntries[i].HashCode >= 0) - { - var bucket = newEntries[i].HashCode % newSize; - newEntries[i].Next = newBuckets[bucket]; - newBuckets[bucket] = i; - } - } - - _buckets = newBuckets; - _entries = newEntries; - } - } - } -} \ No newline at end of file + if (_count == entries1.Length) + { + Resize(); + entries1 = _entries; + targetBucket = hashCode % _buckets.Length; + } + + var index = _count; + _count++; + + entries1[index].HashCode = hashCode; + entries1[index].Next = _buckets[targetBucket]; + entries1[index].Key = key; + entries1[index].Value = value; + _buckets[targetBucket] = index; + } + + private void Resize() + { + Resize(ExpandPrime(_count)); + } + + private void Resize(int newSize) + { + var newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) + newBuckets[i] = -1; + var newEntries = new Entry[newSize]; + Array.Copy(_entries, 0, newEntries, 0, _count); + + for (var i = 0; i < _count; i++) + { + if (newEntries[i].HashCode >= 0) + { + var bucket = newEntries[i].HashCode % newSize; + newEntries[i].Next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + + _buckets = newBuckets; + _entries = newEntries; + } + } + } +} diff --git a/src/Foundatio/DeepCloner/Helpers/DeepClonerCache.cs b/src/Foundatio/DeepCloner/Helpers/DeepClonerCache.cs index a8c94070a..4c8d1781f 100644 --- a/src/Foundatio/DeepCloner/Helpers/DeepClonerCache.cs +++ b/src/Foundatio/DeepCloner/Helpers/DeepClonerCache.cs @@ -3,95 +3,95 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class DeepClonerCache - { - private static readonly ConcurrentDictionary _typeCache = new(); - - private static readonly ConcurrentDictionary _typeCacheDeepTo = new(); - - private static readonly ConcurrentDictionary _typeCacheShallowTo = new(); - - private static readonly ConcurrentDictionary _structAsObjectCache = new(); - - private static readonly ConcurrentDictionary, object> _typeConvertCache = new(); - - public static object GetOrAddClass(Type type, Func adder) - { - // return _typeCache.GetOrAdd(type, x => adder(x)); - - // this implementation is slightly faster than getoradd - object value; - if (_typeCache.TryGetValue(type, out value)) return value; - - // will lock by type object to ensure only one type generator is generated simultaneously - lock (type) - { - value = _typeCache.GetOrAdd(type, t => adder(t)); - } - - return value; - } - - public static object GetOrAddDeepClassTo(Type type, Func adder) - { - object value; - if (_typeCacheDeepTo.TryGetValue(type, out value)) return value; - - // will lock by type object to ensure only one type generator is generated simultaneously - lock (type) - { - value = _typeCacheDeepTo.GetOrAdd(type, t => adder(t)); - } - - return value; - } - - public static object GetOrAddShallowClassTo(Type type, Func adder) - { - object value; - if (_typeCacheShallowTo.TryGetValue(type, out value)) return value; - - // will lock by type object to ensure only one type generator is generated simultaneously - lock (type) - { - value = _typeCacheShallowTo.GetOrAdd(type, t => adder(t)); - } - - return value; - } - - public static object GetOrAddStructAsObject(Type type, Func adder) - { - // return _typeCache.GetOrAdd(type, x => adder(x)); - - // this implementation is slightly faster than getoradd - object value; - if (_structAsObjectCache.TryGetValue(type, out value)) return value; - - // will lock by type object to ensure only one type generator is generated simultaneously - lock (type) - { - value = _structAsObjectCache.GetOrAdd(type, t => adder(t)); - } - - return value; - } - - public static T GetOrAddConvertor(Type from, Type to, Func adder) - { - return (T)_typeConvertCache.GetOrAdd(new Tuple(from, to), (tuple) => adder(tuple.Item1, tuple.Item2)); - } - - /// - /// This method can be used when we switch between safe / unsafe variants (for testing) - /// - public static void ClearCache() - { - _typeCache.Clear(); - _typeCacheDeepTo.Clear(); - _typeCacheShallowTo.Clear(); - _structAsObjectCache.Clear(); - _typeConvertCache.Clear(); - } - } + internal static class DeepClonerCache + { + private static readonly ConcurrentDictionary _typeCache = new(); + + private static readonly ConcurrentDictionary _typeCacheDeepTo = new(); + + private static readonly ConcurrentDictionary _typeCacheShallowTo = new(); + + private static readonly ConcurrentDictionary _structAsObjectCache = new(); + + private static readonly ConcurrentDictionary, object> _typeConvertCache = new(); + + public static object GetOrAddClass(Type type, Func adder) + { + // return _typeCache.GetOrAdd(type, x => adder(x)); + + // this implementation is slightly faster than getoradd + object value; + if (_typeCache.TryGetValue(type, out value)) return value; + + // will lock by type object to ensure only one type generator is generated simultaneously + lock (type) + { + value = _typeCache.GetOrAdd(type, t => adder(t)); + } + + return value; + } + + public static object GetOrAddDeepClassTo(Type type, Func adder) + { + object value; + if (_typeCacheDeepTo.TryGetValue(type, out value)) return value; + + // will lock by type object to ensure only one type generator is generated simultaneously + lock (type) + { + value = _typeCacheDeepTo.GetOrAdd(type, t => adder(t)); + } + + return value; + } + + public static object GetOrAddShallowClassTo(Type type, Func adder) + { + object value; + if (_typeCacheShallowTo.TryGetValue(type, out value)) return value; + + // will lock by type object to ensure only one type generator is generated simultaneously + lock (type) + { + value = _typeCacheShallowTo.GetOrAdd(type, t => adder(t)); + } + + return value; + } + + public static object GetOrAddStructAsObject(Type type, Func adder) + { + // return _typeCache.GetOrAdd(type, x => adder(x)); + + // this implementation is slightly faster than getoradd + object value; + if (_structAsObjectCache.TryGetValue(type, out value)) return value; + + // will lock by type object to ensure only one type generator is generated simultaneously + lock (type) + { + value = _structAsObjectCache.GetOrAdd(type, t => adder(t)); + } + + return value; + } + + public static T GetOrAddConvertor(Type from, Type to, Func adder) + { + return (T)_typeConvertCache.GetOrAdd(new Tuple(from, to), (tuple) => adder(tuple.Item1, tuple.Item2)); + } + + /// + /// This method can be used when we switch between safe / unsafe variants (for testing) + /// + public static void ClearCache() + { + _typeCache.Clear(); + _typeCacheDeepTo.Clear(); + _typeCacheShallowTo.Clear(); + _structAsObjectCache.Clear(); + _typeConvertCache.Clear(); + } + } } diff --git a/src/Foundatio/DeepCloner/Helpers/DeepClonerExprGenerator.cs b/src/Foundatio/DeepCloner/Helpers/DeepClonerExprGenerator.cs index ac056fb1f..80230624b 100644 --- a/src/Foundatio/DeepCloner/Helpers/DeepClonerExprGenerator.cs +++ b/src/Foundatio/DeepCloner/Helpers/DeepClonerExprGenerator.cs @@ -8,257 +8,257 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class DeepClonerExprGenerator - { - private static readonly ConcurrentDictionary _readonlyFields = new ConcurrentDictionary(); - - private static readonly bool _canFastCopyReadonlyFields = false; - - private static readonly MethodInfo _fieldSetMethod; - static DeepClonerExprGenerator() - { - try - { - typeof(DeepClonerExprGenerator).GetPrivateStaticField(nameof(_canFastCopyReadonlyFields)).SetValue(null, true); + internal static class DeepClonerExprGenerator + { + private static readonly ConcurrentDictionary _readonlyFields = new ConcurrentDictionary(); + + private static readonly bool _canFastCopyReadonlyFields = false; + + private static readonly MethodInfo _fieldSetMethod; + static DeepClonerExprGenerator() + { + try + { + typeof(DeepClonerExprGenerator).GetPrivateStaticField(nameof(_canFastCopyReadonlyFields)).SetValue(null, true); #if NETCORE13 _fieldSetMethod = typeof(FieldInfo).GetRuntimeMethod("SetValue", new[] { typeof(object), typeof(object) }); #else - _fieldSetMethod = typeof(FieldInfo).GetMethod("SetValue", new[] {typeof(object), typeof(object)}); + _fieldSetMethod = typeof(FieldInfo).GetMethod("SetValue", new[] { typeof(object), typeof(object) }); #endif - - if (_fieldSetMethod == null) - throw new ArgumentNullException(); - } - catch (Exception) - { - // cannot - } - } - - internal static object GenerateClonerInternal(Type realType, bool asObject) - { - return GenerateProcessMethod(realType, asObject && realType.IsValueType()); - } - - private static FieldInfo _attributesFieldInfo = typeof(FieldInfo).GetPrivateField("m_fieldAttributes"); - - // today, I found that it not required to do such complex things. Just SetValue is enough - // is it new runtime changes, or I made incorrect assumptions eariler - // slow, but hardcore method to set readonly field - internal static void ForceSetField(FieldInfo field, object obj, object value) - { - var fieldInfo = field.GetType().GetPrivateField("m_fieldAttributes"); - - // TODO: think about it - // nothing to do :( we should a throw an exception, but it is no good for user - if (fieldInfo == null) - return; - var ov = fieldInfo.GetValue(field); - if (!(ov is FieldAttributes)) - return; - var v = (FieldAttributes)ov; - - // protect from parallel execution, when first thread set field readonly back, and second set it to write value - lock (fieldInfo) - { - fieldInfo.SetValue(field, v & ~FieldAttributes.InitOnly); - field.SetValue(obj, value); - fieldInfo.SetValue(field, v | FieldAttributes.InitOnly); - } - } - - private static object GenerateProcessMethod(Type type, bool unboxStruct) - { - if (type.IsArray) - { - return GenerateProcessArrayMethod(type); - } - - if (type.FullName != null && type.FullName.StartsWith("System.Tuple`")) - { - // if not safe type it is no guarantee that some type will contain reference to - // this tuple. In usual way, we're creating new object, setting reference for it - // and filling data. For tuple, we will fill data before creating object - // (in constructor arguments) - var genericArguments = type.GenericArguments(); - // current tuples contain only 8 arguments, but may be in future... - // we'll write code that works with it - if (genericArguments.Length < 10 && genericArguments.All(DeepClonerSafeTypes.CanReturnSameObject)) - { - return GenerateProcessTupleMethod(type); - } - } - - var methodType = unboxStruct || type.IsClass() ? typeof(object) : type; - - var expressionList = new List(); - - ParameterExpression from = Expression.Parameter(methodType); - var fromLocal = from; - var toLocal = Expression.Variable(type); - var state = Expression.Parameter(typeof(DeepCloneState)); - - if (!type.IsValueType()) - { - var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); - - // to = (T)from.MemberwiseClone() - expressionList.Add(Expression.Assign(toLocal, Expression.Convert(Expression.Call(from, methodInfo), type))); - - fromLocal = Expression.Variable(type); - // fromLocal = (T)from - expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); - - // added from -> to binding to ensure reference loop handling - // structs cannot loop here - // state.AddKnownRef(from, to) - expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, toLocal)); - } - else - { - if (unboxStruct) - { - // toLocal = (T)from; - expressionList.Add(Expression.Assign(toLocal, Expression.Unbox(from, type))); - fromLocal = Expression.Variable(type); - // fromLocal = toLocal; // structs, it is ok to copy - expressionList.Add(Expression.Assign(fromLocal, toLocal)); - } - else - { - // toLocal = from - expressionList.Add(Expression.Assign(toLocal, from)); - } - } - - List fi = new List(); - var tp = type; - do - { + + if (_fieldSetMethod == null) + throw new ArgumentNullException(); + } + catch (Exception) + { + // cannot + } + } + + internal static object GenerateClonerInternal(Type realType, bool asObject) + { + return GenerateProcessMethod(realType, asObject && realType.IsValueType()); + } + + private static FieldInfo _attributesFieldInfo = typeof(FieldInfo).GetPrivateField("m_fieldAttributes"); + + // today, I found that it not required to do such complex things. Just SetValue is enough + // is it new runtime changes, or I made incorrect assumptions eariler + // slow, but hardcore method to set readonly field + internal static void ForceSetField(FieldInfo field, object obj, object value) + { + var fieldInfo = field.GetType().GetPrivateField("m_fieldAttributes"); + + // TODO: think about it + // nothing to do :( we should a throw an exception, but it is no good for user + if (fieldInfo == null) + return; + var ov = fieldInfo.GetValue(field); + if (!(ov is FieldAttributes)) + return; + var v = (FieldAttributes)ov; + + // protect from parallel execution, when first thread set field readonly back, and second set it to write value + lock (fieldInfo) + { + fieldInfo.SetValue(field, v & ~FieldAttributes.InitOnly); + field.SetValue(obj, value); + fieldInfo.SetValue(field, v | FieldAttributes.InitOnly); + } + } + + private static object GenerateProcessMethod(Type type, bool unboxStruct) + { + if (type.IsArray) + { + return GenerateProcessArrayMethod(type); + } + + if (type.FullName != null && type.FullName.StartsWith("System.Tuple`")) + { + // if not safe type it is no guarantee that some type will contain reference to + // this tuple. In usual way, we're creating new object, setting reference for it + // and filling data. For tuple, we will fill data before creating object + // (in constructor arguments) + var genericArguments = type.GenericArguments(); + // current tuples contain only 8 arguments, but may be in future... + // we'll write code that works with it + if (genericArguments.Length < 10 && genericArguments.All(DeepClonerSafeTypes.CanReturnSameObject)) + { + return GenerateProcessTupleMethod(type); + } + } + + var methodType = unboxStruct || type.IsClass() ? typeof(object) : type; + + var expressionList = new List(); + + ParameterExpression from = Expression.Parameter(methodType); + var fromLocal = from; + var toLocal = Expression.Variable(type); + var state = Expression.Parameter(typeof(DeepCloneState)); + + if (!type.IsValueType()) + { + var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); + + // to = (T)from.MemberwiseClone() + expressionList.Add(Expression.Assign(toLocal, Expression.Convert(Expression.Call(from, methodInfo), type))); + + fromLocal = Expression.Variable(type); + // fromLocal = (T)from + expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); + + // added from -> to binding to ensure reference loop handling + // structs cannot loop here + // state.AddKnownRef(from, to) + expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, toLocal)); + } + else + { + if (unboxStruct) + { + // toLocal = (T)from; + expressionList.Add(Expression.Assign(toLocal, Expression.Unbox(from, type))); + fromLocal = Expression.Variable(type); + // fromLocal = toLocal; // structs, it is ok to copy + expressionList.Add(Expression.Assign(fromLocal, toLocal)); + } + else + { + // toLocal = from + expressionList.Add(Expression.Assign(toLocal, from)); + } + } + + List fi = new List(); + var tp = type; + do + { #if !NETCORE // don't do anything with this dark magic! if (tp == typeof(ContextBoundObject)) break; #else - if (tp.Name == "ContextBoundObject") break; + if (tp.Name == "ContextBoundObject") break; #endif - fi.AddRange(tp.GetDeclaredFields()); - tp = tp.BaseType(); - } - while (tp != null); - - foreach (var fieldInfo in fi) - { - if (!DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) - { - var methodInfo = fieldInfo.FieldType.IsValueType() - ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") - .MakeGenericMethod(fieldInfo.FieldType) - : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); - - var get = Expression.Field(fromLocal, fieldInfo); - - // toLocal.Field = Clone...Internal(fromLocal.Field) - var call = (Expression)Expression.Call(methodInfo, get, state); - if (!fieldInfo.FieldType.IsValueType()) - call = Expression.Convert(call, fieldInfo.FieldType); - - // should handle specially - // todo: think about optimization, but it rare case - var isReadonly = _readonlyFields.GetOrAdd(fieldInfo, f => f.IsInitOnly); - if (isReadonly) - { - if (_canFastCopyReadonlyFields) - { - expressionList.Add(Expression.Call( - Expression.Constant(fieldInfo), - _fieldSetMethod, - Expression.Convert(toLocal, typeof(object)), - Expression.Convert(call, typeof(object)))); - } - else - { - var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); - expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); - } - } - else - { - expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); - } - } - } - - expressionList.Add(Expression.Convert(toLocal, methodType)); - - var funcType = typeof(Func<,,>).MakeGenericType(methodType, typeof(DeepCloneState), methodType); - - var blockParams = new List(); - if (from != fromLocal) blockParams.Add(fromLocal); - blockParams.Add(toLocal); - - return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, state).Compile(); - } - - private static object GenerateProcessArrayMethod(Type type) - { - var elementType = type.GetElementType(); - var rank = type.GetArrayRank(); - - MethodInfo methodInfo; - - // multidim or not zero-based arrays - if (rank != 1 || type != elementType.MakeArrayType()) - { - if (rank == 2 && type == elementType.MakeArrayType(2)) - { - // small optimization for 2 dim arrays - methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType); - } - else - { - methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal"); - } - } - else - { - var methodName = "Clone1DimArrayClassInternal"; - if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; - else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; - methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); - } - - ParameterExpression from = Expression.Parameter(typeof(object)); - var state = Expression.Parameter(typeof(DeepCloneState)); - var call = Expression.Call(methodInfo, Expression.Convert(from, type), state); - - var funcType = typeof(Func<,,>).MakeGenericType(typeof(object), typeof(DeepCloneState), typeof(object)); - - return Expression.Lambda(funcType, call, from, state).Compile(); - } - - private static object GenerateProcessTupleMethod(Type type) - { - ParameterExpression from = Expression.Parameter(typeof(object)); - var state = Expression.Parameter(typeof(DeepCloneState)); - - var local = Expression.Variable(type); - var assign = Expression.Assign(local, Expression.Convert(from, type)); - - var funcType = typeof(Func); - - var tupleLength = type.GenericArguments().Length; - - var constructor = Expression.Assign(local, Expression.New(type.GetPublicConstructors().First(x => x.GetParameters().Length == tupleLength), - type.GetPublicProperties().OrderBy(x => x.Name) - .Where(x => x.CanRead && x.Name.StartsWith("Item") && char.IsDigit(x.Name[4])) - .Select(x => Expression.Property(local, x.Name)))); - - return Expression.Lambda(funcType, Expression.Block(new[] { local }, - assign, constructor, Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, local), - from), - from, state).Compile(); - } - - } + fi.AddRange(tp.GetDeclaredFields()); + tp = tp.BaseType(); + } + while (tp != null); + + foreach (var fieldInfo in fi) + { + if (!DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) + { + var methodInfo = fieldInfo.FieldType.IsValueType() + ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") + .MakeGenericMethod(fieldInfo.FieldType) + : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); + + var get = Expression.Field(fromLocal, fieldInfo); + + // toLocal.Field = Clone...Internal(fromLocal.Field) + var call = (Expression)Expression.Call(methodInfo, get, state); + if (!fieldInfo.FieldType.IsValueType()) + call = Expression.Convert(call, fieldInfo.FieldType); + + // should handle specially + // todo: think about optimization, but it rare case + var isReadonly = _readonlyFields.GetOrAdd(fieldInfo, f => f.IsInitOnly); + if (isReadonly) + { + if (_canFastCopyReadonlyFields) + { + expressionList.Add(Expression.Call( + Expression.Constant(fieldInfo), + _fieldSetMethod, + Expression.Convert(toLocal, typeof(object)), + Expression.Convert(call, typeof(object)))); + } + else + { + var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); + expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); + } + } + else + { + expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); + } + } + } + + expressionList.Add(Expression.Convert(toLocal, methodType)); + + var funcType = typeof(Func<,,>).MakeGenericType(methodType, typeof(DeepCloneState), methodType); + + var blockParams = new List(); + if (from != fromLocal) blockParams.Add(fromLocal); + blockParams.Add(toLocal); + + return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, state).Compile(); + } + + private static object GenerateProcessArrayMethod(Type type) + { + var elementType = type.GetElementType(); + var rank = type.GetArrayRank(); + + MethodInfo methodInfo; + + // multidim or not zero-based arrays + if (rank != 1 || type != elementType.MakeArrayType()) + { + if (rank == 2 && type == elementType.MakeArrayType(2)) + { + // small optimization for 2 dim arrays + methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType); + } + else + { + methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal"); + } + } + else + { + var methodName = "Clone1DimArrayClassInternal"; + if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; + else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; + methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); + } + + ParameterExpression from = Expression.Parameter(typeof(object)); + var state = Expression.Parameter(typeof(DeepCloneState)); + var call = Expression.Call(methodInfo, Expression.Convert(from, type), state); + + var funcType = typeof(Func<,,>).MakeGenericType(typeof(object), typeof(DeepCloneState), typeof(object)); + + return Expression.Lambda(funcType, call, from, state).Compile(); + } + + private static object GenerateProcessTupleMethod(Type type) + { + ParameterExpression from = Expression.Parameter(typeof(object)); + var state = Expression.Parameter(typeof(DeepCloneState)); + + var local = Expression.Variable(type); + var assign = Expression.Assign(local, Expression.Convert(from, type)); + + var funcType = typeof(Func); + + var tupleLength = type.GenericArguments().Length; + + var constructor = Expression.Assign(local, Expression.New(type.GetPublicConstructors().First(x => x.GetParameters().Length == tupleLength), + type.GetPublicProperties().OrderBy(x => x.Name) + .Where(x => x.CanRead && x.Name.StartsWith("Item") && char.IsDigit(x.Name[4])) + .Select(x => Expression.Property(local, x.Name)))); + + return Expression.Lambda(funcType, Expression.Block(new[] { local }, + assign, constructor, Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, local), + from), + from, state).Compile(); + } + + } } diff --git a/src/Foundatio/DeepCloner/Helpers/DeepClonerGenerator.cs b/src/Foundatio/DeepCloner/Helpers/DeepClonerGenerator.cs index c3be60be2..963a35918 100644 --- a/src/Foundatio/DeepCloner/Helpers/DeepClonerGenerator.cs +++ b/src/Foundatio/DeepCloner/Helpers/DeepClonerGenerator.cs @@ -4,229 +4,229 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class DeepClonerGenerator - { - public static T CloneObject(T obj) - { - if (obj is ValueType) - { - var type = obj.GetType(); - if (typeof(T) == type) - { - if (DeepClonerSafeTypes.CanReturnSameObject(type)) - return obj; - - return CloneStructInternal(obj, new DeepCloneState()); - } - } - - return (T)CloneClassRoot(obj); - } - - private static object CloneClassRoot(object obj) - { - if (obj == null) - return null; - - var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); - - // null -> should return same type - if (cloner == null) - return obj; - - return cloner(obj, new DeepCloneState()); - } - - internal static object CloneClassInternal(object obj, DeepCloneState state) - { - if (obj == null) - return null; - - var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); - - // safe object - if (cloner == null) - return obj; - - // loop - var knownRef = state.GetKnownRef(obj); - if (knownRef != null) - return knownRef; - - return cloner(obj, state); - } - - private static T CloneStructInternal(T obj, DeepCloneState state) // where T : struct - { - // no loops, no nulls, no inheritance - var cloner = GetClonerForValueType(); - - // safe ojbect - if (cloner == null) - return obj; - - return cloner(obj, state); - } - - // when we can't use code generation, we can use these methods - internal static T[] Clone1DimArraySafeInternal(T[] obj, DeepCloneState state) - { - var l = obj.Length; - var outArray = new T[l]; - state.AddKnownRef(obj, outArray); - Array.Copy(obj, outArray, obj.Length); - return outArray; - } - - internal static T[] Clone1DimArrayStructInternal(T[] obj, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (obj == null) return null; - var l = obj.Length; - var outArray = new T[l]; - state.AddKnownRef(obj, outArray); - var cloner = GetClonerForValueType(); - for (var i = 0; i < l; i++) - outArray[i] = cloner(obj[i], state); - - return outArray; - } - - internal static T[] Clone1DimArrayClassInternal(T[] obj, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (obj == null) return null; - var l = obj.Length; - var outArray = new T[l]; - state.AddKnownRef(obj, outArray); - for (var i = 0; i < l; i++) - outArray[i] = (T)CloneClassInternal(obj[i], state); - - return outArray; - } - - // relatively frequent case. specially handled - internal static T[,] Clone2DimArrayInternal(T[,] obj, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (obj == null) return null; - - // we cannot determine by type multidim arrays (one dimension is possible) - // so, will check for index here - var lb1 = obj.GetLowerBound(0); - var lb2 = obj.GetLowerBound(1); - if (lb1 != 0 || lb2 != 0) - return (T[,]) CloneAbstractArrayInternal(obj, state); - - var l1 = obj.GetLength(0); - var l2 = obj.GetLength(1); - var outArray = new T[l1, l2]; - state.AddKnownRef(obj, outArray); - if (DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) - { - Array.Copy(obj, outArray, obj.Length); - return outArray; - } - - if (typeof(T).IsValueType()) - { - var cloner = GetClonerForValueType(); - for (var i = 0; i < l1; i++) - for (var k = 0; k < l2; k++) - outArray[i, k] = cloner(obj[i, k], state); - } - else - { - for (var i = 0; i < l1; i++) - for (var k = 0; k < l2; k++) - outArray[i, k] = (T)CloneClassInternal(obj[i, k], state); - } - - return outArray; - } - - // rare cases, very slow cloning. currently it's ok - internal static Array CloneAbstractArrayInternal(Array obj, DeepCloneState state) - { - // not null from called method, but will check it anyway - if (obj == null) return null; - var rank = obj.Rank; - - var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray(); - - var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); - var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); - - var elementType = obj.GetType().GetElementType(); - var outArray = Array.CreateInstance(elementType, lengths, lowerBounds); - - state.AddKnownRef(obj, outArray); - - // we're unable to set any value to this array, so, just return it - if (lengths.Any(x => x == 0)) - return outArray; - - if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) - { - Array.Copy(obj, outArray, obj.Length); - return outArray; - } - - var ofs = rank - 1; - while (true) - { - outArray.SetValue(CloneClassInternal(obj.GetValue(idxes), state), idxes); - idxes[ofs]++; - - if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]) - { - do - { - if (ofs == 0) return outArray; - idxes[ofs] = lowerBounds[ofs]; - ofs--; - idxes[ofs]++; - } while (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]); - - ofs = rank - 1; - } - } - } - - internal static Func GetClonerForValueType() - { - return (Func)DeepClonerCache.GetOrAddStructAsObject(typeof(T), t => GenerateCloner(t, false)); - } - - private static object GenerateCloner(Type t, bool asObject) - { - if (DeepClonerSafeTypes.CanReturnSameObject(t) && (asObject && !t.IsValueType())) - return null; + internal static class DeepClonerGenerator + { + public static T CloneObject(T obj) + { + if (obj is ValueType) + { + var type = obj.GetType(); + if (typeof(T) == type) + { + if (DeepClonerSafeTypes.CanReturnSameObject(type)) + return obj; + + return CloneStructInternal(obj, new DeepCloneState()); + } + } + + return (T)CloneClassRoot(obj); + } + + private static object CloneClassRoot(object obj) + { + if (obj == null) + return null; + + var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); + + // null -> should return same type + if (cloner == null) + return obj; + + return cloner(obj, new DeepCloneState()); + } + + internal static object CloneClassInternal(object obj, DeepCloneState state) + { + if (obj == null) + return null; + + var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); + + // safe object + if (cloner == null) + return obj; + + // loop + var knownRef = state.GetKnownRef(obj); + if (knownRef != null) + return knownRef; + + return cloner(obj, state); + } + + private static T CloneStructInternal(T obj, DeepCloneState state) // where T : struct + { + // no loops, no nulls, no inheritance + var cloner = GetClonerForValueType(); + + // safe ojbect + if (cloner == null) + return obj; + + return cloner(obj, state); + } + + // when we can't use code generation, we can use these methods + internal static T[] Clone1DimArraySafeInternal(T[] obj, DeepCloneState state) + { + var l = obj.Length; + var outArray = new T[l]; + state.AddKnownRef(obj, outArray); + Array.Copy(obj, outArray, obj.Length); + return outArray; + } + + internal static T[] Clone1DimArrayStructInternal(T[] obj, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (obj == null) return null; + var l = obj.Length; + var outArray = new T[l]; + state.AddKnownRef(obj, outArray); + var cloner = GetClonerForValueType(); + for (var i = 0; i < l; i++) + outArray[i] = cloner(obj[i], state); + + return outArray; + } + + internal static T[] Clone1DimArrayClassInternal(T[] obj, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (obj == null) return null; + var l = obj.Length; + var outArray = new T[l]; + state.AddKnownRef(obj, outArray); + for (var i = 0; i < l; i++) + outArray[i] = (T)CloneClassInternal(obj[i], state); + + return outArray; + } + + // relatively frequent case. specially handled + internal static T[,] Clone2DimArrayInternal(T[,] obj, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (obj == null) return null; + + // we cannot determine by type multidim arrays (one dimension is possible) + // so, will check for index here + var lb1 = obj.GetLowerBound(0); + var lb2 = obj.GetLowerBound(1); + if (lb1 != 0 || lb2 != 0) + return (T[,])CloneAbstractArrayInternal(obj, state); + + var l1 = obj.GetLength(0); + var l2 = obj.GetLength(1); + var outArray = new T[l1, l2]; + state.AddKnownRef(obj, outArray); + if (DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) + { + Array.Copy(obj, outArray, obj.Length); + return outArray; + } + + if (typeof(T).IsValueType()) + { + var cloner = GetClonerForValueType(); + for (var i = 0; i < l1; i++) + for (var k = 0; k < l2; k++) + outArray[i, k] = cloner(obj[i, k], state); + } + else + { + for (var i = 0; i < l1; i++) + for (var k = 0; k < l2; k++) + outArray[i, k] = (T)CloneClassInternal(obj[i, k], state); + } + + return outArray; + } + + // rare cases, very slow cloning. currently it's ok + internal static Array CloneAbstractArrayInternal(Array obj, DeepCloneState state) + { + // not null from called method, but will check it anyway + if (obj == null) return null; + var rank = obj.Rank; + + var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray(); + + var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); + var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); + + var elementType = obj.GetType().GetElementType(); + var outArray = Array.CreateInstance(elementType, lengths, lowerBounds); + + state.AddKnownRef(obj, outArray); + + // we're unable to set any value to this array, so, just return it + if (lengths.Any(x => x == 0)) + return outArray; + + if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) + { + Array.Copy(obj, outArray, obj.Length); + return outArray; + } + + var ofs = rank - 1; + while (true) + { + outArray.SetValue(CloneClassInternal(obj.GetValue(idxes), state), idxes); + idxes[ofs]++; + + if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]) + { + do + { + if (ofs == 0) return outArray; + idxes[ofs] = lowerBounds[ofs]; + ofs--; + idxes[ofs]++; + } while (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]); + + ofs = rank - 1; + } + } + } + + internal static Func GetClonerForValueType() + { + return (Func)DeepClonerCache.GetOrAddStructAsObject(typeof(T), t => GenerateCloner(t, false)); + } + + private static object GenerateCloner(Type t, bool asObject) + { + if (DeepClonerSafeTypes.CanReturnSameObject(t) && (asObject && !t.IsValueType())) + return null; #if !NETCORE if (ShallowObjectCloner.IsSafeVariant()) return DeepClonerExprGenerator.GenerateClonerInternal(t, asObject); else return DeepClonerMsilGenerator.GenerateClonerInternal(t, asObject); #else - return DeepClonerExprGenerator.GenerateClonerInternal(t, asObject); + return DeepClonerExprGenerator.GenerateClonerInternal(t, asObject); #endif - } - - public static object CloneObjectTo(object objFrom, object objTo, bool isDeep) - { - if (objTo == null) return null; - - if (objFrom == null) - throw new ArgumentNullException("objFrom", "Cannot copy null object to another"); - var type = objFrom.GetType(); - if (!type.IsInstanceOfType(objTo)) - throw new InvalidOperationException("From object should be derived from From object, but From object has type " + objFrom.GetType().FullName + " and to " + objTo.GetType().FullName); - if (objFrom is string) - throw new InvalidOperationException("It is forbidden to clone strings"); - var cloner = (Func)(isDeep - ? DeepClonerCache.GetOrAddDeepClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, true)) - : DeepClonerCache.GetOrAddShallowClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, false))); - if (cloner == null) return objTo; - return cloner(objFrom, objTo, new DeepCloneState()); - } - } + } + + public static object CloneObjectTo(object objFrom, object objTo, bool isDeep) + { + if (objTo == null) return null; + + if (objFrom == null) + throw new ArgumentNullException("objFrom", "Cannot copy null object to another"); + var type = objFrom.GetType(); + if (!type.IsInstanceOfType(objTo)) + throw new InvalidOperationException("From object should be derived from From object, but From object has type " + objFrom.GetType().FullName + " and to " + objTo.GetType().FullName); + if (objFrom is string) + throw new InvalidOperationException("It is forbidden to clone strings"); + var cloner = (Func)(isDeep + ? DeepClonerCache.GetOrAddDeepClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, true)) + : DeepClonerCache.GetOrAddShallowClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, false))); + if (cloner == null) return objTo; + return cloner(objFrom, objTo, new DeepCloneState()); + } + } } diff --git a/src/Foundatio/DeepCloner/Helpers/DeepClonerSafeTypes.cs b/src/Foundatio/DeepCloner/Helpers/DeepClonerSafeTypes.cs index 1e29f3a45..2f1c55ba5 100644 --- a/src/Foundatio/DeepCloner/Helpers/DeepClonerSafeTypes.cs +++ b/src/Foundatio/DeepCloner/Helpers/DeepClonerSafeTypes.cs @@ -7,44 +7,44 @@ namespace Foundatio.Force.DeepCloner.Helpers { - /// - /// Safe types are types, which can be copied without real cloning. e.g. simple structs or strings (it is immutable) - /// - internal static class DeepClonerSafeTypes - { - internal static readonly ConcurrentDictionary KnownTypes = new(); - - static DeepClonerSafeTypes() - { - foreach ( - var x in - new[] - { - typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), - typeof(float), typeof(double), typeof(decimal), typeof(char), typeof(string), typeof(bool), typeof(DateTime), - typeof(IntPtr), typeof(UIntPtr), typeof(Guid), + /// + /// Safe types are types, which can be copied without real cloning. e.g. simple structs or strings (it is immutable) + /// + internal static class DeepClonerSafeTypes + { + internal static readonly ConcurrentDictionary KnownTypes = new(); + + static DeepClonerSafeTypes() + { + foreach ( + var x in + new[] + { + typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), + typeof(float), typeof(double), typeof(decimal), typeof(char), typeof(string), typeof(bool), typeof(DateTime), + typeof(IntPtr), typeof(UIntPtr), typeof(Guid), // do not clone such native type Type.GetType("System.RuntimeType"), - Type.GetType("System.RuntimeTypeHandle"), + Type.GetType("System.RuntimeTypeHandle"), #if !NETCORE typeof(DBNull) #endif }) KnownTypes.TryAdd(x, true); - } - - private static bool CanReturnSameType(Type type, HashSet processingTypes) - { - bool isSafe; - if (KnownTypes.TryGetValue(type, out isSafe)) - return isSafe; - - // enums are safe - // pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy - if (type.IsEnum() || type.IsPointer) - { - KnownTypes.TryAdd(type, true); - return true; - } + } + + private static bool CanReturnSameType(Type type, HashSet processingTypes) + { + bool isSafe; + if (KnownTypes.TryGetValue(type, out isSafe)) + return isSafe; + + // enums are safe + // pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy + if (type.IsEnum() || type.IsPointer) + { + KnownTypes.TryAdd(type, true); + return true; + } #if !NETCORE // do not do anything with remoting. it is very dangerous to clone, bcs it relate to deep core of framework @@ -82,88 +82,88 @@ private static bool CanReturnSameType(Type type, HashSet processingTypes) return true; } #else - // do not copy db null - if (type.FullName.StartsWith("System.DBNull")) - { - KnownTypes.TryAdd(type, true); - return true; - } - - if (type.FullName.StartsWith("System.RuntimeType")) - { - KnownTypes.TryAdd(type, true); - return true; - } - - if (type.FullName.StartsWith("System.Reflection.") && Equals(type.GetTypeInfo().Assembly, typeof(PropertyInfo).GetTypeInfo().Assembly)) - { - KnownTypes.TryAdd(type, true); - return true; - } - - if (type.IsSubclassOfTypeByName("CriticalFinalizerObject")) - { - KnownTypes.TryAdd(type, true); - return true; - } - - // better not to touch ms dependency injection - if (type.FullName.StartsWith("Microsoft.Extensions.DependencyInjection.")) - { - KnownTypes.TryAdd(type, true); - return true; - } - - if (type.FullName == "Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector") - { - KnownTypes.TryAdd(type, true); - return true; - } + // do not copy db null + if (type.FullName.StartsWith("System.DBNull")) + { + KnownTypes.TryAdd(type, true); + return true; + } + + if (type.FullName.StartsWith("System.RuntimeType")) + { + KnownTypes.TryAdd(type, true); + return true; + } + + if (type.FullName.StartsWith("System.Reflection.") && Equals(type.GetTypeInfo().Assembly, typeof(PropertyInfo).GetTypeInfo().Assembly)) + { + KnownTypes.TryAdd(type, true); + return true; + } + + if (type.IsSubclassOfTypeByName("CriticalFinalizerObject")) + { + KnownTypes.TryAdd(type, true); + return true; + } + + // better not to touch ms dependency injection + if (type.FullName.StartsWith("Microsoft.Extensions.DependencyInjection.")) + { + KnownTypes.TryAdd(type, true); + return true; + } + + if (type.FullName == "Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector") + { + KnownTypes.TryAdd(type, true); + return true; + } #endif - // classes are always unsafe (we should copy it fully to count references) - if (!type.IsValueType()) - { - KnownTypes.TryAdd(type, false); - return false; - } - - if (processingTypes == null) - processingTypes = new HashSet(); - - // structs cannot have a loops, but check it anyway - processingTypes.Add(type); - - List fi = new List(); - var tp = type; - do - { - fi.AddRange(tp.GetAllFields()); - tp = tp.BaseType(); - } - while (tp != null); - - foreach (var fieldInfo in fi) - { - // type loop - var fieldType = fieldInfo.FieldType; - if (processingTypes.Contains(fieldType)) - continue; - - // not safe and not not safe. we need to go deeper - if (!CanReturnSameType(fieldType, processingTypes)) - { - KnownTypes.TryAdd(type, false); - return false; - } - } - - KnownTypes.TryAdd(type, true); - return true; - } - - // not used anymore - /*/// + // classes are always unsafe (we should copy it fully to count references) + if (!type.IsValueType()) + { + KnownTypes.TryAdd(type, false); + return false; + } + + if (processingTypes == null) + processingTypes = new HashSet(); + + // structs cannot have a loops, but check it anyway + processingTypes.Add(type); + + List fi = new List(); + var tp = type; + do + { + fi.AddRange(tp.GetAllFields()); + tp = tp.BaseType(); + } + while (tp != null); + + foreach (var fieldInfo in fi) + { + // type loop + var fieldType = fieldInfo.FieldType; + if (processingTypes.Contains(fieldType)) + continue; + + // not safe and not not safe. we need to go deeper + if (!CanReturnSameType(fieldType, processingTypes)) + { + KnownTypes.TryAdd(type, false); + return false; + } + } + + KnownTypes.TryAdd(type, true); + return true; + } + + // not used anymore + /*/// /// Classes with only safe fields are safe for ShallowClone (if they root objects for copying) /// private static bool CanCopyClassInShallow(Type type) @@ -191,9 +191,9 @@ private static bool CanCopyClassInShallow(Type type) return true; }*/ - public static bool CanReturnSameObject(Type type) - { - return CanReturnSameType(type, null); - } - } + public static bool CanReturnSameObject(Type type) + { + return CanReturnSameType(type, null); + } + } } diff --git a/src/Foundatio/DeepCloner/Helpers/ReflectionHelper.cs b/src/Foundatio/DeepCloner/Helpers/ReflectionHelper.cs index a86134ffc..0aec3542e 100644 --- a/src/Foundatio/DeepCloner/Helpers/ReflectionHelper.cs +++ b/src/Foundatio/DeepCloner/Helpers/ReflectionHelper.cs @@ -5,167 +5,167 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class ReflectionHelper - { - public static bool IsEnum(this Type t) - { + internal static class ReflectionHelper + { + public static bool IsEnum(this Type t) + { #if NETCORE - return t.GetTypeInfo().IsEnum; + return t.GetTypeInfo().IsEnum; #else return t.IsEnum; #endif - } + } - public static bool IsValueType(this Type t) - { + public static bool IsValueType(this Type t) + { #if NETCORE - return t.GetTypeInfo().IsValueType; + return t.GetTypeInfo().IsValueType; #else return t.IsValueType; #endif - } + } - public static bool IsClass(this Type t) - { + public static bool IsClass(this Type t) + { #if NETCORE - return t.GetTypeInfo().IsClass; + return t.GetTypeInfo().IsClass; #else return t.IsClass; #endif - } + } - public static Type BaseType(this Type t) - { + public static Type BaseType(this Type t) + { #if NETCORE - return t.GetTypeInfo().BaseType; + return t.GetTypeInfo().BaseType; #else return t.BaseType; #endif - } + } - public static FieldInfo[] GetAllFields(this Type t) - { + public static FieldInfo[] GetAllFields(this Type t) + { #if NETCORE - return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); + return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); #else return t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); #endif - } - - public static PropertyInfo[] GetPublicProperties(this Type t) - { + } + + public static PropertyInfo[] GetPublicProperties(this Type t) + { #if NETCORE - return t.GetTypeInfo().DeclaredProperties.ToArray(); + return t.GetTypeInfo().DeclaredProperties.ToArray(); #else return t.GetProperties(BindingFlags.Instance | BindingFlags.Public); #endif - } + } - public static FieldInfo[] GetDeclaredFields(this Type t) - { + public static FieldInfo[] GetDeclaredFields(this Type t) + { #if NETCORE - return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); + return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); #else return t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly); #endif - } + } - public static ConstructorInfo[] GetPrivateConstructors(this Type t) - { + public static ConstructorInfo[] GetPrivateConstructors(this Type t) + { #if NETCORE - return t.GetTypeInfo().DeclaredConstructors.ToArray(); + return t.GetTypeInfo().DeclaredConstructors.ToArray(); #else return t.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); #endif - } + } - public static ConstructorInfo[] GetPublicConstructors(this Type t) - { + public static ConstructorInfo[] GetPublicConstructors(this Type t) + { #if NETCORE - return t.GetTypeInfo().DeclaredConstructors.ToArray(); + return t.GetTypeInfo().DeclaredConstructors.ToArray(); #else return t.GetConstructors(BindingFlags.Public | BindingFlags.Instance); #endif - } + } - public static MethodInfo GetPrivateMethod(this Type t, string methodName) - { + public static MethodInfo GetPrivateMethod(this Type t, string methodName) + { #if NETCORE - return t.GetTypeInfo().GetDeclaredMethod(methodName); + return t.GetTypeInfo().GetDeclaredMethod(methodName); #else return t.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); #endif - } + } - public static MethodInfo GetMethod(this Type t, string methodName) - { + public static MethodInfo GetMethod(this Type t, string methodName) + { #if NETCORE - return t.GetTypeInfo().GetDeclaredMethod(methodName); + return t.GetTypeInfo().GetDeclaredMethod(methodName); #else return t.GetMethod(methodName); #endif - } + } - public static MethodInfo GetPrivateStaticMethod(this Type t, string methodName) - { + public static MethodInfo GetPrivateStaticMethod(this Type t, string methodName) + { #if NETCORE - return t.GetTypeInfo().GetDeclaredMethod(methodName); + return t.GetTypeInfo().GetDeclaredMethod(methodName); #else return t.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); #endif - } + } - public static FieldInfo GetPrivateField(this Type t, string fieldName) - { + public static FieldInfo GetPrivateField(this Type t, string fieldName) + { #if NETCORE - return t.GetTypeInfo().GetDeclaredField(fieldName); + return t.GetTypeInfo().GetDeclaredField(fieldName); #else return t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); #endif - } + } - public static FieldInfo GetPrivateStaticField(this Type t, string fieldName) - { + public static FieldInfo GetPrivateStaticField(this Type t, string fieldName) + { #if NETCORE - return t.GetTypeInfo().GetDeclaredField(fieldName); + return t.GetTypeInfo().GetDeclaredField(fieldName); #else return t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static); #endif - } + } #if NETCORE - public static bool IsSubclassOfTypeByName(this Type t, string typeName) - { - while (t != null) - { - if (t.Name == typeName) - return true; - t = t.BaseType(); - } + public static bool IsSubclassOfTypeByName(this Type t, string typeName) + { + while (t != null) + { + if (t.Name == typeName) + return true; + t = t.BaseType(); + } - return false; - } + return false; + } #endif #if NETCORE - public static bool IsAssignableFrom(this Type from, Type to) - { - return from.GetTypeInfo().IsAssignableFrom(to.GetTypeInfo()); - } + public static bool IsAssignableFrom(this Type from, Type to) + { + return from.GetTypeInfo().IsAssignableFrom(to.GetTypeInfo()); + } - public static bool IsInstanceOfType(this Type from, object to) - { - return from.IsAssignableFrom(to.GetType()); - } + public static bool IsInstanceOfType(this Type from, object to) + { + return from.IsAssignableFrom(to.GetType()); + } #endif - - public static Type[] GenericArguments(this Type t) - { + + public static Type[] GenericArguments(this Type t) + { #if NETCORE - return t.GetTypeInfo().GenericTypeArguments; + return t.GetTypeInfo().GenericTypeArguments; #else return t.GetGenericArguments(); #endif - } - } -} \ No newline at end of file + } + } +} diff --git a/src/Foundatio/DeepCloner/Helpers/ShallowClonerGenerator.cs b/src/Foundatio/DeepCloner/Helpers/ShallowClonerGenerator.cs index 82ef84297..2cd2b70ee 100644 --- a/src/Foundatio/DeepCloner/Helpers/ShallowClonerGenerator.cs +++ b/src/Foundatio/DeepCloner/Helpers/ShallowClonerGenerator.cs @@ -2,28 +2,28 @@ namespace Foundatio.Force.DeepCloner.Helpers { - internal static class ShallowClonerGenerator - { - public static T CloneObject(T obj) - { - // this is faster than typeof(T).IsValueType - if (obj is ValueType) - { - if (typeof(T) == obj.GetType()) - return obj; - - // we're here so, we clone value type obj as object type T - // so, we need to copy it, bcs we have a reference, not real object. - return (T)ShallowObjectCloner.CloneObject(obj); - } + internal static class ShallowClonerGenerator + { + public static T CloneObject(T obj) + { + // this is faster than typeof(T).IsValueType + if (obj is ValueType) + { + if (typeof(T) == obj.GetType()) + return obj; - if (ReferenceEquals(obj, null)) - return (T)(object)null; - - if (DeepClonerSafeTypes.CanReturnSameObject(obj.GetType())) - return obj; + // we're here so, we clone value type obj as object type T + // so, we need to copy it, bcs we have a reference, not real object. + return (T)ShallowObjectCloner.CloneObject(obj); + } - return (T)ShallowObjectCloner.CloneObject(obj); - } - } + if (ReferenceEquals(obj, null)) + return (T)(object)null; + + if (DeepClonerSafeTypes.CanReturnSameObject(obj.GetType())) + return obj; + + return (T)ShallowObjectCloner.CloneObject(obj); + } + } } diff --git a/src/Foundatio/DeepCloner/Helpers/ShallowObjectCloner.cs b/src/Foundatio/DeepCloner/Helpers/ShallowObjectCloner.cs index 90e4618f3..db1f81fb7 100644 --- a/src/Foundatio/DeepCloner/Helpers/ShallowObjectCloner.cs +++ b/src/Foundatio/DeepCloner/Helpers/ShallowObjectCloner.cs @@ -10,31 +10,31 @@ namespace Foundatio.Force.DeepCloner.Helpers /// Internal class but due implementation restriction should be public /// internal abstract class ShallowObjectCloner - { - /// - /// Abstract method for real object cloning - /// - protected abstract object DoCloneObject(object obj); + { + /// + /// Abstract method for real object cloning + /// + protected abstract object DoCloneObject(object obj); - private static readonly ShallowObjectCloner _unsafeInstance; + private static readonly ShallowObjectCloner _unsafeInstance; - private static ShallowObjectCloner _instance; + private static ShallowObjectCloner _instance; - /// - /// Performs real shallow object clone - /// - public static object CloneObject(object obj) - { - return _instance.DoCloneObject(obj); - } + /// + /// Performs real shallow object clone + /// + public static object CloneObject(object obj) + { + return _instance.DoCloneObject(obj); + } - internal static bool IsSafeVariant() - { - return _instance is ShallowSafeObjectCloner; - } + internal static bool IsSafeVariant() + { + return _instance is ShallowSafeObjectCloner; + } - static ShallowObjectCloner() - { + static ShallowObjectCloner() + { #if !NETCORE _unsafeInstance = GenerateUnsafeCloner(); _instance = _unsafeInstance; @@ -48,21 +48,21 @@ static ShallowObjectCloner() _instance = new ShallowSafeObjectCloner(); } #else - _instance = new ShallowSafeObjectCloner(); - // no unsafe variant for core - _unsafeInstance = _instance; + _instance = new ShallowSafeObjectCloner(); + // no unsafe variant for core + _unsafeInstance = _instance; #endif - } + } - /// - /// Purpose of this method is testing variants - /// - internal static void SwitchTo(bool isSafe) - { - DeepClonerCache.ClearCache(); - if (isSafe) _instance = new ShallowSafeObjectCloner(); - else _instance = _unsafeInstance; - } + /// + /// Purpose of this method is testing variants + /// + internal static void SwitchTo(bool isSafe) + { + DeepClonerCache.ClearCache(); + if (isSafe) _instance = new ShallowSafeObjectCloner(); + else _instance = _unsafeInstance; + } #if !NETCORE private static ShallowObjectCloner GenerateUnsafeCloner() @@ -95,22 +95,22 @@ private static ShallowObjectCloner GenerateUnsafeCloner() } #endif - private class ShallowSafeObjectCloner : ShallowObjectCloner - { - private static readonly Func _cloneFunc; + private class ShallowSafeObjectCloner : ShallowObjectCloner + { + private static readonly Func _cloneFunc; - static ShallowSafeObjectCloner() - { - var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); - var p = Expression.Parameter(typeof(object)); - var mce = Expression.Call(p, methodInfo); - _cloneFunc = Expression.Lambda>(mce, p).Compile(); - } + static ShallowSafeObjectCloner() + { + var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); + var p = Expression.Parameter(typeof(object)); + var mce = Expression.Call(p, methodInfo); + _cloneFunc = Expression.Lambda>(mce, p).Compile(); + } - protected override object DoCloneObject(object obj) - { - return _cloneFunc(obj); - } - } - } + protected override object DoCloneObject(object obj) + { + return _cloneFunc(obj); + } + } + } } diff --git a/src/Foundatio/Extensions/CacheClientExtensions.cs b/src/Foundatio/Extensions/CacheClientExtensions.cs index cb319c32f..b92304c94 100644 --- a/src/Foundatio/Extensions/CacheClientExtensions.cs +++ b/src/Foundatio/Extensions/CacheClientExtensions.cs @@ -4,113 +4,140 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Caching { - public static class CacheClientExtensions { - public static async Task GetAsync(this ICacheClient client, string key, T defaultValue) { +namespace Foundatio.Caching +{ + public static class CacheClientExtensions + { + public static async Task GetAsync(this ICacheClient client, string key, T defaultValue) + { var cacheValue = await client.GetAsync(key).AnyContext(); return cacheValue.HasValue ? cacheValue.Value : defaultValue; } - public static Task>> GetAllAsync(this ICacheClient client, params string[] keys) { + public static Task>> GetAllAsync(this ICacheClient client, params string[] keys) + { return client.GetAllAsync(keys.ToArray()); } - public static Task IncrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) { + public static Task IncrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) + { return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task IncrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) { + public static Task IncrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) + { return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task IncrementAsync(this ICacheClient client, string key, TimeSpan? expiresIn = null) { + public static Task IncrementAsync(this ICacheClient client, string key, TimeSpan? expiresIn = null) + { return client.IncrementAsync(key, 1, expiresIn); } - public static Task DecrementAsync(this ICacheClient client, string key, TimeSpan? expiresIn = null) { + public static Task DecrementAsync(this ICacheClient client, string key, TimeSpan? expiresIn = null) + { return client.IncrementAsync(key, -1, expiresIn); } - public static Task DecrementAsync(this ICacheClient client, string key, long amount, TimeSpan? expiresIn = null) { + public static Task DecrementAsync(this ICacheClient client, string key, long amount, TimeSpan? expiresIn = null) + { return client.IncrementAsync(key, -amount, expiresIn); } - public static Task DecrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) { + public static Task DecrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) + { return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task DecrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) { + public static Task DecrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) + { return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task AddAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { + public static Task AddAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) + { return client.AddAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task SetAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { + public static Task SetAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) + { return client.SetAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task ReplaceAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { + public static Task ReplaceAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) + { return client.ReplaceAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task ReplaceIfEqualAsync(this ICacheClient client, string key, T value, T expected, DateTime? expiresAtUtc) { + public static Task ReplaceIfEqualAsync(this ICacheClient client, string key, T value, T expected, DateTime? expiresAtUtc) + { return client.ReplaceIfEqualAsync(key, value, expected, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task SetAllAsync(this ICacheClient client, IDictionary values, DateTime? expiresAtUtc) { + public static Task SetAllAsync(this ICacheClient client, IDictionary values, DateTime? expiresAtUtc) + { return client.SetAllAsync(values, expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static Task SetExpirationAsync(this ICacheClient client, string key, DateTime expiresAtUtc) { + public static Task SetExpirationAsync(this ICacheClient client, string key, DateTime expiresAtUtc) + { return client.SetExpirationAsync(key, expiresAtUtc.Subtract(SystemClock.UtcNow)); } - public static async Task ListAddAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) { - return await client.ListAddAsync(key, new [] { value }, expiresIn).AnyContext() > 0; + public static async Task ListAddAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) + { + return await client.ListAddAsync(key, new[] { value }, expiresIn).AnyContext() > 0; } - public static async Task ListRemoveAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) { + public static async Task ListRemoveAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) + { return await client.ListRemoveAsync(key, new[] { value }, expiresIn).AnyContext() > 0; } [Obsolete("Use ListAddAsync instead")] - public static async Task SetAddAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) { - return await client.ListAddAsync(key, new [] { value }, expiresIn).AnyContext() > 0; + public static async Task SetAddAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) + { + return await client.ListAddAsync(key, new[] { value }, expiresIn).AnyContext() > 0; } [Obsolete("Use ListRemoveAsync instead")] - public static async Task SetRemoveAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) { + public static async Task SetRemoveAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) + { return await client.ListRemoveAsync(key, new[] { value }, expiresIn).AnyContext() > 0; } - + [Obsolete("Use ListAddAsync instead")] - public static Task SetAddAsync(this ICacheClient client, string key, IEnumerable value, TimeSpan? expiresIn = null) { + public static Task SetAddAsync(this ICacheClient client, string key, IEnumerable value, TimeSpan? expiresIn = null) + { return client.ListAddAsync(key, new[] { value }, expiresIn); } - + [Obsolete("Use ListRemoveAsync instead")] - public static Task SetRemoveAsync(this ICacheClient client, string key, IEnumerable value, TimeSpan? expiresIn = null) { + public static Task SetRemoveAsync(this ICacheClient client, string key, IEnumerable value, TimeSpan? expiresIn = null) + { return client.ListRemoveAsync(key, value, expiresIn); } - + [Obsolete("Use ListAddAsync instead")] - public static Task>> GetSetAsync(this ICacheClient client, string key) { + public static Task>> GetSetAsync(this ICacheClient client, string key) + { return client.GetListAsync(key); } - public static Task SetIfHigherAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) { + public static Task SetIfHigherAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) + { long unixTime = value.ToUnixTimeMilliseconds(); return client.SetIfHigherAsync(key, unixTime, expiresIn); } - public static Task SetIfLowerAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) { + public static Task SetIfLowerAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) + { long unixTime = value.ToUnixTimeMilliseconds(); return client.SetIfLowerAsync(key, unixTime, expiresIn); } - public static async Task GetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) { + public static async Task GetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) + { var unixTime = await client.GetAsync(key).AnyContext(); if (!unixTime.HasValue) return defaultValue ?? DateTime.MinValue; @@ -118,15 +145,18 @@ public static async Task GetUnixTimeMillisecondsAsync(this ICacheClien return unixTime.Value.FromUnixTimeMilliseconds(); } - public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) { + public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) + { return client.SetAsync(key, value.ToUnixTimeMilliseconds(), expiresIn); } - public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) { + public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) + { return client.SetAsync(key, value.ToUnixTimeMilliseconds(), expiresAtUtc?.Subtract(SystemClock.UtcNow)); } - public static async Task GetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) { + public static async Task GetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) + { var unixTime = await client.GetAsync(key).AnyContext(); if (!unixTime.HasValue) return defaultValue ?? DateTime.MinValue; @@ -134,11 +164,13 @@ public static async Task GetUnixTimeSecondsAsync(this ICacheClient cli return unixTime.Value.FromUnixTimeSeconds(); } - public static Task SetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) { + public static Task SetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime value, TimeSpan? expiresIn = null) + { return client.SetAsync(key, value.ToUnixTimeSeconds(), expiresIn); } - public static Task SetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) { + public static Task SetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) + { return client.SetAsync(key, value.ToUnixTimeSeconds(), expiresAtUtc?.Subtract(SystemClock.UtcNow)); } } diff --git a/src/Foundatio/Extensions/CollectionExtensions.cs b/src/Foundatio/Extensions/CollectionExtensions.cs index 378128670..377098631 100644 --- a/src/Foundatio/Extensions/CollectionExtensions.cs +++ b/src/Foundatio/Extensions/CollectionExtensions.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.Linq; -namespace Foundatio.Utility { - internal static class CollectionExtensions { - public static ICollection ReduceTimeSeries(this ICollection items, Func dateSelector, Func, DateTime, T> reducer, int dataPoints) { +namespace Foundatio.Utility +{ + internal static class CollectionExtensions + { + public static ICollection ReduceTimeSeries(this ICollection items, Func dateSelector, Func, DateTime, T> reducer, int dataPoints) + { if (items.Count <= dataPoints) return items; @@ -14,7 +17,8 @@ public static ICollection ReduceTimeSeries(this ICollection items, Func var bucketSize = (maxTicks - minTicks) / dataPoints; var buckets = new List(); long currentTick = minTicks; - while (currentTick < maxTicks) { + while (currentTick < maxTicks) + { buckets.Add(currentTick); currentTick += bucketSize; } diff --git a/src/Foundatio/Extensions/ConcurrentDictionaryExtensions.cs b/src/Foundatio/Extensions/ConcurrentDictionaryExtensions.cs index 99242cf86..92c3128c4 100644 --- a/src/Foundatio/Extensions/ConcurrentDictionaryExtensions.cs +++ b/src/Foundatio/Extensions/ConcurrentDictionaryExtensions.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Concurrent; -namespace Foundatio.Utility { - internal static class ConcurrentDictionaryExtensions { - public static bool TryUpdate(this ConcurrentDictionary concurrentDictionary, TKey key, Func updateValueFactory) { +namespace Foundatio.Utility +{ + internal static class ConcurrentDictionaryExtensions + { + public static bool TryUpdate(this ConcurrentDictionary concurrentDictionary, TKey key, Func updateValueFactory) + { if (key == null) throw new ArgumentNullException(nameof(key)); @@ -12,10 +15,11 @@ public static bool TryUpdate(this ConcurrentDictionary(this ConcurrentQueue queue) { +namespace Foundatio.Utility +{ + internal static class ConcurrentQueueExtensions + { + public static void Clear(this ConcurrentQueue queue) + { while (queue.TryDequeue(out var _)) { } } } diff --git a/src/Foundatio/Extensions/DateTimeExtensions.cs b/src/Foundatio/Extensions/DateTimeExtensions.cs index 6f83ff24c..ca3ba8331 100644 --- a/src/Foundatio/Extensions/DateTimeExtensions.cs +++ b/src/Foundatio/Extensions/DateTimeExtensions.cs @@ -1,32 +1,41 @@ using System; -namespace Foundatio.Utility { - internal static class DateTimeExtensions { - public static DateTime Floor(this DateTime date, TimeSpan interval) { +namespace Foundatio.Utility +{ + internal static class DateTimeExtensions + { + public static DateTime Floor(this DateTime date, TimeSpan interval) + { return date.AddTicks(-(date.Ticks % interval.Ticks)); } - public static DateTime Ceiling(this DateTime date, TimeSpan interval) { + public static DateTime Ceiling(this DateTime date, TimeSpan interval) + { return date.AddTicks(interval.Ticks - (date.Ticks % interval.Ticks)); } - public static long ToUnixTimeMilliseconds(this DateTime date) { + public static long ToUnixTimeMilliseconds(this DateTime date) + { return new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeMilliseconds(); } - public static DateTime FromUnixTimeMilliseconds(this long timestamp) { + public static DateTime FromUnixTimeMilliseconds(this long timestamp) + { return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).UtcDateTime; } - public static long ToUnixTimeSeconds(this DateTime date) { + public static long ToUnixTimeSeconds(this DateTime date) + { return new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeSeconds(); } - public static DateTime FromUnixTimeSeconds(this long timestamp) { + public static DateTime FromUnixTimeSeconds(this long timestamp) + { return DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; } - - public static DateTime SafeAdd(this DateTime date, TimeSpan value) { + + public static DateTime SafeAdd(this DateTime date, TimeSpan value) + { if (date.Ticks + value.Ticks < DateTime.MinValue.Ticks) return DateTime.MinValue; diff --git a/src/Foundatio/Extensions/EnumExtensions.cs b/src/Foundatio/Extensions/EnumExtensions.cs index eb2667458..7b4e0ef63 100644 --- a/src/Foundatio/Extensions/EnumExtensions.cs +++ b/src/Foundatio/Extensions/EnumExtensions.cs @@ -1,15 +1,18 @@ using System; using System.Reflection; -namespace Foundatio.Utility { - internal static class EnumExtensions { +namespace Foundatio.Utility +{ + internal static class EnumExtensions + { /// /// Will try and parse an enum and it's default type. /// /// /// /// True if the enum value is defined. - public static bool TryEnumIsDefined(Type type, object value) { + public static bool TryEnumIsDefined(Type type, object value) + { if (type == null || value == null || !type.GetTypeInfo().IsEnum) return false; @@ -39,14 +42,17 @@ public static bool TryEnumIsDefined(Type type, object value) { return false; } - public static bool TryEnumIsDefined(Type type, object value) { + public static bool TryEnumIsDefined(Type type, object value) + { // Catch any casting errors that can occur or if 0 is not defined as a default value. - try { + try + { if (value is T && Enum.IsDefined(type, (T)value)) return true; - } catch (Exception) {} + } + catch (Exception) { } return false; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/EnumerableExtensions.cs b/src/Foundatio/Extensions/EnumerableExtensions.cs index ff1e6999a..652b68b4e 100644 --- a/src/Foundatio/Extensions/EnumerableExtensions.cs +++ b/src/Foundatio/Extensions/EnumerableExtensions.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; -namespace Foundatio.Utility { - internal static class EnumerableExtensions { - public static void ForEach(this IEnumerable collection, Action action) { +namespace Foundatio.Utility +{ + internal static class EnumerableExtensions + { + public static void ForEach(this IEnumerable collection, Action action) + { if (collection == null || action == null) return; @@ -11,7 +14,8 @@ public static void ForEach(this IEnumerable collection, Action action) action(item); } - public static void AddRange(this ICollection list, IEnumerable range) { + public static void AddRange(this ICollection list, IEnumerable range) + { if (list == null || range == null) return; @@ -19,4 +23,4 @@ public static void AddRange(this ICollection list, IEnumerable range) { list.Add(r); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/ExceptionExtensions.cs b/src/Foundatio/Extensions/ExceptionExtensions.cs index 5a2fb741b..ba0ac48b6 100644 --- a/src/Foundatio/Extensions/ExceptionExtensions.cs +++ b/src/Foundatio/Extensions/ExceptionExtensions.cs @@ -1,9 +1,12 @@ using System; using System.Linq; -namespace Foundatio.Utility { - internal static class ExceptionExtensions { - public static Exception GetInnermostException(this Exception exception) { +namespace Foundatio.Utility +{ + internal static class ExceptionExtensions + { + public static Exception GetInnermostException(this Exception exception) + { if (exception == null) return null; @@ -14,7 +17,8 @@ public static Exception GetInnermostException(this Exception exception) { return current; } - public static string GetMessage(this Exception exception) { + public static string GetMessage(this Exception exception) + { if (exception == null) return String.Empty; @@ -24,4 +28,4 @@ public static string GetMessage(this Exception exception) { return exception.GetInnermostException().Message; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/LoggerExtensions.cs b/src/Foundatio/Extensions/LoggerExtensions.cs index 5471268ba..0b2b80a72 100644 --- a/src/Foundatio/Extensions/LoggerExtensions.cs +++ b/src/Foundatio/Extensions/LoggerExtensions.cs @@ -3,101 +3,123 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Extensions.Logging { - public class LogState : IEnumerable> { +namespace Microsoft.Extensions.Logging +{ + public class LogState : IEnumerable> + { private readonly Dictionary _state = new(); public int Count => _state.Count; - public object this[string property] { + public object this[string property] + { get { return _state[property]; } set { _state[property] = value; } } - public LogState Property(string property, object value) { + public LogState Property(string property, object value) + { _state.Add(property, value); return this; } - public LogState PropertyIf(string property, object value, bool condition) { + public LogState PropertyIf(string property, object value, bool condition) + { if (condition) _state.Add(property, value); return this; } - public bool ContainsProperty(string property) { + public bool ContainsProperty(string property) + { return _state.ContainsKey(property); } - public IEnumerator> GetEnumerator() { + public IEnumerator> GetEnumerator() + { return _state.GetEnumerator(); } - IEnumerator IEnumerable.GetEnumerator() { + IEnumerator IEnumerable.GetEnumerator() + { return GetEnumerator(); } } - public static class LoggerExtensions { - public static IDisposable BeginScope(this ILogger logger, Func stateBuilder) { + public static class LoggerExtensions + { + public static IDisposable BeginScope(this ILogger logger, Func stateBuilder) + { var logState = new LogState(); logState = stateBuilder(logState); return logger.BeginScope(logState); } - public static IDisposable BeginScope(this ILogger logger, string property, object value) { + public static IDisposable BeginScope(this ILogger logger, string property, object value) + { return logger.BeginScope(b => b.Property(property, value)); } - public static void LogDebug(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogDebug(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogDebug(message, args); } - public static void LogTrace(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogTrace(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogTrace(message, args); } - public static void LogInformation(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogInformation(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogInformation(message, args); } - public static void LogWarning(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogWarning(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogWarning(message, args); } - public static void LogError(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogError(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogError(message, args); } - public static void LogError(this ILogger logger, Func stateBuilder, Exception exception, string message, params object[] args) { + public static void LogError(this ILogger logger, Func stateBuilder, Exception exception, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogError(exception, message, args); } - public static void LogCritical(this ILogger logger, Func stateBuilder, string message, params object[] args) { + public static void LogCritical(this ILogger logger, Func stateBuilder, string message, params object[] args) + { using (BeginScope(logger, stateBuilder)) logger.LogCritical(message, args); } - public static LogState Critical(this LogState builder, bool isCritical = true) { + public static LogState Critical(this LogState builder, bool isCritical = true) + { return isCritical ? builder.Tag("Critical") : builder; } - public static LogState Tag(this LogState builder, string tag) { + public static LogState Tag(this LogState builder, string tag) + { return builder.Tag(new[] { tag }); } - public static LogState Tag(this LogState builder, IEnumerable tags) { + public static LogState Tag(this LogState builder, IEnumerable tags) + { var tagList = new List(); if (builder.ContainsProperty("Tags") && builder["Tags"] is List) tagList = builder["Tags"] as List; - foreach (string tag in tags) { + foreach (string tag in tags) + { if (!tagList.Any(s => s.Equals(tag, StringComparison.OrdinalIgnoreCase))) tagList.Add(tag); } @@ -105,7 +127,8 @@ public static LogState Tag(this LogState builder, IEnumerable tags) { return builder.Property("Tags", tagList); } - public static LogState Properties(this LogState builder, ICollection> collection) { + public static LogState Properties(this LogState builder, ICollection> collection) + { if (collection == null) return builder; diff --git a/src/Foundatio/Extensions/NumberExtensions.cs b/src/Foundatio/Extensions/NumberExtensions.cs index 01f0d250d..12b716480 100644 --- a/src/Foundatio/Extensions/NumberExtensions.cs +++ b/src/Foundatio/Extensions/NumberExtensions.cs @@ -1,20 +1,26 @@ using System; -namespace Foundatio.Utility { - internal static class NumericExtensions { - public static string ToFileSizeDisplay(this int i) { +namespace Foundatio.Utility +{ + internal static class NumericExtensions + { + public static string ToFileSizeDisplay(this int i) + { return ToFileSizeDisplay((long)i, 2); } - public static string ToFileSizeDisplay(this int i, int decimals) { + public static string ToFileSizeDisplay(this int i, int decimals) + { return ToFileSizeDisplay((long)i, decimals); } - public static string ToFileSizeDisplay(this long i) { + public static string ToFileSizeDisplay(this long i) + { return ToFileSizeDisplay(i, 2); } - public static string ToFileSizeDisplay(this long i, int decimals) { + public static string ToFileSizeDisplay(this long i, int decimals) + { if (i < 1024 * 1024 * 1024) // 1 GB { string value = Math.Round((decimal)i / 1024m / 1024m, decimals).ToString("N" + decimals); @@ -22,7 +28,9 @@ public static string ToFileSizeDisplay(this long i, int decimals) { value = value.Substring(0, value.Length - decimals - 1); return String.Concat(value, " MB"); - } else { + } + else + { string value = Math.Round((decimal)i / 1024m / 1024m / 1024m, decimals).ToString("N" + decimals); if (decimals > 0 && value.EndsWith(new string('0', decimals))) value = value.Substring(0, value.Length - decimals - 1); @@ -30,16 +38,19 @@ public static string ToFileSizeDisplay(this long i, int decimals) { return String.Concat(value, " GB"); } } - - public static string ToOrdinal(this int num) { - switch (num % 100) { + + public static string ToOrdinal(this int num) + { + switch (num % 100) + { case 11: case 12: case 13: return num.ToString("#,###0") + "th"; } - switch (num % 10) { + switch (num % 10) + { case 1: return num.ToString("#,###0") + "st"; case 2: diff --git a/src/Foundatio/Extensions/ObjectExtensions.cs b/src/Foundatio/Extensions/ObjectExtensions.cs index 2e846dfae..f39fd1aa4 100644 --- a/src/Foundatio/Extensions/ObjectExtensions.cs +++ b/src/Foundatio/Extensions/ObjectExtensions.cs @@ -1,10 +1,13 @@ using System; using Foundatio.Force.DeepCloner.Helpers; -namespace Foundatio.Utility { - public static class ObjectExtensions { - public static T DeepClone(this T original) { +namespace Foundatio.Utility +{ + public static class ObjectExtensions + { + public static T DeepClone(this T original) + { return DeepClonerGenerator.CloneObject(original); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/StringExtensions.cs b/src/Foundatio/Extensions/StringExtensions.cs index ee01f38d0..2fa849393 100644 --- a/src/Foundatio/Extensions/StringExtensions.cs +++ b/src/Foundatio/Extensions/StringExtensions.cs @@ -1,12 +1,15 @@ using System; using System.IO; -namespace Foundatio.Extensions { - internal static class StringExtensions { - public static string NormalizePath(this string path) { +namespace Foundatio.Extensions +{ + internal static class StringExtensions + { + public static string NormalizePath(this string path) + { if (String.IsNullOrEmpty(path)) return path; - + if (Path.DirectorySeparatorChar == '\\') path = path.Replace('/', Path.DirectorySeparatorChar); else if (Path.DirectorySeparatorChar == '/') @@ -15,4 +18,4 @@ public static string NormalizePath(this string path) { return path; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/TaskExtensions.cs b/src/Foundatio/Extensions/TaskExtensions.cs index 29939e73a..c5e2f03e5 100644 --- a/src/Foundatio/Extensions/TaskExtensions.cs +++ b/src/Foundatio/Extensions/TaskExtensions.cs @@ -5,35 +5,43 @@ using System.Threading.Tasks; using Foundatio.AsyncEx; -namespace Foundatio.Utility { - internal static class TaskExtensions { +namespace Foundatio.Utility +{ + internal static class TaskExtensions + { [DebuggerStepThrough] - public static ConfiguredTaskAwaitable AnyContext(this Task task) { + public static ConfiguredTaskAwaitable AnyContext(this Task task) + { return task.ConfigureAwait(continueOnCapturedContext: false); } [DebuggerStepThrough] - public static ConfiguredCancelableAsyncEnumerable AnyContext(this IAsyncEnumerable source) { + public static ConfiguredCancelableAsyncEnumerable AnyContext(this IAsyncEnumerable source) + { return source.ConfigureAwait(continueOnCapturedContext: false); } [DebuggerStepThrough] - public static ConfiguredAsyncDisposable AnyContext(this IAsyncDisposable source) { + public static ConfiguredAsyncDisposable AnyContext(this IAsyncDisposable source) + { return source.ConfigureAwait(continueOnCapturedContext: false); } [DebuggerStepThrough] - public static ConfiguredTaskAwaitable AnyContext(this Task task) { + public static ConfiguredTaskAwaitable AnyContext(this Task task) + { return task.ConfigureAwait(continueOnCapturedContext: false); } [DebuggerStepThrough] - public static ConfiguredValueTaskAwaitable AnyContext(this ValueTask task) { + public static ConfiguredValueTaskAwaitable AnyContext(this ValueTask task) + { return task.ConfigureAwait(continueOnCapturedContext: false); } [DebuggerStepThrough] - public static ConfiguredTaskAwaitable AnyContext(this AwaitableDisposable task) where TResult : IDisposable { + public static ConfiguredTaskAwaitable AnyContext(this AwaitableDisposable task) where TResult : IDisposable + { return task.ConfigureAwait(continueOnCapturedContext: false); } } diff --git a/src/Foundatio/Extensions/TimespanExtensions.cs b/src/Foundatio/Extensions/TimespanExtensions.cs index 8e7405b21..c3af315dc 100644 --- a/src/Foundatio/Extensions/TimespanExtensions.cs +++ b/src/Foundatio/Extensions/TimespanExtensions.cs @@ -1,10 +1,14 @@ using System; using System.Threading; -namespace Foundatio.Utility { - internal static class TimeSpanExtensions { - public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan timeout) { - if (timeout == TimeSpan.Zero) { +namespace Foundatio.Utility +{ + internal static class TimeSpanExtensions + { + public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan timeout) + { + if (timeout == TimeSpan.Zero) + { var source = new CancellationTokenSource(); source.Cancel(); return source; @@ -16,23 +20,27 @@ public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan ti return new CancellationTokenSource(); } - public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout) { + public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout) + { if (timeout.HasValue) return timeout.Value.ToCancellationTokenSource(); return new CancellationTokenSource(); } - public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout, TimeSpan defaultTimeout) { + public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout, TimeSpan defaultTimeout) + { return (timeout ?? defaultTimeout).ToCancellationTokenSource(); } - public static TimeSpan Min(this TimeSpan source, TimeSpan other) { + public static TimeSpan Min(this TimeSpan source, TimeSpan other) + { return source.Ticks > other.Ticks ? other : source; } - public static TimeSpan Max(this TimeSpan source, TimeSpan other) { + public static TimeSpan Max(this TimeSpan source, TimeSpan other) + { return source.Ticks < other.Ticks ? other : source; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Extensions/TypeExtensions.cs b/src/Foundatio/Extensions/TypeExtensions.cs index b0d40691a..092f25dec 100644 --- a/src/Foundatio/Extensions/TypeExtensions.cs +++ b/src/Foundatio/Extensions/TypeExtensions.cs @@ -3,9 +3,12 @@ using System.Reflection; using Foundatio.Serializer; -namespace Foundatio.Utility { - internal static class TypeExtensions { - public static bool IsNumeric(this Type type) { +namespace Foundatio.Utility +{ + internal static class TypeExtensions + { + public static bool IsNumeric(this Type type) + { if (type.IsArray) return false; @@ -22,7 +25,8 @@ public static bool IsNumeric(this Type type) { type == TypeHelper.UInt64Type) return true; - switch (Type.GetTypeCode(type)) { + switch (Type.GetTypeCode(type)) + { case TypeCode.Byte: case TypeCode.Decimal: case TypeCode.Double: @@ -40,7 +44,8 @@ public static bool IsNumeric(this Type type) { return false; } - public static bool IsNullableNumeric(this Type type) { + public static bool IsNullableNumeric(this Type type) + { if (type.IsArray) return false; @@ -48,12 +53,17 @@ public static bool IsNullableNumeric(this Type type) { return t != null && t.IsNumeric(); } - public static T ToType(this object value, ISerializer serializer = null) { + public static T ToType(this object value, ISerializer serializer = null) + { var targetType = typeof(T); - if (value == null) { - try { + if (value == null) + { + try + { return (T)Convert.ChangeType(value, targetType); - } catch { + } + catch + { throw new ArgumentNullException(nameof(value)); } } @@ -65,9 +75,11 @@ public static T ToType(this object value, ISerializer serializer = null) { return (T)value; var targetTypeInfo = targetType.GetTypeInfo(); - if (targetTypeInfo.IsEnum && (value is string || valueType.GetTypeInfo().IsEnum)) { + if (targetTypeInfo.IsEnum && (value is string || valueType.GetTypeInfo().IsEnum)) + { // attempt to match enum by name. - if (EnumExtensions.TryEnumIsDefined(targetType, value.ToString())) { + if (EnumExtensions.TryEnumIsDefined(targetType, value.ToString())) + { object parsedValue = Enum.Parse(targetType, value.ToString(), false); return (T)parsedValue; } @@ -79,31 +91,41 @@ public static T ToType(this object value, ISerializer serializer = null) { if (targetTypeInfo.IsEnum && valueType.IsNumeric()) return (T)Enum.ToObject(targetType, value); - if (converter.CanConvertFrom(valueType)) { + if (converter.CanConvertFrom(valueType)) + { object convertedValue = converter.ConvertFrom(value); return (T)convertedValue; } - if (serializer != null && value is byte[] data) { - try { + if (serializer != null && value is byte[] data) + { + try + { return serializer.Deserialize(data); - } catch { } + } + catch { } } - if (serializer != null && value is string stringValue) { - try { + if (serializer != null && value is string stringValue) + { + try + { return serializer.Deserialize(stringValue); - } catch { } + } + catch { } } - if (value is IConvertible) { - try { + if (value is IConvertible) + { + try + { object convertedValue = Convert.ChangeType(value, targetType); return (T)convertedValue; - } catch { } + } + catch { } } throw new ArgumentException($"An incompatible value specified. Target Type: {targetType.FullName} Value Type: {value.GetType().FullName}", nameof(value)); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/IJob.cs b/src/Foundatio/Jobs/IJob.cs index 8fe9a786a..039d5d9a5 100644 --- a/src/Foundatio/Jobs/IJob.cs +++ b/src/Foundatio/Jobs/IJob.cs @@ -6,42 +6,57 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Jobs { - public interface IJob { +namespace Foundatio.Jobs +{ + public interface IJob + { Task RunAsync(CancellationToken cancellationToken = default); } - public static class JobExtensions { - public static async Task TryRunAsync(this IJob job, CancellationToken cancellationToken = default) { - try { + public static class JobExtensions + { + public static async Task TryRunAsync(this IJob job, CancellationToken cancellationToken = default) + { + try + { return await job.RunAsync(cancellationToken).AnyContext(); - } catch (OperationCanceledException) { + } + catch (OperationCanceledException) + { return JobResult.Cancelled; - } catch (Exception ex) { + } + catch (Exception ex) + { return JobResult.FromException(ex); } } - public static async Task RunContinuousAsync(this IJob job, TimeSpan? interval = null, int iterationLimit = -1, CancellationToken cancellationToken = default, Func> continuationCallback = null) { + public static async Task RunContinuousAsync(this IJob job, TimeSpan? interval = null, int iterationLimit = -1, CancellationToken cancellationToken = default, Func> continuationCallback = null) + { int iterations = 0; string jobName = job.GetType().Name; var logger = job.GetLogger(); - using (logger.BeginScope(new Dictionary {{ "job", jobName }})) { + using (logger.BeginScope(new Dictionary { { "job", jobName } })) + { if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Starting continuous job type {JobName} on machine {MachineName}...", jobName, Environment.MachineName); - while (!cancellationToken.IsCancellationRequested) { + while (!cancellationToken.IsCancellationRequested) + { var result = await job.TryRunAsync(cancellationToken).AnyContext(); logger.LogJobResult(result, jobName); iterations++; if (cancellationToken.IsCancellationRequested || (iterationLimit > -1 && iterationLimit <= iterations)) - break; + break; - if (result.Error != null) { + if (result.Error != null) + { await SystemClock.SleepSafeAsync(Math.Max((int)(interval?.TotalMilliseconds ?? 0), 100), cancellationToken).AnyContext(); - } else if (interval.HasValue && interval.Value > TimeSpan.Zero) { + } + else if (interval.HasValue && interval.Value > TimeSpan.Zero) + { await SystemClock.SleepSafeAsync(interval.Value, cancellationToken).AnyContext(); } @@ -54,10 +69,13 @@ public static async Task RunContinuousAsync(this IJob job, TimeSpan? interval = if (continuationCallback == null) continue; - try { + try + { if (!await continuationCallback().AnyContext()) break; - } catch (Exception ex) { + } + catch (Exception ex) + { if (logger.IsEnabled(LogLevel.Error)) logger.LogError(ex, "Error in continuation callback: {Message}", ex.Message); } diff --git a/src/Foundatio/Jobs/IQueueJob.cs b/src/Foundatio/Jobs/IQueueJob.cs index 989d10a2f..19eff3eda 100644 --- a/src/Foundatio/Jobs/IQueueJob.cs +++ b/src/Foundatio/Jobs/IQueueJob.cs @@ -1,13 +1,15 @@ using System; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Queues; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public interface IQueueJob : IJob where T : class { +namespace Foundatio.Jobs +{ + public interface IQueueJob : IJob where T : class + { /// /// Processes a queue entry and returns the result. This method is typically called from RunAsync() /// but can also be called from a function passing in the queue entry. @@ -16,10 +18,13 @@ public interface IQueueJob : IJob where T : class { IQueue Queue { get; } } - public static class QueueJobExtensions { - public static Task RunUntilEmptyAsync(this IQueueJob job, CancellationToken cancellationToken = default) where T : class { + public static class QueueJobExtensions + { + public static Task RunUntilEmptyAsync(this IQueueJob job, CancellationToken cancellationToken = default) where T : class + { var logger = job.GetLogger(); - return job.RunContinuousAsync(cancellationToken: cancellationToken, continuationCallback: async () => { + return job.RunContinuousAsync(cancellationToken: cancellationToken, continuationCallback: async () => + { // Allow abandoned items to be added in a background task. Thread.Yield(); @@ -31,4 +36,4 @@ public static Task RunUntilEmptyAsync(this IQueueJob job, CancellationToke }); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobAttribute.cs b/src/Foundatio/Jobs/JobAttribute.cs index fc1e30207..65a26889d 100644 --- a/src/Foundatio/Jobs/JobAttribute.cs +++ b/src/Foundatio/Jobs/JobAttribute.cs @@ -1,8 +1,10 @@ using System; -namespace Foundatio.Jobs { +namespace Foundatio.Jobs +{ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class JobAttribute : Attribute { + public class JobAttribute : Attribute + { public string Name { get; set; } public string Description { get; set; } public bool IsContinuous { get; set; } = true; @@ -11,4 +13,4 @@ public class JobAttribute : Attribute { public int IterationLimit { get; set; } = -1; public int InstanceCount { get; set; } = 1; } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobBase.cs b/src/Foundatio/Jobs/JobBase.cs index eff97588b..599ce99fc 100644 --- a/src/Foundatio/Jobs/JobBase.cs +++ b/src/Foundatio/Jobs/JobBase.cs @@ -5,21 +5,25 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public abstract class JobBase : IJob, IHaveLogger { +namespace Foundatio.Jobs +{ + public abstract class JobBase : IJob, IHaveLogger + { protected readonly ILogger _logger; - public JobBase(ILoggerFactory loggerFactory = null) { + public JobBase(ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); ILogger IHaveLogger.Logger => _logger; - public virtual Task RunAsync(CancellationToken cancellationToken = default) { + public virtual Task RunAsync(CancellationToken cancellationToken = default) + { return RunInternalAsync(new JobContext(cancellationToken)); } protected abstract Task RunInternalAsync(JobContext context); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobContext.cs b/src/Foundatio/Jobs/JobContext.cs index 0b635431a..8755d7491 100644 --- a/src/Foundatio/Jobs/JobContext.cs +++ b/src/Foundatio/Jobs/JobContext.cs @@ -2,9 +2,12 @@ using System.Threading.Tasks; using Foundatio.Lock; -namespace Foundatio.Jobs { - public class JobContext { - public JobContext(CancellationToken cancellationToken, ILock lck = null) { +namespace Foundatio.Jobs +{ + public class JobContext + { + public JobContext(CancellationToken cancellationToken, ILock lck = null) + { Lock = lck; CancellationToken = cancellationToken; } @@ -12,11 +15,12 @@ public JobContext(CancellationToken cancellationToken, ILock lck = null) { public ILock Lock { get; } public CancellationToken CancellationToken { get; } - public virtual Task RenewLockAsync() { + public virtual Task RenewLockAsync() + { if (Lock != null) return Lock.RenewAsync(); return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobOptions.cs b/src/Foundatio/Jobs/JobOptions.cs index ee7c7d6c6..338e0dc5a 100644 --- a/src/Foundatio/Jobs/JobOptions.cs +++ b/src/Foundatio/Jobs/JobOptions.cs @@ -2,8 +2,10 @@ using System.Reflection; using Foundatio.Utility; -namespace Foundatio.Jobs { - public class JobOptions { +namespace Foundatio.Jobs +{ + public class JobOptions + { public string Name { get; set; } public string Description { get; set; } public Func JobFactory { get; set; } @@ -13,17 +15,20 @@ public class JobOptions { public int IterationLimit { get; set; } = -1; public int InstanceCount { get; set; } = 1; - public static JobOptions GetDefaults(Type jobType) { + public static JobOptions GetDefaults(Type jobType) + { var jobOptions = new JobOptions(); ApplyDefaults(jobOptions, jobType); return jobOptions; } - public static void ApplyDefaults(JobOptions jobOptions, Type jobType) { + public static void ApplyDefaults(JobOptions jobOptions, Type jobType) + { var jobAttribute = jobType.GetCustomAttribute() ?? new JobAttribute(); jobOptions.Name = jobAttribute.Name; - if (String.IsNullOrEmpty(jobOptions.Name)) { + if (String.IsNullOrEmpty(jobOptions.Name)) + { string jobName = jobType.Name; if (jobName.EndsWith("Job")) jobName = jobName.Substring(0, jobName.Length - 3); @@ -34,13 +39,15 @@ public static void ApplyDefaults(JobOptions jobOptions, Type jobType) { jobOptions.Description = jobAttribute.Description; jobOptions.RunContinuous = jobAttribute.IsContinuous; - if (!String.IsNullOrEmpty(jobAttribute.Interval)) { + if (!String.IsNullOrEmpty(jobAttribute.Interval)) + { TimeSpan? interval; if (TimeUnit.TryParse(jobAttribute.Interval, out interval)) jobOptions.Interval = interval; } - if (!String.IsNullOrEmpty(jobAttribute.InitialDelay)) { + if (!String.IsNullOrEmpty(jobAttribute.InitialDelay)) + { TimeSpan? delay; if (TimeUnit.TryParse(jobAttribute.InitialDelay, out delay)) jobOptions.InitialDelay = delay; @@ -50,38 +57,45 @@ public static void ApplyDefaults(JobOptions jobOptions, Type jobType) { jobOptions.InstanceCount = jobAttribute.InstanceCount; } - public static JobOptions GetDefaults() where T : IJob { + public static JobOptions GetDefaults() where T : IJob + { return GetDefaults(typeof(T)); } - public static JobOptions GetDefaults(IJob instance) { + public static JobOptions GetDefaults(IJob instance) + { var jobOptions = GetDefaults(instance.GetType()); jobOptions.JobFactory = () => instance; return jobOptions; } - public static JobOptions GetDefaults(IJob instance) where T : IJob { + public static JobOptions GetDefaults(IJob instance) where T : IJob + { var jobOptions = GetDefaults(); jobOptions.JobFactory = () => instance; return jobOptions; } - public static JobOptions GetDefaults(Type jobType, Func jobFactory) { + public static JobOptions GetDefaults(Type jobType, Func jobFactory) + { var jobOptions = GetDefaults(jobType); jobOptions.JobFactory = jobFactory; return jobOptions; } - public static JobOptions GetDefaults(Func jobFactory) where T : IJob { + public static JobOptions GetDefaults(Func jobFactory) where T : IJob + { var jobOptions = GetDefaults(); jobOptions.JobFactory = jobFactory; return jobOptions; } } - public static class JobOptionExtensions { - public static void ApplyDefaults(this JobOptions jobOptions) { + public static class JobOptionExtensions + { + public static void ApplyDefaults(this JobOptions jobOptions) + { JobOptions.ApplyDefaults(jobOptions, typeof(T)); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobResult.cs b/src/Foundatio/Jobs/JobResult.cs index 9d63a9eb2..ae1cc63b5 100644 --- a/src/Foundatio/Jobs/JobResult.cs +++ b/src/Foundatio/Jobs/JobResult.cs @@ -1,59 +1,75 @@ using System; using Microsoft.Extensions.Logging; -namespace Foundatio.Jobs { - public class JobResult { +namespace Foundatio.Jobs +{ + public class JobResult + { public bool IsCancelled { get; set; } public Exception Error { get; set; } public string Message { get; set; } public bool IsSuccess { get; set; } - public static readonly JobResult None = new() { + public static readonly JobResult None = new() + { IsSuccess = true, Message = String.Empty }; - public static readonly JobResult Cancelled = new() { + public static readonly JobResult Cancelled = new() + { IsCancelled = true }; - public static readonly JobResult Success = new() { + public static readonly JobResult Success = new() + { IsSuccess = true }; - public static JobResult FromException(Exception exception, string message = null) { - return new JobResult { + public static JobResult FromException(Exception exception, string message = null) + { + return new JobResult + { Error = exception, IsSuccess = false, Message = message ?? exception.Message }; } - public static JobResult CancelledWithMessage(string message) { - return new JobResult { + public static JobResult CancelledWithMessage(string message) + { + return new JobResult + { IsCancelled = true, Message = message }; } - public static JobResult SuccessWithMessage(string message) { - return new JobResult { + public static JobResult SuccessWithMessage(string message) + { + return new JobResult + { IsSuccess = true, Message = message }; } - public static JobResult FailedWithMessage(string message) { - return new JobResult { + public static JobResult FailedWithMessage(string message) + { + return new JobResult + { IsSuccess = false, Message = message }; } } - public static class JobResultExtensions { - public static void LogJobResult(this ILogger logger, JobResult result, string jobName) { - if (result == null) { + public static class JobResultExtensions + { + public static void LogJobResult(this ILogger logger, JobResult result, string jobName) + { + if (result == null) + { if (logger.IsEnabled(LogLevel.Error)) logger.LogError("Null job run result for {JobName}.", jobName); @@ -70,4 +86,4 @@ public static void LogJobResult(this ILogger logger, JobResult result, string jo logger.LogDebug("Job run {JobName} succeeded.", jobName); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/JobRunner.cs b/src/Foundatio/Jobs/JobRunner.cs index 3d48c2648..25472215e 100644 --- a/src/Foundatio/Jobs/JobRunner.cs +++ b/src/Foundatio/Jobs/JobRunner.cs @@ -8,52 +8,65 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public class JobRunner { +namespace Foundatio.Jobs +{ + public class JobRunner + { private readonly ILogger _logger; private string _jobName; private readonly JobOptions _options; - public JobRunner(JobOptions options, ILoggerFactory loggerFactory = null) { + public JobRunner(JobOptions options, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _options = options; } public JobRunner(IJob instance, ILoggerFactory loggerFactory = null, TimeSpan? initialDelay = null, int instanceCount = 1, bool runContinuous = true, int iterationLimit = -1, TimeSpan? interval = null) - : this(new JobOptions { - JobFactory = () => instance, - InitialDelay = initialDelay, - InstanceCount = instanceCount, - IterationLimit = iterationLimit, - RunContinuous = runContinuous, - Interval = interval - }, loggerFactory) { - } + : this(new JobOptions + { + JobFactory = () => instance, + InitialDelay = initialDelay, + InstanceCount = instanceCount, + IterationLimit = iterationLimit, + RunContinuous = runContinuous, + Interval = interval + }, loggerFactory) + { + } public JobRunner(Func jobFactory, ILoggerFactory loggerFactory = null, TimeSpan? initialDelay = null, int instanceCount = 1, bool runContinuous = true, int iterationLimit = -1, TimeSpan? interval = null) - : this(new JobOptions { + : this(new JobOptions + { JobFactory = jobFactory, InitialDelay = initialDelay, InstanceCount = instanceCount, IterationLimit = iterationLimit, RunContinuous = runContinuous, Interval = interval - }, loggerFactory) {} + }, loggerFactory) + { } public CancellationTokenSource CancellationTokenSource { get; private set; } - public async Task RunInConsoleAsync() { + public async Task RunInConsoleAsync() + { int result; - try { + try + { CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(GetShutdownCancellationToken(_logger)); bool success = await RunAsync(CancellationTokenSource.Token).AnyContext(); result = success ? 0 : -1; if (Debugger.IsAttached) Console.ReadKey(); - } catch (TaskCanceledException) { + } + catch (TaskCanceledException) + { return 0; - } catch (FileNotFoundException e) { + } + catch (FileNotFoundException e) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError("{Message} ({FileName})", e.GetMessage(), e.FileName); @@ -61,7 +74,9 @@ public async Task RunInConsoleAsync() { Console.ReadKey(); return 1; - } catch (Exception e) { + } + catch (Exception e) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(e, "Job {JobName} error: {Message}", _jobName, e.GetMessage()); @@ -74,49 +89,67 @@ public async Task RunInConsoleAsync() { return result; } - public void RunInBackground(CancellationToken cancellationToken = default) { - if (_options.InstanceCount == 1) { - _ = Task.Run(async () => { - try { + public void RunInBackground(CancellationToken cancellationToken = default) + { + if (_options.InstanceCount == 1) + { + _ = Task.Run(async () => + { + try + { await RunAsync(cancellationToken).AnyContext(); - } catch (TaskCanceledException) { - } catch (Exception ex) { + } + catch (TaskCanceledException) + { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error running job in background: {Message}", ex.Message); throw; } }, cancellationToken); - } else { + } + else + { var ignored = RunAsync(cancellationToken); } } - public async Task RunAsync(CancellationToken cancellationToken = default) { - if (_options.JobFactory == null) { + public async Task RunAsync(CancellationToken cancellationToken = default) + { + if (_options.JobFactory == null) + { _logger.LogError("JobFactory must be specified"); return false; } IJob job = null; - try { + try + { job = _options.JobFactory(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error creating job instance from JobFactory"); return false; } - if (job == null) { + if (job == null) + { _logger.LogError("JobFactory returned null job instance"); return false; } _jobName = TypeHelper.GetTypeDisplayName(job.GetType()); - using (_logger.BeginScope(s => s.Property("job", _jobName ))) { + using (_logger.BeginScope(s => s.Property("job", _jobName))) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Starting job type {JobName} on machine {MachineName}...", _jobName, Environment.MachineName); var jobLifetime = job as IAsyncLifetime; - if (jobLifetime != null) { + if (jobLifetime != null) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Initializing job lifetime {JobName} on machine {MachineName}...", _jobName, Environment.MachineName); await jobLifetime.InitializeAsync().AnyContext(); @@ -124,19 +157,28 @@ public async Task RunAsync(CancellationToken cancellationToken = default) _logger.LogInformation("Done initializing job lifetime {JobName} on machine {MachineName}.", _jobName, Environment.MachineName); } - try { + try + { if (_options.InitialDelay.HasValue && _options.InitialDelay.Value > TimeSpan.Zero) await SystemClock.SleepAsync(_options.InitialDelay.Value, cancellationToken).AnyContext(); - if (_options.RunContinuous && _options.InstanceCount > 1) { + if (_options.RunContinuous && _options.InstanceCount > 1) + { var tasks = new List(_options.InstanceCount); - for (int i = 0; i < _options.InstanceCount; i++) { - tasks.Add(Task.Run(async () => { - try { + for (int i = 0; i < _options.InstanceCount; i++) + { + tasks.Add(Task.Run(async () => + { + try + { var jobInstance = _options.JobFactory(); await jobInstance.RunContinuousAsync(_options.Interval, _options.IterationLimit, cancellationToken).AnyContext(); - } catch (TaskCanceledException) { - } catch (Exception ex) { + } + catch (TaskCanceledException) + { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error running job instance: {Message}", ex.Message); throw; @@ -145,17 +187,24 @@ public async Task RunAsync(CancellationToken cancellationToken = default) } await Task.WhenAll(tasks).AnyContext(); - } else if (_options.RunContinuous && _options.InstanceCount == 1) { + } + else if (_options.RunContinuous && _options.InstanceCount == 1) + { await job.RunContinuousAsync(_options.Interval, _options.IterationLimit, cancellationToken).AnyContext(); - } else { + } + else + { var result = await job.TryRunAsync(cancellationToken).AnyContext(); _logger.LogJobResult(result, _jobName); return result.IsSuccess; } - } finally { + } + finally + { var jobDisposable = job as IAsyncDisposable; - if (jobDisposable != null) { + if (jobDisposable != null) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Disposing job lifetime {JobName} on machine {MachineName}...", _jobName, Environment.MachineName); await jobDisposable.DisposeAsync().AnyContext(); @@ -170,16 +219,19 @@ public async Task RunAsync(CancellationToken cancellationToken = default) private static CancellationTokenSource _jobShutdownCancellationTokenSource; private static readonly object _lock = new(); - public static CancellationToken GetShutdownCancellationToken(ILogger logger = null) { + public static CancellationToken GetShutdownCancellationToken(ILogger logger = null) + { if (_jobShutdownCancellationTokenSource != null) return _jobShutdownCancellationTokenSource.Token; - lock (_lock) { + lock (_lock) + { if (_jobShutdownCancellationTokenSource != null) return _jobShutdownCancellationTokenSource.Token; _jobShutdownCancellationTokenSource = new CancellationTokenSource(); - Console.CancelKeyPress += (sender, args) => { + Console.CancelKeyPress += (sender, args) => + { _jobShutdownCancellationTokenSource.Cancel(); if (logger != null & logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Job shutdown event signaled: {SpecialKey}", args.SpecialKey); @@ -190,7 +242,8 @@ public static CancellationToken GetShutdownCancellationToken(ILogger logger = nu if (String.IsNullOrEmpty(webJobsShutdownFile)) return _jobShutdownCancellationTokenSource.Token; - var handler = new FileSystemEventHandler((s, e) => { + var handler = new FileSystemEventHandler((s, e) => + { if (e.FullPath.IndexOf(Path.GetFileName(webJobsShutdownFile), StringComparison.OrdinalIgnoreCase) < 0) return; diff --git a/src/Foundatio/Jobs/JobWithLockBase.cs b/src/Foundatio/Jobs/JobWithLockBase.cs index e1fb38040..a95e612ee 100644 --- a/src/Foundatio/Jobs/JobWithLockBase.cs +++ b/src/Foundatio/Jobs/JobWithLockBase.cs @@ -1,32 +1,40 @@ using System; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Lock; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public abstract class JobWithLockBase : IJob, IHaveLogger { +namespace Foundatio.Jobs +{ + public abstract class JobWithLockBase : IJob, IHaveLogger + { protected readonly ILogger _logger; - public JobWithLockBase(ILoggerFactory loggerFactory = null) { + public JobWithLockBase(ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); ILogger IHaveLogger.Logger => _logger; - public async virtual Task RunAsync(CancellationToken cancellationToken = default) { + public async virtual Task RunAsync(CancellationToken cancellationToken = default) + { var lockValue = await GetLockAsync(cancellationToken).AnyContext(); - if (lockValue == null) { + if (lockValue == null) + { _logger.LogTrace("Unable to acquire job lock"); return JobResult.Success; } - try { + try + { return await RunInternalAsync(new JobContext(cancellationToken, lockValue)).AnyContext(); - } finally { + } + finally + { await lockValue.ReleaseAsync().AnyContext(); } } @@ -35,4 +43,4 @@ public async virtual Task RunAsync(CancellationToken cancellationToke protected abstract Task GetLockAsync(CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/QueueEntryContext.cs b/src/Foundatio/Jobs/QueueEntryContext.cs index ac4ddef12..f0ed993f1 100644 --- a/src/Foundatio/Jobs/QueueEntryContext.cs +++ b/src/Foundatio/Jobs/QueueEntryContext.cs @@ -1,22 +1,26 @@ using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Lock; using Foundatio.Queues; +using Foundatio.Utility; -namespace Foundatio.Jobs { - public class QueueEntryContext : JobContext where T : class { - public QueueEntryContext(IQueueEntry queueEntry, ILock queueEntryLock, CancellationToken cancellationToken = default) : base(cancellationToken, queueEntryLock) { +namespace Foundatio.Jobs +{ + public class QueueEntryContext : JobContext where T : class + { + public QueueEntryContext(IQueueEntry queueEntry, ILock queueEntryLock, CancellationToken cancellationToken = default) : base(cancellationToken, queueEntryLock) + { QueueEntry = queueEntry; } public IQueueEntry QueueEntry { get; private set; } - public override async Task RenewLockAsync() { + public override async Task RenewLockAsync() + { if (QueueEntry != null) await QueueEntry.RenewLockAsync().AnyContext(); await base.RenewLockAsync().AnyContext(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/QueueJobBase.cs b/src/Foundatio/Jobs/QueueJobBase.cs index fb2d26053..3a332fc55 100644 --- a/src/Foundatio/Jobs/QueueJobBase.cs +++ b/src/Foundatio/Jobs/QueueJobBase.cs @@ -9,33 +9,41 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public abstract class QueueJobBase : IQueueJob, IHaveLogger where T : class { +namespace Foundatio.Jobs +{ + public abstract class QueueJobBase : IQueueJob, IHaveLogger where T : class + { protected readonly ILogger _logger; protected readonly Lazy> _queue; protected readonly string _queueEntryName = typeof(T).Name; - public QueueJobBase(Lazy> queue, ILoggerFactory loggerFactory = null) { + public QueueJobBase(Lazy> queue, ILoggerFactory loggerFactory = null) + { _queue = queue; _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; AutoComplete = true; } - public QueueJobBase(IQueue queue, ILoggerFactory loggerFactory = null) : this(new Lazy>(() => queue), loggerFactory) {} + public QueueJobBase(IQueue queue, ILoggerFactory loggerFactory = null) : this(new Lazy>(() => queue), loggerFactory) { } protected bool AutoComplete { get; set; } public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); IQueue IQueueJob.Queue => _queue.Value; ILogger IHaveLogger.Logger => _logger; - public virtual async Task RunAsync(CancellationToken cancellationToken = default) { + public virtual async Task RunAsync(CancellationToken cancellationToken = default) + { IQueueEntry queueEntry; using (var timeoutCancellationTokenSource = new CancellationTokenSource(30000)) - using (var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - try { + using (var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) + { + try + { queueEntry = await _queue.Value.DequeueAsync(linkedCancellationToken.Token).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { return JobResult.FromException(ex, $"Error trying to dequeue message: {ex.Message}"); } } @@ -43,7 +51,8 @@ public virtual async Task RunAsync(CancellationToken cancellationToke return await ProcessAsync(queueEntry, cancellationToken).AnyContext(); } - public async Task ProcessAsync(IQueueEntry queueEntry, CancellationToken cancellationToken) { + public async Task ProcessAsync(IQueueEntry queueEntry, CancellationToken cancellationToken) + { if (queueEntry == null) return JobResult.Success; @@ -53,10 +62,11 @@ public async Task ProcessAsync(IQueueEntry queueEntry, Cancellatio .Property("QueueEntryId", queueEntry.Id) .PropertyIf("CorrelationId", queueEntry.CorrelationId, !String.IsNullOrEmpty(queueEntry.CorrelationId)) .Property("QueueEntryName", _queueEntryName)); - + _logger.LogInformation("Processing queue entry: id={QueueEntryId} type={QueueEntryName} attempt={QueueEntryAttempt}", queueEntry.Id, _queueEntryName, queueEntry.Attempts); - if (cancellationToken.IsCancellationRequested) { + if (cancellationToken.IsCancellationRequested) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Job was cancelled. Abandoning {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); @@ -65,24 +75,29 @@ public async Task ProcessAsync(IQueueEntry queueEntry, Cancellatio } var lockValue = await GetQueueEntryLockAsync(queueEntry, cancellationToken).AnyContext(); - if (lockValue == null) { + if (lockValue == null) + { await queueEntry.AbandonAsync().AnyContext(); _logger.LogTrace("Unable to acquire queue entry lock"); return JobResult.Success; } bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - try { + try + { LogProcessingQueueEntry(queueEntry); var result = await ProcessQueueEntryAsync(new QueueEntryContext(queueEntry, lockValue, cancellationToken)).AnyContext(); if (!AutoComplete || queueEntry.IsCompleted || queueEntry.IsAbandoned) return result; - if (result.IsSuccess) { + if (result.IsSuccess) + { await queueEntry.CompleteAsync().AnyContext(); LogAutoCompletedQueueEntry(queueEntry); - } else { + } + else + { if (result.Error != null || result.Message != null) _logger.LogError(result.Error, "{QueueEntryName} queue entry {Id} returned an unsuccessful response: {Message}", _queueEntryName, queueEntry.Id, result.Message ?? result.Error?.Message); @@ -94,14 +109,18 @@ public async Task ProcessAsync(IQueueEntry queueEntry, Cancellatio } return result; - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error processing {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); if (!queueEntry.IsCompleted && !queueEntry.IsAbandoned) await queueEntry.AbandonAsync().AnyContext(); throw; - } finally { + } + finally + { if (isTraceLogLevelEnabled) _logger.LogTrace("Releasing Lock for {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); await lockValue.ReleaseAsync().AnyContext(); @@ -110,7 +129,8 @@ public async Task ProcessAsync(IQueueEntry queueEntry, Cancellatio } } - protected virtual Activity StartProcessQueueEntryActivity(IQueueEntry entry) { + protected virtual Activity StartProcessQueueEntryActivity(IQueueEntry entry) + { var activity = FoundatioDiagnostics.ActivitySource.StartActivity("ProcessQueueEntry", ActivityKind.Server, entry.CorrelationId); if (activity == null) @@ -126,7 +146,8 @@ protected virtual Activity StartProcessQueueEntryActivity(IQueueEntry entry) return activity; } - protected virtual void EnrichProcessQueueEntryActivity(Activity activity, IQueueEntry entry) { + protected virtual void EnrichProcessQueueEntryActivity(Activity activity, IQueueEntry entry) + { if (!activity.IsAllDataRequested) return; @@ -136,30 +157,34 @@ protected virtual void EnrichProcessQueueEntryActivity(Activity activity, IQueue if (entry.Properties == null || entry.Properties.Count <= 0) return; - - foreach (var p in entry.Properties) { + + foreach (var p in entry.Properties) + { if (p.Key != "TraceState") activity.AddTag(p.Key, p.Value); } } - - protected virtual void LogProcessingQueueEntry(IQueueEntry queueEntry) { + + protected virtual void LogProcessingQueueEntry(IQueueEntry queueEntry) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Processing {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); } - protected virtual void LogAutoCompletedQueueEntry(IQueueEntry queueEntry) { + protected virtual void LogAutoCompletedQueueEntry(IQueueEntry queueEntry) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Auto completed {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); } protected abstract Task ProcessQueueEntryAsync(QueueEntryContext context); - protected virtual Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = default) { + protected virtual Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = default) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Returning Empty Lock for {QueueEntryName} queue entry: {Id}", _queueEntryName, queueEntry.Id); - + return Task.FromResult(Disposable.EmptyLock); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemContext.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemContext.cs index 9ea073efd..33739cb93 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemContext.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemContext.cs @@ -3,11 +3,14 @@ using System.Threading.Tasks; using Foundatio.Lock; -namespace Foundatio.Jobs { - public class WorkItemContext { +namespace Foundatio.Jobs +{ + public class WorkItemContext + { private readonly Func _progressCallback; - public WorkItemContext(object data, string jobId, ILock workItemLock, CancellationToken cancellationToken, Func progressCallback) { + public WorkItemContext(object data, string jobId, ILock workItemLock, CancellationToken cancellationToken, Func progressCallback) + { Data = data; JobId = jobId; WorkItemLock = workItemLock; @@ -21,19 +24,22 @@ public WorkItemContext(object data, string jobId, ILock workItemLock, Cancellati public JobResult Result { get; set; } = JobResult.Success; public CancellationToken CancellationToken { get; private set; } - public Task ReportProgressAsync(int progress, string message = null) { + public Task ReportProgressAsync(int progress, string message = null) + { return _progressCallback(progress, message); } - public Task RenewLockAsync() { + public Task RenewLockAsync() + { if (WorkItemLock != null) return WorkItemLock.RenewAsync(); return Task.CompletedTask; } - public T GetData() where T : class { + public T GetData() where T : class + { return Data as T; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemData.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemData.cs index ea2682edc..ab3ceeb2a 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemData.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemData.cs @@ -2,8 +2,10 @@ using Foundatio.Metrics; using Foundatio.Queues; -namespace Foundatio.Jobs { - public class WorkItemData : IHaveSubMetricName, IHaveUniqueIdentifier { +namespace Foundatio.Jobs +{ + public class WorkItemData : IHaveSubMetricName, IHaveUniqueIdentifier + { public string WorkItemId { get; set; } public string Type { get; set; } public byte[] Data { get; set; } @@ -11,4 +13,4 @@ public class WorkItemData : IHaveSubMetricName, IHaveUniqueIdentifier { public string UniqueIdentifier { get; set; } public string SubMetricName { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemHandlers.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemHandlers.cs index 0b6f83a81..389aff5c2 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemHandlers.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemHandlers.cs @@ -8,27 +8,34 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Jobs { - public class WorkItemHandlers { +namespace Foundatio.Jobs +{ + public class WorkItemHandlers + { private readonly ConcurrentDictionary> _handlers; - public WorkItemHandlers() { + public WorkItemHandlers() + { _handlers = new ConcurrentDictionary>(); } - public void Register(IWorkItemHandler handler) { + public void Register(IWorkItemHandler handler) + { _handlers.TryAdd(typeof(T), new Lazy(() => handler)); } - public void Register(Func handler) { + public void Register(Func handler) + { _handlers.TryAdd(typeof(T), new Lazy(handler)); } - public void Register(Func handler, ILogger logger = null, Action, Type, object> logProcessingWorkItem = null, Action, Type, object> logAutoCompletedWorkItem = null) where T : class { + public void Register(Func handler, ILogger logger = null, Action, Type, object> logProcessingWorkItem = null, Action, Type, object> logAutoCompletedWorkItem = null) where T : class + { _handlers.TryAdd(typeof(T), new Lazy(() => new DelegateWorkItemHandler(handler, logger, logProcessingWorkItem, logAutoCompletedWorkItem))); } - public IWorkItemHandler GetHandler(Type jobDataType) { + public IWorkItemHandler GetHandler(Type jobDataType) + { if (!_handlers.TryGetValue(jobDataType, out var handler)) return null; @@ -36,7 +43,8 @@ public IWorkItemHandler GetHandler(Type jobDataType) { } } - public interface IWorkItemHandler { + public interface IWorkItemHandler + { Task GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = default); Task HandleItemAsync(WorkItemContext context); bool AutoRenewLockOnProgress { get; set; } @@ -45,68 +53,80 @@ public interface IWorkItemHandler { void LogAutoCompletedQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem); } - public abstract class WorkItemHandlerBase : IWorkItemHandler { - public WorkItemHandlerBase(ILoggerFactory loggerFactory = null) { + public abstract class WorkItemHandlerBase : IWorkItemHandler + { + public WorkItemHandlerBase(ILoggerFactory loggerFactory = null) + { Log = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } - public WorkItemHandlerBase(ILogger logger) { + public WorkItemHandlerBase(ILogger logger) + { Log = logger ?? NullLogger.Instance; } - public virtual Task GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = default) { + public virtual Task GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = default) + { return Task.FromResult(Disposable.EmptyLock); } public bool AutoRenewLockOnProgress { get; set; } public ILogger Log { get; set; } - public virtual void LogProcessingQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) { + public virtual void LogProcessingQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) + { if (Log.IsEnabled(LogLevel.Information)) Log.LogInformation("Processing {TypeName} work item queue entry: {Id}.", workItemDataType.Name, queueEntry.Id); } - public virtual void LogAutoCompletedQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) { + public virtual void LogAutoCompletedQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) + { if (Log.IsEnabled(LogLevel.Information)) Log.LogInformation("Auto completed {TypeName} work item queue entry: {Id}.", workItemDataType.Name, queueEntry.Id); } public abstract Task HandleItemAsync(WorkItemContext context); - protected int CalculateProgress(long total, long completed, int startProgress = 0, int endProgress = 100) { + protected int CalculateProgress(long total, long completed, int startProgress = 0, int endProgress = 100) + { return startProgress + (int)((100 * (double)completed / total) * (((double)endProgress - startProgress) / 100)); } } - public class DelegateWorkItemHandler : WorkItemHandlerBase { + public class DelegateWorkItemHandler : WorkItemHandlerBase + { private readonly Func _handler; private readonly Action, Type, object> _logProcessingWorkItem; private readonly Action, Type, object> _logAutoCompletedWorkItem; - public DelegateWorkItemHandler(Func handler, ILogger logger = null, Action, Type, object> logProcessingWorkItem = null, Action, Type, object> logAutoCompletedWorkItem = null) : base(logger) { + public DelegateWorkItemHandler(Func handler, ILogger logger = null, Action, Type, object> logProcessingWorkItem = null, Action, Type, object> logAutoCompletedWorkItem = null) : base(logger) + { _handler = handler; _logProcessingWorkItem = logProcessingWorkItem; _logAutoCompletedWorkItem = logAutoCompletedWorkItem; } - public override Task HandleItemAsync(WorkItemContext context) { + public override Task HandleItemAsync(WorkItemContext context) + { if (_handler == null) return Task.CompletedTask; return _handler(context); } - public override void LogProcessingQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) { + public override void LogProcessingQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) + { if (_logProcessingWorkItem != null) _logProcessingWorkItem(queueEntry, workItemDataType, workItem); else base.LogProcessingQueueEntry(queueEntry, workItemDataType, workItem); } - public override void LogAutoCompletedQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) { + public override void LogAutoCompletedQueueEntry(IQueueEntry queueEntry, Type workItemDataType, object workItem) + { if (_logAutoCompletedWorkItem != null) _logAutoCompletedWorkItem(queueEntry, workItemDataType, workItem); else base.LogAutoCompletedQueueEntry(queueEntry, workItemDataType, workItem); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemJob.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemJob.cs index cfc708af9..6feb9393b 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemJob.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemJob.cs @@ -1,24 +1,27 @@ using System; +using System.Collections.Concurrent; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Messaging; using Foundatio.Queues; using Foundatio.Serializer; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using System.Collections.Concurrent; -using System.Diagnostics; -namespace Foundatio.Jobs { +namespace Foundatio.Jobs +{ [Job(Description = "Processes adhoc work item queues entries")] - public class WorkItemJob : IQueueJob, IHaveLogger { + public class WorkItemJob : IQueueJob, IHaveLogger + { protected readonly IMessagePublisher _publisher; protected readonly WorkItemHandlers _handlers; protected readonly IQueue _queue; protected readonly ILogger _logger; - public WorkItemJob(IQueue queue, IMessagePublisher publisher, WorkItemHandlers handlers, ILoggerFactory loggerFactory = null) { + public WorkItemJob(IQueue queue, IMessagePublisher publisher, WorkItemHandlers handlers, ILoggerFactory loggerFactory = null) + { _publisher = publisher; _handlers = handlers; _queue = queue; @@ -29,14 +32,19 @@ public WorkItemJob(IQueue queue, IMessagePublisher publisher, Work IQueue IQueueJob.Queue => _queue; ILogger IHaveLogger.Logger => _logger; - public async virtual Task RunAsync(CancellationToken cancellationToken = default) { + public async virtual Task RunAsync(CancellationToken cancellationToken = default) + { IQueueEntry queueEntry; using (var timeoutCancellationTokenSource = new CancellationTokenSource(30000)) - using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - try { + using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) + { + try + { queueEntry = await _queue.DequeueAsync(linkedCancellationTokenSource.Token).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { return JobResult.FromException(ex, $"Error trying to dequeue work item: {ex.Message}"); } } @@ -44,17 +52,20 @@ public async virtual Task RunAsync(CancellationToken cancellationToke return await ProcessAsync(queueEntry, cancellationToken).AnyContext(); } - public async Task ProcessAsync(IQueueEntry queueEntry, CancellationToken cancellationToken) { + public async Task ProcessAsync(IQueueEntry queueEntry, CancellationToken cancellationToken) + { if (queueEntry == null) return JobResult.Success; - if (cancellationToken.IsCancellationRequested) { + if (cancellationToken.IsCancellationRequested) + { await queueEntry.AbandonAsync().AnyContext(); return JobResult.CancelledWithMessage($"Abandoning {queueEntry.Value.Type} work item: {queueEntry.Id}"); } var workItemDataType = GetWorkItemType(queueEntry.Value.Type); - if (workItemDataType == null) { + if (workItemDataType == null) + { await queueEntry.AbandonAsync().AnyContext(); return JobResult.FailedWithMessage($"Abandoning {queueEntry.Value.Type} work item: {queueEntry.Id}: Could not resolve work item data type"); } @@ -67,15 +78,19 @@ public async Task ProcessAsync(IQueueEntry queueEntry, .Property("QueueEntryName", workItemDataType.Name)); object workItemData; - try { + try + { workItemData = _queue.Serializer.Deserialize(queueEntry.Value.Data, workItemDataType); - } catch (Exception ex) { + } + catch (Exception ex) + { await queueEntry.AbandonAsync().AnyContext(); return JobResult.FromException(ex, $"Abandoning {queueEntry.Value.Type} work item: {queueEntry.Id}: Failed to parse {workItemDataType.Name} work item data"); } var handler = _handlers.GetHandler(workItemDataType); - if (handler == null) { + if (handler == null) + { await queueEntry.CompleteAsync().AnyContext(); return JobResult.FailedWithMessage($"Completing {queueEntry.Value.Type} work item: {queueEntry.Id}: Handler for type {workItemDataType.Name} not registered"); } @@ -84,7 +99,8 @@ public async Task ProcessAsync(IQueueEntry queueEntry, await ReportProgressAsync(handler, queueEntry).AnyContext(); var lockValue = await handler.GetWorkItemLockAsync(workItemData, cancellationToken).AnyContext(); - if (lockValue == null) { + if (lockValue == null) + { if (handler.Log.IsEnabled(LogLevel.Information)) handler.Log.LogInformation("Abandoning {TypeName} work item: {Id}: Unable to acquire work item lock.", queueEntry.Value.Type, queueEntry.Id); @@ -92,14 +108,19 @@ public async Task ProcessAsync(IQueueEntry queueEntry, return JobResult.Success; } - var progressCallback = new Func(async (progress, message) => { - if (handler.AutoRenewLockOnProgress) { - try { + var progressCallback = new Func(async (progress, message) => + { + if (handler.AutoRenewLockOnProgress) + { + try + { await Task.WhenAll( queueEntry.RenewLockAsync(), lockValue.RenewAsync() ).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (handler.Log.IsEnabled(LogLevel.Error)) handler.Log.LogError(ex, "Error renewing work item locks: {Message}", ex.Message); } @@ -110,19 +131,23 @@ await Task.WhenAll( handler.Log.LogInformation("{TypeName} Progress {Progress}%: {Message}", workItemDataType.Name, progress, message); }); - try { + try + { handler.LogProcessingQueueEntry(queueEntry, workItemDataType, workItemData); var workItemContext = new WorkItemContext(workItemData, JobId, lockValue, cancellationToken, progressCallback); await handler.HandleItemAsync(workItemContext).AnyContext(); - if (!workItemContext.Result.IsSuccess) { - if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) { + if (!workItemContext.Result.IsSuccess) + { + if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { await queueEntry.AbandonAsync().AnyContext(); return workItemContext.Result; } } - if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) { + if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { await queueEntry.CompleteAsync().AnyContext(); handler.LogAutoCompletedQueueEntry(queueEntry, workItemDataType, workItemData); } @@ -131,23 +156,29 @@ await Task.WhenAll( await ReportProgressAsync(handler, queueEntry, 100).AnyContext(); return JobResult.Success; - } catch (Exception ex) { + } + catch (Exception ex) + { if (queueEntry.Value.SendProgressReports) await ReportProgressAsync(handler, queueEntry, -1, $"Failed: {ex.Message}").AnyContext(); - if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) { + if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { await queueEntry.AbandonAsync().AnyContext(); return JobResult.FromException(ex, $"Abandoning {queueEntry.Value.Type} work item: {queueEntry.Id}: Error in handler {workItemDataType.Name}"); } return JobResult.FromException(ex, $"Error processing {queueEntry.Value.Type} work item: {queueEntry.Id} in handler: {workItemDataType.Name}"); - } finally { + } + finally + { await lockValue.ReleaseAsync().AnyContext(); } } - protected virtual Activity StartProcessWorkItemActivity(IQueueEntry entry, Type workItemDataType) { + protected virtual Activity StartProcessWorkItemActivity(IQueueEntry entry, Type workItemDataType) + { var activity = FoundatioDiagnostics.ActivitySource.StartActivity("ProcessQueueEntry", ActivityKind.Server, entry.CorrelationId); if (activity == null) @@ -163,7 +194,8 @@ protected virtual Activity StartProcessWorkItemActivity(IQueueEntry entry, Type workItemDataType) { + protected virtual void EnrichProcessWorkItemActivity(Activity activity, IQueueEntry entry, Type workItemDataType) + { if (!activity.IsAllDataRequested) return; @@ -174,26 +206,35 @@ protected virtual void EnrichProcessWorkItemActivity(Activity activity, IQueueEn if (entry.Properties == null || entry.Properties.Count <= 0) return; - foreach (var p in entry.Properties) { + foreach (var p in entry.Properties) + { if (p.Key != "TraceState") activity.AddTag(p.Key, p.Value); } } private readonly ConcurrentDictionary _knownTypesCache = new(); - protected virtual Type GetWorkItemType(string workItemType) { - return _knownTypesCache.GetOrAdd(workItemType, type => { - try { + protected virtual Type GetWorkItemType(string workItemType) + { + return _knownTypesCache.GetOrAdd(workItemType, type => + { + try + { return Type.GetType(type); - } catch (Exception) { - try { + } + catch (Exception) + { + try + { string[] typeParts = type.Split(','); if (typeParts.Length >= 2) type = String.Join(",", typeParts[0], typeParts[1]); - + // try resolve type without version return Type.GetType(type); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning(ex, "Error getting work item type: {WorkItemType}", type); @@ -203,15 +244,20 @@ protected virtual Type GetWorkItemType(string workItemType) { }); } - protected async Task ReportProgressAsync(IWorkItemHandler handler, IQueueEntry queueEntry, int progress = 0, string message = null) { - try { - await _publisher.PublishAsync(new WorkItemStatus { + protected async Task ReportProgressAsync(IWorkItemHandler handler, IQueueEntry queueEntry, int progress = 0, string message = null) + { + try + { + await _publisher.PublishAsync(new WorkItemStatus + { WorkItemId = queueEntry.Value.WorkItemId, Type = queueEntry.Value.Type, Progress = progress, Message = message }).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (handler.Log.IsEnabled(LogLevel.Error)) handler.Log.LogError(ex, "Error sending progress report: {Message}", ex.Message); } diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemQueueExtensions.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemQueueExtensions.cs index 11be5a420..18f1a5a9d 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemQueueExtensions.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemQueueExtensions.cs @@ -1,17 +1,21 @@ using System; using System.Threading.Tasks; using Foundatio.Metrics; -using Foundatio.Utility; using Foundatio.Queues; using Foundatio.Serializer; +using Foundatio.Utility; -namespace Foundatio.Jobs { - public static class WorkItemQueueExtensions { - public static async Task EnqueueAsync(this IQueue queue, T workItemData, bool includeProgressReporting = false) { +namespace Foundatio.Jobs +{ + public static class WorkItemQueueExtensions + { + public static async Task EnqueueAsync(this IQueue queue, T workItemData, bool includeProgressReporting = false) + { string jobId = Guid.NewGuid().ToString("N"); var bytes = queue.Serializer.SerializeToBytes(workItemData); - var data = new WorkItemData { + var data = new WorkItemData + { Data = bytes, WorkItemId = jobId, Type = typeof(T).AssemblyQualifiedName, @@ -31,7 +35,8 @@ public static async Task EnqueueAsync(this IQueue queue return jobId; } - private static string GetDefaultSubMetricName(WorkItemData data) { + private static string GetDefaultSubMetricName(WorkItemData data) + { if (String.IsNullOrEmpty(data.Type)) return null; @@ -42,14 +47,15 @@ private static string GetDefaultSubMetricName(WorkItemData data) { return type?.ToLowerInvariant(); } - private static string GetTypeName(string assemblyQualifiedName) { + private static string GetTypeName(string assemblyQualifiedName) + { if (String.IsNullOrEmpty(assemblyQualifiedName)) return null; var parts = assemblyQualifiedName.Split(','); int i = parts[0].LastIndexOf('.'); - + return i < 0 ? null : parts[0].Substring(i + 1); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Jobs/WorkItemJob/WorkItemStatus.cs b/src/Foundatio/Jobs/WorkItemJob/WorkItemStatus.cs index f5bc28405..6b460f255 100644 --- a/src/Foundatio/Jobs/WorkItemJob/WorkItemStatus.cs +++ b/src/Foundatio/Jobs/WorkItemJob/WorkItemStatus.cs @@ -1,8 +1,10 @@ -namespace Foundatio.Jobs { - public class WorkItemStatus { +namespace Foundatio.Jobs +{ + public class WorkItemStatus + { public string WorkItemId { get; set; } public int Progress { get; set; } public string Message { get; set; } public string Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Lock/CacheLockProvider.cs b/src/Foundatio/Lock/CacheLockProvider.cs index a8958bdfb..090ede7b3 100644 --- a/src/Foundatio/Lock/CacheLockProvider.cs +++ b/src/Foundatio/Lock/CacheLockProvider.cs @@ -1,18 +1,20 @@ using System; -using Foundatio.Caching; -using Foundatio.Messaging; -using Foundatio.AsyncEx; -using System.Threading; -using System.Threading.Tasks; using System.Collections.Concurrent; using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Threading; +using System.Threading.Tasks; +using Foundatio.AsyncEx; +using Foundatio.Caching; +using Foundatio.Messaging; using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using System.Diagnostics.Metrics; -namespace Foundatio.Lock { - public class CacheLockProvider : ILockProvider, IHaveLogger { +namespace Foundatio.Lock +{ + public class CacheLockProvider : ILockProvider, IHaveLogger + { private readonly ICacheClient _cacheClient; private readonly IMessageBus _messageBus; private readonly ConcurrentDictionary _autoResetEvents = new(); @@ -22,7 +24,8 @@ public class CacheLockProvider : ILockProvider, IHaveLogger { private readonly Histogram _lockWaitTimeHistogram; private readonly Counter _lockTimeoutCounter; - public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILoggerFactory loggerFactory = null) { + public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _cacheClient = new ScopedCacheClient(cacheClient, "lock"); _messageBus = messageBus; @@ -33,11 +36,13 @@ public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILogg ILogger IHaveLogger.Logger => _logger; - private async Task EnsureTopicSubscriptionAsync() { + private async Task EnsureTopicSubscriptionAsync() + { if (_isSubscribed) return; - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { if (_isSubscribed) return; @@ -49,17 +54,19 @@ private async Task EnsureTopicSubscriptionAsync() { } } - private Task OnLockReleasedAsync(CacheLockReleased msg, CancellationToken cancellationToken = default) { + private Task OnLockReleasedAsync(CacheLockReleased msg, CancellationToken cancellationToken = default) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Got lock released message: {Resource} ({LockId})", msg.Resource, msg.LockId); - + if (_autoResetEvents.TryGetValue(msg.Resource, out var autoResetEvent)) autoResetEvent.Target.Set(); return Task.CompletedTask; } - protected virtual Activity StartLockActivity(string resource) { + protected virtual Activity StartLockActivity(string resource) + { var activity = FoundatioDiagnostics.ActivitySource.StartActivity("AcquireLock"); if (activity == null) @@ -71,7 +78,8 @@ protected virtual Activity StartLockActivity(string resource) { return activity; } - public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) { + public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); bool isDebugLogLevelEnabled = _logger.IsEnabled(LogLevel.Debug); bool shouldWait = !cancellationToken.IsCancellationRequested; @@ -86,25 +94,30 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire bool gotLock = false; string lockId = GenerateNewLockId(); var sw = Stopwatch.StartNew(); - try { - do { - try { + try + { + do + { + try + { if (timeUntilExpires.Value == TimeSpan.Zero) // no lock timeout gotLock = await _cacheClient.AddAsync(resource, lockId).AnyContext(); else gotLock = await _cacheClient.AddAsync(resource, lockId, timeUntilExpires).AnyContext(); - } catch { } + } + catch { } if (gotLock) break; if (isDebugLogLevelEnabled) _logger.LogDebug("Failed to acquire lock: {Resource}", resource); - - if (cancellationToken.IsCancellationRequested) { + + if (cancellationToken.IsCancellationRequested) + { if (isTraceLogLevelEnabled && shouldWait) _logger.LogTrace("Cancellation requested"); - + break; } @@ -114,31 +127,37 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire var keyExpiration = SystemClock.UtcNow.SafeAdd(await _cacheClient.GetExpirationAsync(resource).AnyContext() ?? TimeSpan.Zero); var delayAmount = keyExpiration.Subtract(SystemClock.UtcNow); - + // delay a minimum of 50ms if (delayAmount < TimeSpan.FromMilliseconds(50)) delayAmount = TimeSpan.FromMilliseconds(50); - + // delay a maximum of 3 seconds if (delayAmount > TimeSpan.FromSeconds(3)) delayAmount = TimeSpan.FromSeconds(3); - + if (isTraceLogLevelEnabled) _logger.LogTrace("Will wait {Delay:g} before retrying to acquire lock: {Resource}", delayAmount, resource); // wait until we get a message saying the lock was released or 3 seconds has elapsed or cancellation has been requested using (var maxWaitCancellationTokenSource = new CancellationTokenSource(delayAmount)) - using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, maxWaitCancellationTokenSource.Token)) { - try { + using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, maxWaitCancellationTokenSource.Token)) + { + try + { await autoResetEvent.Target.WaitAsync(linkedCancellationTokenSource.Token).AnyContext(); - } catch (OperationCanceledException) {} + } + catch (OperationCanceledException) { } } - + Thread.Yield(); } while (!cancellationToken.IsCancellationRequested); - } finally { + } + finally + { bool shouldRemove = false; - _autoResetEvents.TryUpdate(resource, (n, e) => { + _autoResetEvents.TryUpdate(resource, (n, e) => + { e.RefCount--; if (e.RefCount == 0) shouldRemove = true; @@ -152,14 +171,15 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire _lockWaitTimeHistogram.Record(sw.Elapsed.TotalMilliseconds); - if (!gotLock) { + if (!gotLock) + { _lockTimeoutCounter.Add(1); if (cancellationToken.IsCancellationRequested && isTraceLogLevelEnabled) _logger.LogTrace("Cancellation requested for lock {Resource} after {Duration:g}", resource, sw.Elapsed); else if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Failed to acquire lock {Resource} after {Duration:g}", resource, lockId, sw.Elapsed); - + return null; } @@ -167,16 +187,18 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire _logger.LogWarning("Acquired lock {Resource} ({LockId}) after {Duration:g}", resource, lockId, sw.Elapsed); else if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Acquired lock {Resource} ({LockId}) after {Duration:g}", resource, lockId, sw.Elapsed); - + return new DisposableLock(resource, lockId, sw.Elapsed, this, _logger, releaseOnDispose); } - public async Task IsLockedAsync(string resource) { + public async Task IsLockedAsync(string resource) + { var result = await Run.WithRetriesAsync(() => _cacheClient.ExistsAsync(resource), logger: _logger).AnyContext(); return result; } - public async Task ReleaseAsync(string resource, string lockId) { + public async Task ReleaseAsync(string resource, string lockId) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("ReleaseAsync Start: {Resource} ({LockId})", resource, lockId); @@ -186,8 +208,9 @@ public async Task ReleaseAsync(string resource, string lockId) { if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Released lock: {Resource} ({LockId})", resource, lockId); } - - public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) { + + public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) + { if (!timeUntilExpires.HasValue) timeUntilExpires = TimeSpan.FromMinutes(20); @@ -197,7 +220,8 @@ public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpire return Run.WithRetriesAsync(() => _cacheClient.ReplaceIfEqualAsync(resource, lockId, lockId, timeUntilExpires.Value)); } - private class ResetEventWithRefCount { + private class ResetEventWithRefCount + { public int RefCount { get; set; } public AsyncAutoResetEvent Target { get; set; } } @@ -205,7 +229,8 @@ private class ResetEventWithRefCount { private static string _allowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static Random _rng = new(); - private string GenerateNewLockId() { + private string GenerateNewLockId() + { char[] chars = new char[16]; for (int i = 0; i < 16; ++i) @@ -215,7 +240,8 @@ private string GenerateNewLockId() { } } - public class CacheLockReleased { + public class CacheLockReleased + { public string Resource { get; set; } public string LockId { get; set; } } diff --git a/src/Foundatio/Lock/DisposableLock.cs b/src/Foundatio/Lock/DisposableLock.cs index 6174d510d..9afc4a0fa 100644 --- a/src/Foundatio/Lock/DisposableLock.cs +++ b/src/Foundatio/Lock/DisposableLock.cs @@ -4,8 +4,10 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Lock { - internal class DisposableLock : ILock { +namespace Foundatio.Lock +{ + internal class DisposableLock : ILock + { private readonly ILockProvider _lockProvider; private readonly ILogger _logger; private bool _isReleased; @@ -14,7 +16,8 @@ internal class DisposableLock : ILock { private readonly Stopwatch _duration; private readonly bool _shouldReleaseOnDispose; - public DisposableLock(string resource, string lockId, TimeSpan timeWaitedForLock, ILockProvider lockProvider, ILogger logger, bool shouldReleaseOnDispose) { + public DisposableLock(string resource, string lockId, TimeSpan timeWaitedForLock, ILockProvider lockProvider, ILogger logger, bool shouldReleaseOnDispose) + { Resource = resource; LockId = lockId; TimeWaitedForLock = timeWaitedForLock; @@ -31,7 +34,8 @@ public DisposableLock(string resource, string lockId, TimeSpan timeWaitedForLock public TimeSpan TimeWaitedForLock { get; } public int RenewalCount => _renewalCount; - public async ValueTask DisposeAsync() { + public async ValueTask DisposeAsync() + { if (!_shouldReleaseOnDispose) return; @@ -39,9 +43,12 @@ public async ValueTask DisposeAsync() { if (isTraceLogLevelEnabled) _logger.LogTrace("Disposing lock {Resource}", Resource); - try { + try + { await ReleaseAsync().AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Unable to release lock {Resource}", Resource); } @@ -50,7 +57,8 @@ public async ValueTask DisposeAsync() { _logger.LogTrace("Disposed lock {Resource}", Resource); } - public async Task RenewAsync(TimeSpan? lockExtension = null) { + public async Task RenewAsync(TimeSpan? lockExtension = null) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Renewing lock {Resource}", Resource); @@ -61,11 +69,13 @@ public async Task RenewAsync(TimeSpan? lockExtension = null) { _logger.LogDebug("Renewed lock {Resource}", Resource); } - public Task ReleaseAsync() { + public Task ReleaseAsync() + { if (_isReleased) return Task.CompletedTask; - lock (_lock) { + lock (_lock) + { if (_isReleased) return Task.CompletedTask; @@ -79,4 +89,4 @@ public Task ReleaseAsync() { } } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Lock/DisposableLockCollection.cs b/src/Foundatio/Lock/DisposableLockCollection.cs index 9358551b3..a8d85f190 100644 --- a/src/Foundatio/Lock/DisposableLockCollection.cs +++ b/src/Foundatio/Lock/DisposableLockCollection.cs @@ -6,19 +6,22 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Lock { - internal class DisposableLockCollection : ILock { +namespace Foundatio.Lock +{ + internal class DisposableLockCollection : ILock + { private readonly List _locks = new(); private readonly ILogger _logger; private bool _isReleased; private int _renewalCount; private readonly object _lock = new(); private readonly Stopwatch _duration; - - public DisposableLockCollection(IEnumerable locks, string lockId, TimeSpan timeWaitedForLock, ILogger logger) { + + public DisposableLockCollection(IEnumerable locks, string lockId, TimeSpan timeWaitedForLock, ILogger logger) + { if (locks == null) throw new ArgumentNullException(nameof(locks)); - + _locks.AddRange(locks); Resource = String.Join("+", _locks.Select(l => l.Resource)); LockId = lockId; @@ -35,7 +38,8 @@ public DisposableLockCollection(IEnumerable locks, string lockId, TimeSpa public TimeSpan TimeWaitedForLock { get; } public int RenewalCount => _renewalCount; - public async Task RenewAsync(TimeSpan? lockExtension = null) { + public async Task RenewAsync(TimeSpan? lockExtension = null) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Renewing {LockCount} locks {Resource}", _locks.Count, Resource); @@ -46,11 +50,13 @@ public async Task RenewAsync(TimeSpan? lockExtension = null) { _logger.LogDebug("Renewing {LockCount} locks {Resource}", _locks.Count, Resource); } - public Task ReleaseAsync() { + public Task ReleaseAsync() + { if (_isReleased) return Task.CompletedTask; - lock (_lock) { + lock (_lock) + { if (_isReleased) return Task.CompletedTask; @@ -63,15 +69,19 @@ public Task ReleaseAsync() { return Task.WhenAll(_locks.Select(l => l.ReleaseAsync())); } } - - public async ValueTask DisposeAsync() { + + public async ValueTask DisposeAsync() + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (isTraceLogLevelEnabled) _logger.LogTrace("Disposing {LockCount} locks {Resource}", _locks.Count, Resource); - try { + try + { await Task.WhenAll(_locks.Select(l => l.ReleaseAsync())).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Unable to release {LockCount} locks {Resource}", _locks.Count, Resource); } @@ -80,4 +90,4 @@ public async ValueTask DisposeAsync() { _logger.LogTrace("Disposed {LockCount} locks {Resource}", _locks.Count, Resource); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Lock/ILockProvider.cs b/src/Foundatio/Lock/ILockProvider.cs index 2b1d4e545..b7569e9ab 100644 --- a/src/Foundatio/Lock/ILockProvider.cs +++ b/src/Foundatio/Lock/ILockProvider.cs @@ -8,15 +8,18 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Lock { - public interface ILockProvider { +namespace Foundatio.Lock +{ + public interface ILockProvider + { Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default); Task IsLockedAsync(string resource); Task ReleaseAsync(string resource, string lockId); Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null); } - public interface ILock : IAsyncDisposable { + public interface ILock : IAsyncDisposable + { Task RenewAsync(TimeSpan? timeUntilExpires = null); Task ReleaseAsync(); string LockId { get; } @@ -26,119 +29,146 @@ public interface ILock : IAsyncDisposable { int RenewalCount { get; } } - public static class LockProviderExtensions { - public static Task ReleaseAsync(this ILockProvider provider, ILock @lock) { + public static class LockProviderExtensions + { + public static Task ReleaseAsync(this ILockProvider provider, ILock @lock) + { return provider.ReleaseAsync(@lock.Resource, @lock.LockId); } - public static Task RenewAsync(this ILockProvider provider, ILock @lock, TimeSpan? timeUntilExpires = null) { + public static Task RenewAsync(this ILockProvider provider, ILock @lock, TimeSpan? timeUntilExpires = null) + { return provider.RenewAsync(@lock.Resource, @lock.LockId, timeUntilExpires); } - public static Task AcquireAsync(this ILockProvider provider, string resource, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) { + public static Task AcquireAsync(this ILockProvider provider, string resource, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) + { return provider.AcquireAsync(resource, timeUntilExpires, true, cancellationToken); } - public static async Task AcquireAsync(this ILockProvider provider, string resource, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) { + public static async Task AcquireAsync(this ILockProvider provider, string resource, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(TimeSpan.FromSeconds(30)); return await provider.AcquireAsync(resource, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); } - - public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) { + + public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) + { var l = await locker.AcquireAsync(resource, timeUntilExpires, true, cancellationToken).AnyContext(); if (l == null) return false; - try { + try + { await work(cancellationToken).AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) { + public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) + { var l = await locker.AcquireAsync(resource, timeUntilExpires, true, cancellationToken).AnyContext(); if (l == null) return false; - try { + try + { await work().AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) { + public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(); var l = await locker.AcquireAsync(resource, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); if (l == null) return false; - try { + try + { await work(cancellationTokenSource.Token).AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) { + public static async Task TryUsingAsync(this ILockProvider locker, string resource, Func work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(); var l = await locker.AcquireAsync(resource, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); if (l == null) return false; - try { + try + { await work().AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static Task TryUsingAsync(this ILockProvider locker, string resource, Action work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) { - return locker.TryUsingAsync(resource, () => { + public static Task TryUsingAsync(this ILockProvider locker, string resource, Action work, TimeSpan? timeUntilExpires = null, TimeSpan? acquireTimeout = null) + { + return locker.TryUsingAsync(resource, () => + { work(); return Task.CompletedTask; }, timeUntilExpires, acquireTimeout); } - public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) { + public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) + { if (resources == null) throw new ArgumentNullException(nameof(resources)); - + var resourceList = resources.Distinct().ToArray(); if (resourceList.Length == 0) return new EmptyLock(); var logger = provider.GetLogger(); - + if (logger.IsEnabled(LogLevel.Trace)) logger.LogTrace("Acquiring {LockCount} locks {Resource}", resourceList.Length, resourceList); - + var sw = Stopwatch.StartNew(); var locks = await Task.WhenAll(resourceList.Select(r => provider.AcquireAsync(r, timeUntilExpires, releaseOnDispose, cancellationToken))); sw.Stop(); - + // if any lock is null, release any acquired and return null (all or nothing) var acquiredLocks = locks.Where(l => l != null).ToArray(); - if (resourceList.Length > acquiredLocks.Length) { + if (resourceList.Length > acquiredLocks.Length) + { var unacquiredResources = new List(); string[] acquiredResources = acquiredLocks.Select(l => l.Resource).ToArray(); - foreach (string resource in resourceList) { + foreach (string resource in resourceList) + { // account for scoped lock providers with prefixes if (!acquiredResources.Any(r => r.EndsWith(resource))) unacquiredResources.Add(resource); } - if (unacquiredResources.Count > 0) { + if (unacquiredResources.Count > 0) + { if (logger.IsEnabled(LogLevel.Trace)) logger.LogTrace("Unable to acquire all {LockCount} locks {Resource} releasing acquired locks", unacquiredResources.Count, unacquiredResources); @@ -146,83 +176,103 @@ public static async Task AcquireAsync(this ILockProvider provider, IEnume return null; } } - + if (logger.IsEnabled(LogLevel.Trace)) logger.LogTrace("Acquired {LockCount} locks {Resource} after {Duration:g}", resourceList.Length, resourceList, sw.Elapsed); - + return new DisposableLockCollection(locks, String.Join("+", locks.Select(l => l.LockId)), sw.Elapsed, logger); } - public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) { + public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(TimeSpan.FromSeconds(30)); return await provider.AcquireAsync(resources, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); } - public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout, bool releaseOnDispose) { + public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout, bool releaseOnDispose) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(TimeSpan.FromSeconds(30)); return await provider.AcquireAsync(resources, timeUntilExpires, releaseOnDispose, cancellationTokenSource.Token).AnyContext(); } - - public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) { + + public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) + { var l = await locker.AcquireAsync(resources, timeUntilExpires, true, cancellationToken).AnyContext(); if (l == null) return false; - try { + try + { await work(cancellationToken).AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) { + public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires = null, CancellationToken cancellationToken = default) + { var l = await locker.AcquireAsync(resources, timeUntilExpires, true, cancellationToken).AnyContext(); if (l == null) return false; - try { + try + { await work().AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) { + public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(); var l = await locker.AcquireAsync(resources, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); if (l == null) return false; - try { + try + { await work(cancellationTokenSource.Token).AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) { + public static async Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Func work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) + { using var cancellationTokenSource = acquireTimeout.ToCancellationTokenSource(); var l = await locker.AcquireAsync(resources, timeUntilExpires, true, cancellationTokenSource.Token).AnyContext(); if (l == null) return false; - try { + try + { await work().AnyContext(); - } finally { + } + finally + { await l.ReleaseAsync().AnyContext(); } return true; } - public static Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Action work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) { - return locker.TryUsingAsync(resources, () => { + public static Task TryUsingAsync(this ILockProvider locker, IEnumerable resources, Action work, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) + { + return locker.TryUsingAsync(resources, () => + { work(); return Task.CompletedTask; }, timeUntilExpires, acquireTimeout); diff --git a/src/Foundatio/Lock/ScopedLockProvider.cs b/src/Foundatio/Lock/ScopedLockProvider.cs index 9931bb77d..3ab518be9 100644 --- a/src/Foundatio/Lock/ScopedLockProvider.cs +++ b/src/Foundatio/Lock/ScopedLockProvider.cs @@ -4,13 +4,16 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Lock { - public class ScopedLockProvider : ILockProvider, IHaveLogger { +namespace Foundatio.Lock +{ + public class ScopedLockProvider : ILockProvider, IHaveLogger + { private string _keyPrefix; private bool _isLocked; private readonly object _lock = new(); - public ScopedLockProvider(ILockProvider lockProvider, string scope = null) { + public ScopedLockProvider(ILockProvider lockProvider, string scope = null) + { UnscopedLockProvider = lockProvider; _isLocked = scope != null; Scope = !String.IsNullOrWhiteSpace(scope) ? scope.Trim() : null; @@ -22,11 +25,13 @@ public ScopedLockProvider(ILockProvider lockProvider, string scope = null) { public string Scope { get; private set; } ILogger IHaveLogger.Logger => UnscopedLockProvider.GetLogger(); - public void SetScope(string scope) { + public void SetScope(string scope) + { if (_isLocked) throw new InvalidOperationException("Scope can't be changed after it has been set"); - lock (_lock) { + lock (_lock) + { if (_isLocked) throw new InvalidOperationException("Scope can't be changed after it has been set"); @@ -36,24 +41,29 @@ public void SetScope(string scope) { } } - protected string GetScopedLockProviderKey(string key) { + protected string GetScopedLockProviderKey(string key) + { return String.Concat(_keyPrefix, key); } - public Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) { + public Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) + { return UnscopedLockProvider.AcquireAsync(GetScopedLockProviderKey(resource), timeUntilExpires, releaseOnDispose, cancellationToken); } - public Task IsLockedAsync(string resource) { + public Task IsLockedAsync(string resource) + { return UnscopedLockProvider.IsLockedAsync(GetScopedLockProviderKey(resource)); } - public Task ReleaseAsync(string resource, string lockId) { + public Task ReleaseAsync(string resource, string lockId) + { return UnscopedLockProvider.ReleaseAsync(resource, lockId); } - public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) { + public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) + { return UnscopedLockProvider.RenewAsync(resource, lockId, timeUntilExpires); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Lock/ThrottlingLockProvider.cs b/src/Foundatio/Lock/ThrottlingLockProvider.cs index 7718efd45..79618a707 100644 --- a/src/Foundatio/Lock/ThrottlingLockProvider.cs +++ b/src/Foundatio/Lock/ThrottlingLockProvider.cs @@ -7,14 +7,17 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Lock { - public class ThrottlingLockProvider : ILockProvider, IHaveLogger { +namespace Foundatio.Lock +{ + public class ThrottlingLockProvider : ILockProvider, IHaveLogger + { private readonly ICacheClient _cacheClient; private readonly TimeSpan _throttlingPeriod = TimeSpan.FromMinutes(15); private readonly int _maxHitsPerPeriod; private readonly ILogger _logger; - public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null, ILoggerFactory loggerFactory = null) { + public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _cacheClient = new ScopedCacheClient(cacheClient, "lock:throttled"); _maxHitsPerPeriod = maxHitsPerPeriod; @@ -25,10 +28,11 @@ public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 1 if (throttlingPeriod.HasValue) _throttlingPeriod = throttlingPeriod.Value; } - + ILogger IHaveLogger.Logger => _logger; - public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) { + public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (isTraceLogLevelEnabled) _logger.LogTrace("AcquireLockAsync: {Resource}", resource); @@ -37,27 +41,33 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire string lockId = Guid.NewGuid().ToString("N"); var sw = Stopwatch.StartNew(); - do { + do + { string cacheKey = GetCacheKey(resource, SystemClock.UtcNow); - try { + try + { if (isTraceLogLevelEnabled) _logger.LogTrace("Current time: {CurrentTime} throttle: {ThrottlingPeriod} key: {Key}", SystemClock.UtcNow.ToString("mm:ss.fff"), SystemClock.UtcNow.Floor(_throttlingPeriod).ToString("mm:ss.fff"), cacheKey); var hitCount = await _cacheClient.GetAsync(cacheKey, 0).AnyContext(); if (isTraceLogLevelEnabled) _logger.LogTrace("Current hit count: {HitCount} max: {MaxHitsPerPeriod}", hitCount, _maxHitsPerPeriod); - if (hitCount <= _maxHitsPerPeriod - 1) { + if (hitCount <= _maxHitsPerPeriod - 1) + { hitCount = await _cacheClient.IncrementAsync(cacheKey, 1, SystemClock.UtcNow.Ceiling(_throttlingPeriod)).AnyContext(); // make sure someone didn't beat us to it. - if (hitCount <= _maxHitsPerPeriod) { + if (hitCount <= _maxHitsPerPeriod) + { allowLock = true; break; } if (isTraceLogLevelEnabled) _logger.LogTrace("Max hits exceeded after increment for {Resource}.", resource); - } else if (isTraceLogLevelEnabled) { + } + else if (isTraceLogLevelEnabled) + { _logger.LogTrace("Max hits exceeded for {Resource}.", resource); } @@ -65,16 +75,23 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire break; var sleepUntil = SystemClock.UtcNow.Ceiling(_throttlingPeriod).AddMilliseconds(1); - if (sleepUntil > SystemClock.UtcNow) { + if (sleepUntil > SystemClock.UtcNow) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Sleeping until key expires: {SleepUntil}", sleepUntil - SystemClock.UtcNow); await SystemClock.SleepAsync(sleepUntil - SystemClock.UtcNow, cancellationToken).AnyContext(); - } else { + } + else + { if (isTraceLogLevelEnabled) _logger.LogTrace("Default sleep"); await SystemClock.SleepAsync(50, cancellationToken).AnyContext(); } - } catch (OperationCanceledException) { + } + catch (OperationCanceledException) + { return null; - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error acquiring throttled lock: name={Resource} message={Message}", resource, ex.Message); errors++; if (errors >= 3) @@ -92,26 +109,30 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire if (isTraceLogLevelEnabled) _logger.LogTrace("Allowing lock: {Resource}", resource); - + sw.Stop(); return new DisposableLock(resource, lockId, sw.Elapsed, this, _logger, releaseOnDispose); } - public async Task IsLockedAsync(string resource) { + public async Task IsLockedAsync(string resource) + { string cacheKey = GetCacheKey(resource, SystemClock.UtcNow); long hitCount = await _cacheClient.GetAsync(cacheKey, 0).AnyContext(); return hitCount >= _maxHitsPerPeriod; } - public Task ReleaseAsync(string resource, string lockId) { + public Task ReleaseAsync(string resource, string lockId) + { return Task.CompletedTask; } - - public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) { + + public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) + { return Task.CompletedTask; } - private string GetCacheKey(string resource, DateTime now) { + private string GetCacheKey(string resource, DateTime now) + { return String.Concat(resource, ":", now.Floor(_throttlingPeriod).Ticks); } } diff --git a/src/Foundatio/Messaging/IMessageBus.cs b/src/Foundatio/Messaging/IMessageBus.cs index f1eeca8df..3af63a204 100644 --- a/src/Foundatio/Messaging/IMessageBus.cs +++ b/src/Foundatio/Messaging/IMessageBus.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; -namespace Foundatio.Messaging { - public interface IMessageBus : IMessagePublisher, IMessageSubscriber, IDisposable {} - - public class MessageOptions { +namespace Foundatio.Messaging +{ + public interface IMessageBus : IMessagePublisher, IMessageSubscriber, IDisposable { } + + public class MessageOptions + { public string UniqueId { get; set; } public string CorrelationId { get; set; } public TimeSpan? DeliveryDelay { get; set; } public IDictionary Properties { get; set; } = new Dictionary(); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Messaging/IMessagePublisher.cs b/src/Foundatio/Messaging/IMessagePublisher.cs index cfb7078fb..534a6786d 100644 --- a/src/Foundatio/Messaging/IMessagePublisher.cs +++ b/src/Foundatio/Messaging/IMessagePublisher.cs @@ -2,17 +2,22 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Messaging { - public interface IMessagePublisher { +namespace Foundatio.Messaging +{ + public interface IMessagePublisher + { Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default); } - public static class MessagePublisherExtensions { - public static Task PublishAsync(this IMessagePublisher publisher, T message, MessageOptions options = null) where T : class { + public static class MessagePublisherExtensions + { + public static Task PublishAsync(this IMessagePublisher publisher, T message, MessageOptions options = null) where T : class + { return publisher.PublishAsync(typeof(T), message, options); } - public static Task PublishAsync(this IMessagePublisher publisher, T message, TimeSpan delay, CancellationToken cancellationToken = default) where T : class { + public static Task PublishAsync(this IMessagePublisher publisher, T message, TimeSpan delay, CancellationToken cancellationToken = default) where T : class + { return publisher.PublishAsync(typeof(T), message, new MessageOptions { DeliveryDelay = delay }, cancellationToken); } } diff --git a/src/Foundatio/Messaging/IMessageSubscriber.cs b/src/Foundatio/Messaging/IMessageSubscriber.cs index e7517d958..b8c56bb70 100644 --- a/src/Foundatio/Messaging/IMessageSubscriber.cs +++ b/src/Foundatio/Messaging/IMessageSubscriber.cs @@ -2,33 +2,43 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Messaging { - public interface IMessageSubscriber { +namespace Foundatio.Messaging +{ + public interface IMessageSubscriber + { Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class; } - public static class MessageBusExtensions { - public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) where T : class { + public static class MessageBusExtensions + { + public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) where T : class + { return subscriber.SubscribeAsync((msg, token) => handler(msg), cancellationToken); } - public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) where T : class { - return subscriber.SubscribeAsync((msg, token) => { + public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) where T : class + { + return subscriber.SubscribeAsync((msg, token) => + { handler(msg); return Task.CompletedTask; }, cancellationToken); } - public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) { + public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) + { return subscriber.SubscribeAsync((msg, token) => handler(msg, token), cancellationToken); } - public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) { + public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) + { return subscriber.SubscribeAsync((msg, token) => handler(msg), cancellationToken); } - public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) { - return subscriber.SubscribeAsync((msg, token) => { + public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) + { + return subscriber.SubscribeAsync((msg, token) => + { handler(msg); return Task.CompletedTask; }, cancellationToken); diff --git a/src/Foundatio/Messaging/InMemoryMessageBus.cs b/src/Foundatio/Messaging/InMemoryMessageBus.cs index cfdd5cda5..46e60cb17 100644 --- a/src/Foundatio/Messaging/InMemoryMessageBus.cs +++ b/src/Foundatio/Messaging/InMemoryMessageBus.cs @@ -5,12 +5,14 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Messaging { - public class InMemoryMessageBus : MessageBusBase { +namespace Foundatio.Messaging +{ + public class InMemoryMessageBus : MessageBusBase + { private readonly ConcurrentDictionary _messageCounts = new(); private long _messagesSent; - public InMemoryMessageBus() : this(o => o) {} + public InMemoryMessageBus() : this(o => o) { } public InMemoryMessageBus(InMemoryMessageBusOptions options) : base(options) { } @@ -19,20 +21,24 @@ public InMemoryMessageBus(Builder _messagesSent; - public long GetMessagesSent(Type messageType) { + public long GetMessagesSent(Type messageType) + { return _messageCounts.TryGetValue(GetMappedMessageType(messageType), out long count) ? count : 0; } - public long GetMessagesSent() { + public long GetMessagesSent() + { return _messageCounts.TryGetValue(GetMappedMessageType(typeof(T)), out long count) ? count : 0; } - public void ResetMessagesSent() { + public void ResetMessagesSent() + { Interlocked.Exchange(ref _messagesSent, 0); _messageCounts.Clear(); } - protected override async Task PublishImplAsync(string messageType, object message, MessageOptions options, CancellationToken cancellationToken) { + protected override async Task PublishImplAsync(string messageType, object message, MessageOptions options, CancellationToken cancellationToken) + { Interlocked.Increment(ref _messagesSent); _messageCounts.AddOrUpdate(messageType, t => 1, (t, c) => c + 1); var mappedType = GetMappedMessageType(messageType); @@ -41,15 +47,17 @@ protected override async Task PublishImplAsync(string messageType, object messag return; bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (options.DeliveryDelay.HasValue && options.DeliveryDelay.Value > TimeSpan.Zero) { + if (options.DeliveryDelay.HasValue && options.DeliveryDelay.Value > TimeSpan.Zero) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Schedule delayed message: {MessageType} ({Delay}ms)", messageType, options.DeliveryDelay.Value.TotalMilliseconds); SendDelayedMessage(mappedType, message, options.DeliveryDelay.Value); return; } - + byte[] body = SerializeMessageBody(messageType, message); - var messageData = new Message(body, DeserializeMessageBody) { + var messageData = new Message(body, DeserializeMessageBody) + { CorrelationId = options.CorrelationId, UniqueId = options.UniqueId, Type = messageType, @@ -59,12 +67,15 @@ protected override async Task PublishImplAsync(string messageType, object messag foreach (var property in options.Properties) messageData.Properties[property.Key] = property.Value; - try { + try + { await SendMessageToSubscribersAsync(messageData).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { // swallow exceptions from subscriber handlers for the in memory bus _logger.LogWarning(ex, "Error sending message to subscribers: {Message}", ex.Message); } } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Messaging/InMemoryMessageBusOptions.cs b/src/Foundatio/Messaging/InMemoryMessageBusOptions.cs index 7b373aedc..2049725ad 100644 --- a/src/Foundatio/Messaging/InMemoryMessageBusOptions.cs +++ b/src/Foundatio/Messaging/InMemoryMessageBusOptions.cs @@ -1,5 +1,6 @@ -namespace Foundatio.Messaging { +namespace Foundatio.Messaging +{ public class InMemoryMessageBusOptions : SharedMessageBusOptions { } - public class InMemoryMessageBusOptionsBuilder : SharedMessageBusOptionsBuilder {} -} \ No newline at end of file + public class InMemoryMessageBusOptionsBuilder : SharedMessageBusOptionsBuilder { } +} diff --git a/src/Foundatio/Messaging/Message.cs b/src/Foundatio/Messaging/Message.cs index 0b41b4590..386c4e67a 100644 --- a/src/Foundatio/Messaging/Message.cs +++ b/src/Foundatio/Messaging/Message.cs @@ -3,8 +3,10 @@ using System.Diagnostics; using Foundatio.Serializer; -namespace Foundatio.Messaging { - public interface IMessage { +namespace Foundatio.Messaging +{ + public interface IMessage + { string UniqueId { get; } string CorrelationId { get; } string Type { get; } @@ -14,15 +16,18 @@ public interface IMessage { IDictionary Properties { get; } } - public interface IMessage : IMessage where T: class { + public interface IMessage : IMessage where T : class + { T Body { get; } } [DebuggerDisplay("Type: {Type}")] - public class Message : IMessage { + public class Message : IMessage + { private readonly Func _getBody; - public Message(byte[] data, Func getBody) { + public Message(byte[] data, Func getBody) + { Data = data; _getBody = getBody; } @@ -36,10 +41,12 @@ public Message(byte[] data, Func getBody) { public object GetBody() => _getBody(this); } - public class Message : IMessage where T : class { + public class Message : IMessage where T : class + { private readonly IMessage _message; - public Message(IMessage message) { + public Message(IMessage message) + { _message = message; } @@ -59,4 +66,4 @@ public Message(IMessage message) { public object GetBody() => _message.GetBody(); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Messaging/MessageBusBase.cs b/src/Foundatio/Messaging/MessageBusBase.cs index 3201126d6..c0059b6b5 100644 --- a/src/Foundatio/Messaging/MessageBusBase.cs +++ b/src/Foundatio/Messaging/MessageBusBase.cs @@ -11,8 +11,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Messaging { - public abstract class MessageBusBase : IMessageBus, IDisposable where TOptions : SharedMessageBusOptions { +namespace Foundatio.Messaging +{ + public abstract class MessageBusBase : IMessageBus, IDisposable where TOptions : SharedMessageBusOptions + { private readonly CancellationTokenSource _messageBusDisposedCancellationTokenSource; protected readonly ConcurrentDictionary _subscribers = new(); protected readonly TOptions _options; @@ -20,7 +22,8 @@ public abstract class MessageBusBase : IMessageBus, IDisposable where protected readonly ISerializer _serializer; private bool _isDisposed; - public MessageBusBase(TOptions options) { + public MessageBusBase(TOptions options) + { _options = options ?? throw new ArgumentNullException(nameof(options)); var loggerFactory = options?.LoggerFactory ?? NullLoggerFactory.Instance; _logger = loggerFactory.CreateLogger(GetType()); @@ -31,13 +34,15 @@ public MessageBusBase(TOptions options) { protected virtual Task EnsureTopicCreatedAsync(CancellationToken cancellationToken) => Task.CompletedTask; protected abstract Task PublishImplAsync(string messageType, object message, MessageOptions options, CancellationToken cancellationToken); - public async Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default) { + public async Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default) + { if (messageType == null || message == null) return; options ??= new MessageOptions(); - if (String.IsNullOrEmpty(options.CorrelationId)) { + if (String.IsNullOrEmpty(options.CorrelationId)) + { options.CorrelationId = Activity.Current?.Id; if (!String.IsNullOrEmpty(Activity.Current?.TraceStateString)) options.Properties.Add("TraceState", Activity.Current.TraceStateString); @@ -46,38 +51,48 @@ public async Task PublishAsync(Type messageType, object message, MessageOptions await EnsureTopicCreatedAsync(cancellationToken).AnyContext(); await PublishImplAsync(GetMappedMessageType(messageType), message, options ?? new MessageOptions(), cancellationToken).AnyContext(); } - + private readonly ConcurrentDictionary _mappedMessageTypesCache = new(); - protected string GetMappedMessageType(Type messageType) { - return _mappedMessageTypesCache.GetOrAdd(messageType, type => { + protected string GetMappedMessageType(Type messageType) + { + return _mappedMessageTypesCache.GetOrAdd(messageType, type => + { var reversedMap = _options.MessageTypeMappings.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); if (reversedMap.ContainsKey(type)) return reversedMap[type]; - + return String.Concat(messageType.FullName, ", ", messageType.Assembly.GetName().Name); }); } private readonly ConcurrentDictionary _knownMessageTypesCache = new(); - protected virtual Type GetMappedMessageType(string messageType) { + protected virtual Type GetMappedMessageType(string messageType) + { if (String.IsNullOrEmpty(messageType)) return null; - - return _knownMessageTypesCache.GetOrAdd(messageType, type => { + + return _knownMessageTypesCache.GetOrAdd(messageType, type => + { if (_options.MessageTypeMappings != null && _options.MessageTypeMappings.ContainsKey(type)) return _options.MessageTypeMappings[type]; - - try { + + try + { return Type.GetType(type); - } catch (Exception) { - try { + } + catch (Exception) + { + try + { string[] typeParts = type.Split(','); if (typeParts.Length >= 2) type = String.Join(",", typeParts[0], typeParts[1]); - + // try resolve type without version return Type.GetType(type); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning(ex, "Error getting message body type: {MessageType}", type); @@ -90,12 +105,16 @@ protected virtual Type GetMappedMessageType(string messageType) { protected virtual Task RemoveTopicSubscriptionAsync() => Task.CompletedTask; protected virtual Task EnsureTopicSubscriptionAsync(CancellationToken cancellationToken) => Task.CompletedTask; - protected virtual Task SubscribeImplAsync(Func handler, CancellationToken cancellationToken) where T : class { - var subscriber = new Subscriber { + protected virtual Task SubscribeImplAsync(Func handler, CancellationToken cancellationToken) where T : class + { + var subscriber = new Subscriber + { CancellationToken = cancellationToken, Type = typeof(T), - Action = (message, token) => { - if (message is not T) { + Action = (message, token) => + { + if (message is not T) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Unable to call subscriber action: {MessageType} cannot be safely casted to {SubscriberType}", message.GetType(), typeof(T)); return Task.CompletedTask; @@ -105,15 +124,18 @@ protected virtual Task SubscribeImplAsync(Func ha } }; - if (cancellationToken != CancellationToken.None) { - cancellationToken.Register(() => { + if (cancellationToken != CancellationToken.None) + { + cancellationToken.Register(() => + { _subscribers.TryRemove(subscriber.Id, out _); if (_subscribers.Count == 0) RemoveTopicSubscriptionAsync().GetAwaiter().GetResult(); }); } - if (subscriber.Type.Name == "IMessage`1" && subscriber.Type.GenericTypeArguments.Length == 1) { + if (subscriber.Type.Name == "IMessage`1" && subscriber.Type.GenericTypeArguments.Length == 1) + { var modelType = subscriber.Type.GenericTypeArguments.Single(); subscriber.GenericType = typeof(Message<>).MakeGenericType(modelType); } @@ -124,7 +146,8 @@ protected virtual Task SubscribeImplAsync(Func ha return Task.CompletedTask; } - public async Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class { + public async Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Adding subscriber for {MessageType}.", typeof(T).FullName); @@ -132,44 +155,52 @@ public async Task SubscribeAsync(Func handler, Ca await EnsureTopicSubscriptionAsync(cancellationToken).AnyContext(); } - protected List GetMessageSubscribers(IMessage message) { + protected List GetMessageSubscribers(IMessage message) + { return _subscribers.Values.Where(s => SubscriberHandlesMessage(s, message)).ToList(); } - protected virtual bool SubscriberHandlesMessage(Subscriber subscriber, IMessage message) { + protected virtual bool SubscriberHandlesMessage(Subscriber subscriber, IMessage message) + { if (subscriber.Type == typeof(IMessage)) return true; var clrType = message.ClrType ?? GetMappedMessageType(message.Type); - if (clrType is null) { + if (clrType is null) + { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Unable to resolve CLR type for message body type: ClrType={MessageClrType} Type={MessageType}", message.ClrType, message.Type); - + return false; } if (subscriber.IsAssignableFrom(clrType)) return true; - + return false; } - protected virtual byte[] SerializeMessageBody(string messageType, object body) { + protected virtual byte[] SerializeMessageBody(string messageType, object body) + { if (body == null) return Array.Empty(); return _serializer.SerializeToBytes(body); } - protected virtual object DeserializeMessageBody(IMessage message) { + protected virtual object DeserializeMessageBody(IMessage message) + { if (message.Data is null || message.Data.Length == 0) return null; object body; - try { + try + { var clrType = message.ClrType ?? GetMappedMessageType(message.Type); body = clrType != null ? _serializer.Deserialize(message.Data, clrType) : message.Data; - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error deserializing message body: {Message}", ex.Message); @@ -179,7 +210,8 @@ protected virtual object DeserializeMessageBody(IMessage message) { return body; } - protected async Task SendMessageToSubscribersAsync(IMessage message) { + protected async Task SendMessageToSubscribersAsync(IMessage message) + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); var subscribers = GetMessageSubscribers(message); @@ -189,20 +221,27 @@ protected async Task SendMessageToSubscribersAsync(IMessage message) { if (subscribers.Count == 0) return; - var subscriberHandlers = subscribers.Select(subscriber => { - if (subscriber.CancellationToken.IsCancellationRequested) { - if (_subscribers.TryRemove(subscriber.Id, out _)) { + var subscriberHandlers = subscribers.Select(subscriber => + { + if (subscriber.CancellationToken.IsCancellationRequested) + { + if (_subscribers.TryRemove(subscriber.Id, out _)) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Removed cancelled subscriber: {SubscriberId}", subscriber.Id); - } else if (isTraceLogLevelEnabled) { + } + else if (isTraceLogLevelEnabled) + { _logger.LogTrace("Unable to remove cancelled subscriber: {SubscriberId}", subscriber.Id); } return Task.CompletedTask; } - return Task.Run(async () => { - if (subscriber.CancellationToken.IsCancellationRequested) { + return Task.Run(async () => + { + if (subscriber.CancellationToken.IsCancellationRequested) + { if (isTraceLogLevelEnabled) _logger.LogTrace("The cancelled subscriber action will not be called: {SubscriberId}", subscriber.Id); @@ -216,14 +255,20 @@ protected async Task SendMessageToSubscribersAsync(IMessage message) { using (_logger.BeginScope(s => s .PropertyIf("UniqueId", message.UniqueId, !String.IsNullOrEmpty(message.UniqueId)) - .PropertyIf("CorrelationId", message.CorrelationId, !String.IsNullOrEmpty(message.CorrelationId)))) { + .PropertyIf("CorrelationId", message.CorrelationId, !String.IsNullOrEmpty(message.CorrelationId)))) + { - if (subscriber.Type == typeof(IMessage)) { + if (subscriber.Type == typeof(IMessage)) + { await subscriber.Action(message, subscriber.CancellationToken).AnyContext(); - } else if (subscriber.GenericType != null) { + } + else if (subscriber.GenericType != null) + { object typedMessage = Activator.CreateInstance(subscriber.GenericType, message); await subscriber.Action(typedMessage, subscriber.CancellationToken).AnyContext(); - } else { + } + else + { await subscriber.Action(message.GetBody(), subscriber.CancellationToken).AnyContext(); } } @@ -233,9 +278,12 @@ protected async Task SendMessageToSubscribersAsync(IMessage message) { }); }); - try { + try + { await Task.WhenAll(subscriberHandlers.ToArray()); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogWarning(ex, "Error sending message to subscribers: {Message}", ex.Message); throw; @@ -245,7 +293,8 @@ protected async Task SendMessageToSubscribersAsync(IMessage message) { _logger.LogTrace("Done enqueueing message to {SubscriberCount} subscribers for message type {MessageType}", subscribers.Count, message.Type); } - protected virtual Activity StartHandleMessageActivity(IMessage message) { + protected virtual Activity StartHandleMessageActivity(IMessage message) + { var activity = FoundatioDiagnostics.ActivitySource.StartActivity("HandleMessage", ActivityKind.Server, message.CorrelationId); if (activity == null) @@ -261,7 +310,8 @@ protected virtual Activity StartHandleMessageActivity(IMessage message) { return activity; } - protected virtual void EnrichHandleMessageActivity(Activity activity, IMessage message) { + protected virtual void EnrichHandleMessageActivity(Activity activity, IMessage message) + { if (!activity.IsAllDataRequested) return; @@ -273,13 +323,15 @@ protected virtual void EnrichHandleMessageActivity(Activity activity, IMessage m if (message.Properties == null || message.Properties.Count <= 0) return; - foreach (var p in message.Properties) { + foreach (var p in message.Properties) + { if (p.Key != "TraceState") activity.AddTag(p.Key, p.Value); } } - protected Task AddDelayedMessageAsync(Type messageType, object message, TimeSpan delay) { + protected Task AddDelayedMessageAsync(Type messageType, object message, TimeSpan delay) + { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -288,24 +340,27 @@ protected Task AddDelayedMessageAsync(Type messageType, object message, TimeSpan return Task.CompletedTask; } - protected void SendDelayedMessage(Type messageType, object message, TimeSpan delay) { + protected void SendDelayedMessage(Type messageType, object message, TimeSpan delay) + { if (message == null) throw new ArgumentNullException(nameof(message)); - + if (delay <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(delay)); var sendTime = SystemClock.UtcNow.SafeAdd(delay); - Task.Factory.StartNew(async () => { + Task.Factory.StartNew(async () => + { await SystemClock.SleepSafeAsync(delay, _messageBusDisposedCancellationTokenSource.Token).AnyContext(); bool isTraceLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (_messageBusDisposedCancellationTokenSource.IsCancellationRequested) { + if (_messageBusDisposedCancellationTokenSource.IsCancellationRequested) + { if (isTraceLevelEnabled) _logger.LogTrace("Discarding delayed message scheduled for {SendTime:O} for type {MessageType}", sendTime, messageType); return; } - + if (isTraceLevelEnabled) _logger.LogTrace("Sending delayed message scheduled for {SendTime:O} for type {MessageType}", sendTime, messageType); @@ -315,14 +370,16 @@ protected void SendDelayedMessage(Type messageType, object message, TimeSpan del public string MessageBusId { get; protected set; } - public virtual void Dispose() { - if (_isDisposed) { + public virtual void Dispose() + { + if (_isDisposed) + { _logger.LogTrace("MessageBus {0} dispose was already called.", MessageBusId); return; } - + _isDisposed = true; - + _logger.LogTrace("MessageBus {0} dispose", MessageBusId); _subscribers?.Clear(); _messageBusDisposedCancellationTokenSource?.Cancel(); @@ -330,14 +387,16 @@ public virtual void Dispose() { } [DebuggerDisplay("MessageType: {MessageType} SendTime: {SendTime} Message: {Message}")] - protected class DelayedMessage { + protected class DelayedMessage + { public DateTime SendTime { get; set; } public Type MessageType { get; set; } public object Message { get; set; } } [DebuggerDisplay("Id: {Id} Type: {Type} CancellationToken: {CancellationToken}")] - protected class Subscriber { + protected class Subscriber + { private readonly ConcurrentDictionary _assignableTypesCache = new(); public string Id { get; private set; } = Guid.NewGuid().ToString("N"); @@ -346,12 +405,15 @@ protected class Subscriber { public Type GenericType { get; set; } public Func Action { get; set; } - public bool IsAssignableFrom(Type type) { + public bool IsAssignableFrom(Type type) + { if (type is null) return false; - - return _assignableTypesCache.GetOrAdd(type, t => { - if (t.IsClass) { + + return _assignableTypesCache.GetOrAdd(type, t => + { + if (t.IsClass) + { var typedMessageType = typeof(IMessage<>).MakeGenericType(t); if (Type == typedMessageType) return true; @@ -362,4 +424,4 @@ public bool IsAssignableFrom(Type type) { } } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Messaging/NullMessageBus.cs b/src/Foundatio/Messaging/NullMessageBus.cs index a7bce4390..bab93f414 100644 --- a/src/Foundatio/Messaging/NullMessageBus.cs +++ b/src/Foundatio/Messaging/NullMessageBus.cs @@ -2,18 +2,22 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Messaging { - public class NullMessageBus : IMessageBus { +namespace Foundatio.Messaging +{ + public class NullMessageBus : IMessageBus + { public static readonly NullMessageBus Instance = new(); - public Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default) { + public Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default) + { return Task.CompletedTask; } - public Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class { + public Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class + { return Task.CompletedTask; } - public void Dispose() {} + public void Dispose() { } } } diff --git a/src/Foundatio/Messaging/SharedMessageBusOptions.cs b/src/Foundatio/Messaging/SharedMessageBusOptions.cs index b7146a1a6..24dea2bdd 100644 --- a/src/Foundatio/Messaging/SharedMessageBusOptions.cs +++ b/src/Foundatio/Messaging/SharedMessageBusOptions.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; -namespace Foundatio.Messaging { - public class SharedMessageBusOptions : SharedOptions { +namespace Foundatio.Messaging +{ + public class SharedMessageBusOptions : SharedOptions + { /// /// The topic name /// @@ -16,28 +18,32 @@ public class SharedMessageBusOptions : SharedOptions { public class SharedMessageBusOptionsBuilder : SharedOptionsBuilder where TOptions : SharedMessageBusOptions, new() - where TBuilder : SharedMessageBusOptionsBuilder { - public TBuilder Topic(string topic) { + where TBuilder : SharedMessageBusOptionsBuilder + { + public TBuilder Topic(string topic) + { if (string.IsNullOrEmpty(topic)) throw new ArgumentNullException(nameof(topic)); Target.Topic = topic; return (TBuilder)this; } - public TBuilder MapMessageType(string name) { + public TBuilder MapMessageType(string name) + { if (Target.MessageTypeMappings == null) Target.MessageTypeMappings = new Dictionary(); - + Target.MessageTypeMappings[name] = typeof(T); return (TBuilder)this; } - public TBuilder MapMessageTypeToClassName() { + public TBuilder MapMessageTypeToClassName() + { if (Target.MessageTypeMappings == null) Target.MessageTypeMappings = new Dictionary(); - + Target.MessageTypeMappings[typeof(T).Name] = typeof(T); return (TBuilder)this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/BufferedMetricsClientBase.cs b/src/Foundatio/Metrics/BufferedMetricsClientBase.cs index 172405af8..906956ba5 100644 --- a/src/Foundatio/Metrics/BufferedMetricsClientBase.cs +++ b/src/Foundatio/Metrics/BufferedMetricsClientBase.cs @@ -5,13 +5,15 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.AsyncEx; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Metrics { - public abstract class BufferedMetricsClientBase : IBufferedMetricsClient { +namespace Foundatio.Metrics +{ + public abstract class BufferedMetricsClientBase : IBufferedMetricsClient + { protected readonly List _timeBuckets = new() { new TimeBucket { Size = TimeSpan.FromMinutes(1) } }; @@ -21,7 +23,8 @@ public abstract class BufferedMetricsClientBase : IBufferedMetricsClient { private readonly SharedMetricsClientOptions _options; protected readonly ILogger _logger; - public BufferedMetricsClientBase(SharedMetricsClientOptions options) { + public BufferedMetricsClientBase(SharedMetricsClientOptions options) + { _options = options; _logger = options.LoggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; if (options.Buffered) @@ -30,7 +33,8 @@ public BufferedMetricsClientBase(SharedMetricsClientOptions options) { public AsyncEvent Counted { get; } = new AsyncEvent(true); - protected virtual Task OnCountedAsync(long value) { + protected virtual Task OnCountedAsync(long value) + { var counted = Counted; if (counted == null) return Task.CompletedTask; @@ -39,7 +43,8 @@ protected virtual Task OnCountedAsync(long value) { return counted.InvokeAsync(this, args); } - public void Counter(string name, int value = 1) { + public void Counter(string name, int value = 1) + { var entry = new MetricEntry { Name = name, Type = MetricType.Counter, Counter = value }; if (!_options.Buffered) SubmitMetric(entry); @@ -47,7 +52,8 @@ public void Counter(string name, int value = 1) { _queue.Enqueue(entry); } - public void Gauge(string name, double value) { + public void Gauge(string name, double value) + { var entry = new MetricEntry { Name = name, Type = MetricType.Gauge, Gauge = value }; if (!_options.Buffered) SubmitMetric(entry); @@ -55,7 +61,8 @@ public void Gauge(string name, double value) { _queue.Enqueue(entry); } - public void Timer(string name, int milliseconds) { + public void Timer(string name, int milliseconds) + { var entry = new MetricEntry { Name = name, Type = MetricType.Timing, Timing = milliseconds }; if (!_options.Buffered) SubmitMetric(entry); @@ -63,32 +70,39 @@ public void Timer(string name, int milliseconds) { _queue.Enqueue(entry); } - private void OnMetricsTimer(object state) { + private void OnMetricsTimer(object state) + { if (_sendingMetrics || _queue.IsEmpty) return; - try { + try + { FlushAsync().AnyContext().GetAwaiter().GetResult(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error flushing metrics: {Message}", ex.Message); } } private bool _sendingMetrics = false; - public async Task FlushAsync() { + public async Task FlushAsync() + { if (_sendingMetrics || _queue.IsEmpty) return; bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (isTraceLogLevelEnabled) _logger.LogTrace("Flushing metrics: count={Count}", _queue.Count); - try { + try + { _sendingMetrics = true; var startTime = SystemClock.UtcNow; var entries = new List(); - while (_queue.TryDequeue(out var entry)) { + while (_queue.TryDequeue(out var entry)) + { entries.Add(entry); if (entry.EnqueuedDate > startTime) break; @@ -99,24 +113,31 @@ public async Task FlushAsync() { if (isTraceLogLevelEnabled) _logger.LogTrace("Dequeued {Count} metrics", entries.Count); await SubmitMetricsAsync(entries).AnyContext(); - } finally { + } + finally + { _sendingMetrics = false; } } - private void SubmitMetric(MetricEntry metric) { + private void SubmitMetric(MetricEntry metric) + { SubmitMetricsAsync(new List { metric }).AnyContext().GetAwaiter().GetResult(); } - protected virtual async Task SubmitMetricsAsync(List metrics) { + protected virtual async Task SubmitMetricsAsync(List metrics) + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - foreach (var timeBucket in _timeBuckets) { - try { + foreach (var timeBucket in _timeBuckets) + { + try + { // counters var counters = metrics.Where(e => e.Type == MetricType.Counter).ToList(); var groupedCounters = counters .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedCounterMetric { + .Select(e => new AggregatedCounterMetric + { Key = e.Key, Value = e.Sum(c => c.Counter), Entries = e.ToList() @@ -129,7 +150,8 @@ protected virtual async Task SubmitMetricsAsync(List metrics) { var gauges = metrics.Where(e => e.Type == MetricType.Gauge).ToList(); var groupedGauges = gauges .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedGaugeMetric { + .Select(e => new AggregatedGaugeMetric + { Key = e.Key, Count = e.Count(), Total = e.Sum(c => c.Gauge), @@ -146,7 +168,8 @@ protected virtual async Task SubmitMetricsAsync(List metrics) { var timings = metrics.Where(e => e.Type == MetricType.Timing).ToList(); var groupedTimings = timings .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedTimingMetric { + .Select(e => new AggregatedTimingMetric + { Key = e.Key, Count = e.Count(), TotalDuration = e.Sum(c => (long)c.Timing), @@ -161,7 +184,9 @@ protected virtual async Task SubmitMetricsAsync(List metrics) { // store aggregated metrics if (counters.Count > 0 || gauges.Count > 0 || timings.Count > 0) await StoreAggregatedMetricsInternalAsync(timeBucket, groupedCounters, groupedGauges, groupedTimings).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error aggregating metrics: {Message}", ex.Message); throw; @@ -169,14 +194,18 @@ protected virtual async Task SubmitMetricsAsync(List metrics) { } } - private async Task StoreAggregatedMetricsInternalAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) { + private async Task StoreAggregatedMetricsInternalAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (isTraceLogLevelEnabled) _logger.LogTrace("Storing {CountersCount} counters, {GaugesCount} gauges, {TimingsCount} timings.", counters.Count, gauges.Count, timings.Count); - try { + try + { await Run.WithRetriesAsync(() => StoreAggregatedMetricsAsync(timeBucket, counters, gauges, timings)).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error storing aggregated metrics: {Message}", ex.Message); throw; @@ -188,12 +217,14 @@ private async Task StoreAggregatedMetricsInternalAsync(TimeBucket timeBucket, IC protected abstract Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings); - public async Task WaitForCounterAsync(string statName, long count = 1, TimeSpan? timeout = null) { + public async Task WaitForCounterAsync(string statName, long count = 1, TimeSpan? timeout = null) + { using var cancellationTokenSource = timeout.ToCancellationTokenSource(TimeSpan.FromSeconds(10)); return await WaitForCounterAsync(statName, () => Task.CompletedTask, count, cancellationTokenSource.Token).AnyContext(); } - public async Task WaitForCounterAsync(string statName, Func work, long count = 1, CancellationToken cancellationToken = default) { + public async Task WaitForCounterAsync(string statName, Func work, long count = 1, CancellationToken cancellationToken = default) + { if (count <= 0) return true; @@ -202,11 +233,13 @@ public async Task WaitForCounterAsync(string statName, Func work, lo var start = SystemClock.UtcNow; bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - using (Counted.AddHandler((s, e) => { + using (Counted.AddHandler((s, e) => + { currentCount -= e.Value; resetEvent.Set(); return Task.CompletedTask; - })) { + })) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Wait: count={Count}", currentCount); if (work != null) @@ -215,10 +248,13 @@ public async Task WaitForCounterAsync(string statName, Func work, lo if (currentCount <= 0) return true; - do { - try { + do + { + try + { await resetEvent.WaitAsync(cancellationToken).AnyContext(); - } catch (OperationCanceledException) { } + } + catch (OperationCanceledException) { } if (isTraceLogLevelEnabled) _logger.LogTrace("Got signal: count={CurrentCount} expected={Count}", currentCount, count); @@ -231,14 +267,16 @@ public async Task WaitForCounterAsync(string statName, Func work, lo return currentCount <= 0; } - public virtual void Dispose() { + public virtual void Dispose() + { _flushTimer?.Dispose(); FlushAsync().AnyContext().GetAwaiter().GetResult(); _queue?.Clear(); } [DebuggerDisplay("Date: {EnqueuedDate} Type: {Type} Name: {Name} Counter: {Counter} Gauge: {Gauge} Timing: {Timing}")] - protected class MetricEntry { + protected class MetricEntry + { public DateTime EnqueuedDate { get; } = SystemClock.UtcNow; public string Name { get; set; } public MetricType Type { get; set; } @@ -247,35 +285,41 @@ protected class MetricEntry { public int Timing { get; set; } } - protected enum MetricType { + protected enum MetricType + { Counter, Gauge, Timing } [DebuggerDisplay("Time: {Time} Key: {Key}")] - protected class MetricBucket { + protected class MetricBucket + { public string Key { get; set; } public DateTime Time { get; set; } } - protected interface IAggregatedMetric where T: class { + protected interface IAggregatedMetric where T : class + { MetricKey Key { get; set; } ICollection Entries { get; set; } T Add(T other); } - protected class AggregatedCounterMetric : IAggregatedMetric { + protected class AggregatedCounterMetric : IAggregatedMetric + { public MetricKey Key { get; set; } public long Value { get; set; } public ICollection Entries { get; set; } - public AggregatedCounterMetric Add(AggregatedCounterMetric other) { + public AggregatedCounterMetric Add(AggregatedCounterMetric other) + { return this; } } - protected class AggregatedGaugeMetric : IAggregatedMetric { + protected class AggregatedGaugeMetric : IAggregatedMetric + { public MetricKey Key { get; set; } public int Count { get; set; } public double Total { get; set; } @@ -284,12 +328,14 @@ protected class AggregatedGaugeMetric : IAggregatedMetric public double Max { get; set; } public ICollection Entries { get; set; } - public AggregatedGaugeMetric Add(AggregatedGaugeMetric other) { + public AggregatedGaugeMetric Add(AggregatedGaugeMetric other) + { return this; } } - protected class AggregatedTimingMetric : IAggregatedMetric { + protected class AggregatedTimingMetric : IAggregatedMetric + { public MetricKey Key { get; set; } public int Count { get; set; } public long TotalDuration { get; set; } @@ -297,19 +343,22 @@ protected class AggregatedTimingMetric : IAggregatedMetric Entries { get; set; } - public AggregatedTimingMetric Add(AggregatedTimingMetric other) { + public AggregatedTimingMetric Add(AggregatedTimingMetric other) + { return this; } } [DebuggerDisplay("Size: {Size} Ttl: {Ttl}")] - protected struct TimeBucket { + protected struct TimeBucket + { public TimeSpan Size { get; set; } public TimeSpan Ttl { get; set; } } } - public class CountedEventArgs : EventArgs { + public class CountedEventArgs : EventArgs + { public long Value { get; set; } } } diff --git a/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs b/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs index 021a01b98..6512a54cb 100644 --- a/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs +++ b/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs @@ -6,12 +6,15 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Metrics { - public abstract class CacheBucketMetricsClientBase : BufferedMetricsClientBase, IMetricsClientStats { +namespace Foundatio.Metrics +{ + public abstract class CacheBucketMetricsClientBase : BufferedMetricsClientBase, IMetricsClientStats + { protected readonly ICacheClient _cache; private readonly string _prefix; - public CacheBucketMetricsClientBase(ICacheClient cache, SharedMetricsClientOptions options) : base(options) { + public CacheBucketMetricsClientBase(ICacheClient cache, SharedMetricsClientOptions options) : base(options) + { _cache = cache; _prefix = !String.IsNullOrEmpty(options.Prefix) ? (!options.Prefix.EndsWith(":") ? options.Prefix + ":" : options.Prefix) : String.Empty; @@ -20,7 +23,8 @@ public CacheBucketMetricsClientBase(ICacheClient cache, SharedMetricsClientOptio _timeBuckets.Add(new TimeBucket { Size = TimeSpan.FromHours(1), Ttl = TimeSpan.FromDays(7) }); } - protected override Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) { + protected override Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) + { var tasks = new List(); foreach (var counter in counters) tasks.Add(StoreCounterAsync(timeBucket, counter)); @@ -34,7 +38,8 @@ protected override Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, IColl return Task.WhenAll(tasks); } - private async Task StoreCounterAsync(TimeBucket timeBucket, AggregatedCounterMetric counter) { + private async Task StoreCounterAsync(TimeBucket timeBucket, AggregatedCounterMetric counter) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Storing counter name={Name} value={Value} time={Duration}", counter.Key.Name, counter.Value, counter.Key.Duration); @@ -44,7 +49,8 @@ private async Task StoreCounterAsync(TimeBucket timeBucket, AggregatedCounterMet if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing counter name={Name}", counter.Key.Name); } - private async Task StoreGaugeAsync(TimeBucket timeBucket, AggregatedGaugeMetric gauge) { + private async Task StoreGaugeAsync(TimeBucket timeBucket, AggregatedGaugeMetric gauge) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Storing gauge name={Name} count={Count} total={Total} last={Last} min={Min} max={Max} time={StartTimeUtc}", gauge.Key.Name, gauge.Count, gauge.Total, gauge.Last, gauge.Min, gauge.Max, gauge.Key.StartTimeUtc); @@ -65,7 +71,8 @@ await Task.WhenAll( if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing gauge name={Name}", gauge.Key.Name); } - private async Task StoreTimingAsync(TimeBucket timeBucket, AggregatedTimingMetric timing) { + private async Task StoreTimingAsync(TimeBucket timeBucket, AggregatedTimingMetric timing) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Storing timing name={Name} count={Count} total={TotalDuration} min={MinDuration} max={MaxDuration} time={StartTimeUtc}", timing.Key.Name, timing.Count, timing.TotalDuration, timing.MinDuration, timing.MaxDuration, timing.Key.StartTimeUtc); @@ -84,7 +91,8 @@ await Task.WhenAll( if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing timing name={Name}", timing.Key.Name); } - public async Task GetCounterStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) { + public async Task GetCounterStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) + { if (!start.HasValue) start = SystemClock.UtcNow.AddHours(-4); @@ -97,16 +105,19 @@ public async Task GetCounterStatsAsync(string name, DateTime var countResults = await _cache.GetAllAsync(countBuckets.Select(k => k.Key)).AnyContext(); ICollection stats = new List(); - foreach (var bucket in countBuckets) { + foreach (var bucket in countBuckets) + { string countKey = bucket.Key; - stats.Add(new CounterStat { + stats.Add(new CounterStat + { Time = bucket.Time, Count = countResults[countKey].Value }); } - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new CounterStat { + stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new CounterStat + { Time = d, Count = s.Sum(i => i.Count) }, dataPoints); @@ -114,7 +125,8 @@ public async Task GetCounterStatsAsync(string name, DateTime return new CounterStatSummary(name, stats, start.Value, end.Value); } - public async Task GetGaugeStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) { + public async Task GetGaugeStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) + { if (!start.HasValue) start = SystemClock.UtcNow.AddHours(-4); @@ -138,14 +150,16 @@ public async Task GetGaugeStatsAsync(string name, DateTime? st await Task.WhenAll(countTask, totalTask, lastTask, minTask, maxTask).AnyContext(); ICollection stats = new List(); - for (int i = 0; i < maxBuckets.Count; i++) { + for (int i = 0; i < maxBuckets.Count; i++) + { string countKey = countBuckets[i].Key; string totalKey = totalBuckets[i].Key; string minKey = minBuckets[i].Key; string maxKey = maxBuckets[i].Key; string lastKey = lastBuckets[i].Key; - stats.Add(new GaugeStat { + stats.Add(new GaugeStat + { Time = maxBuckets[i].Time, Count = countTask.Result[countKey].Value, Total = totalTask.Result[totalKey].Value, @@ -155,7 +169,8 @@ public async Task GetGaugeStatsAsync(string name, DateTime? st }); } - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new GaugeStat { + stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new GaugeStat + { Time = d, Count = s.Sum(i => i.Count), Total = s.Sum(i => i.Total), @@ -167,7 +182,8 @@ public async Task GetGaugeStatsAsync(string name, DateTime? st return new GaugeStatSummary(name, stats, start.Value, end.Value); } - public async Task GetTimerStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) { + public async Task GetTimerStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) + { if (!start.HasValue) start = SystemClock.UtcNow.AddHours(-4); @@ -189,13 +205,15 @@ public async Task GetTimerStatsAsync(string name, DateTime? s await Task.WhenAll(countTask, durationTask, minTask, maxTask).AnyContext(); ICollection stats = new List(); - for (int i = 0; i < countBuckets.Count; i++) { + for (int i = 0; i < countBuckets.Count; i++) + { string countKey = countBuckets[i].Key; string durationKey = durationBuckets[i].Key; string minKey = minBuckets[i].Key; string maxKey = maxBuckets[i].Key; - stats.Add(new TimingStat { + stats.Add(new TimingStat + { Time = countBuckets[i].Time, Count = countTask.Result[countKey].Value, TotalDuration = durationTask.Result[durationKey].Value, @@ -204,7 +222,8 @@ public async Task GetTimerStatsAsync(string name, DateTime? s }); } - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new TimingStat { + stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new TimingStat + { Time = d, Count = s.Sum(i => i.Count), MinDuration = s.Min(i => i.MinDuration), @@ -215,7 +234,8 @@ public async Task GetTimerStatsAsync(string name, DateTime? s return new TimingStatSummary(name, stats, start.Value, end.Value); } - private string GetBucketKey(string metricType, string statName, DateTime? dateTime = null, TimeSpan? interval = null, string suffix = null) { + private string GetBucketKey(string metricType, string statName, DateTime? dateTime = null, TimeSpan? interval = null, string suffix = null) + { if (interval == null) interval = _timeBuckets[0].Size; @@ -228,7 +248,8 @@ private string GetBucketKey(string metricType, string statName, DateTime? dateTi return String.Concat(_prefix, "m:", metricType, ":", statName, ":", interval.Value.TotalMinutes, ":", dateTime.Value.ToString("yy-MM-dd-hh-mm"), suffix); } - private List GetMetricBuckets(string metricType, string statName, DateTime start, DateTime end, TimeSpan? interval = null, string suffix = null) { + private List GetMetricBuckets(string metricType, string statName, DateTime start, DateTime end, TimeSpan? interval = null, string suffix = null) + { if (interval == null) interval = _timeBuckets[0].Size; @@ -237,7 +258,8 @@ private List GetMetricBuckets(string metricType, string statName, var current = start; var keys = new List(); - while (current <= end) { + while (current <= end) + { keys.Add(new MetricBucket { Key = GetBucketKey(metricType, statName, current, interval, suffix), Time = current }); current = current.Add(interval.Value); } @@ -245,7 +267,8 @@ private List GetMetricBuckets(string metricType, string statName, return keys; } - private class CacheMetricNames { + private class CacheMetricNames + { public const string Counter = "c"; public const string Gauge = "g"; public const string Timing = "t"; diff --git a/src/Foundatio/Metrics/CounterStat.cs b/src/Foundatio/Metrics/CounterStat.cs index 570cc68a1..6a1022830 100644 --- a/src/Foundatio/Metrics/CounterStat.cs +++ b/src/Foundatio/Metrics/CounterStat.cs @@ -4,16 +4,20 @@ using System.Linq; using Foundatio.Utility; -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ [DebuggerDisplay("Time: {Time} Count: {Count}")] - public class CounterStat { + public class CounterStat + { public DateTime Time { get; set; } public long Count { get; set; } } [DebuggerDisplay("Time: {StartTime}-{EndTime} Count: {Count}")] - public class CounterStatSummary { - public CounterStatSummary(string name, ICollection stats, DateTime start, DateTime end) { + public class CounterStatSummary + { + public CounterStatSummary(string name, ICollection stats, DateTime start, DateTime end) + { Name = name; Stats.AddRange(stats); Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; @@ -27,8 +31,9 @@ public CounterStatSummary(string name, ICollection stats, DateTime public ICollection Stats { get; } = new List(); public long Count { get; private set; } - public override string ToString() { + public override string ToString() + { return $"Counter: {Name} Value: {Count}"; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs b/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs index b5a6a2415..0bfda25e7 100644 --- a/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs +++ b/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs @@ -2,8 +2,10 @@ using System.Collections.Concurrent; using System.Diagnostics.Metrics; -namespace Foundatio.Metrics { - public class DiagnosticsMetricsClient : IMetricsClient { +namespace Foundatio.Metrics +{ + public class DiagnosticsMetricsClient : IMetricsClient + { private readonly ConcurrentDictionary> _counters = new(); private readonly ConcurrentDictionary _gauges = new(); private readonly ConcurrentDictionary> _timers = new(); @@ -12,7 +14,8 @@ public class DiagnosticsMetricsClient : IMetricsClient { public DiagnosticsMetricsClient() : this(o => o) { } - public DiagnosticsMetricsClient(DiagnosticsMetricsClientOptions options) { + public DiagnosticsMetricsClient(DiagnosticsMetricsClientOptions options) + { _prefix = !String.IsNullOrEmpty(options.Prefix) ? (!options.Prefix.EndsWith(".") ? options.Prefix + "." : options.Prefix) : String.Empty; _meter = new Meter(options.MeterName ?? "Foundatio.MetricsClient", options.MeterVersion ?? FoundatioDiagnostics.AssemblyName.Version.ToString()); } @@ -20,27 +23,33 @@ public DiagnosticsMetricsClient(DiagnosticsMetricsClientOptions options) { public DiagnosticsMetricsClient(Builder config) : this(config(new DiagnosticsMetricsClientOptionsBuilder()).Build()) { } - public void Counter(string name, int value = 1) { + public void Counter(string name, int value = 1) + { var counter = _counters.GetOrAdd(_prefix + name, _meter.CreateCounter(name)); counter.Add(value); } - public void Gauge(string name, double value) { + public void Gauge(string name, double value) + { var gauge = _gauges.GetOrAdd(_prefix + name, new GaugeInfo(_meter, name)); gauge.Value = value; } - public void Timer(string name, int milliseconds) { + public void Timer(string name, int milliseconds) + { var timer = _timers.GetOrAdd(_prefix + name, _meter.CreateHistogram(name, "ms")); timer.Record(milliseconds); } - public void Dispose() { + public void Dispose() + { _meter.Dispose(); } - private class GaugeInfo { - public GaugeInfo(Meter meter, string name) { + private class GaugeInfo + { + public GaugeInfo(Meter meter, string name) + { Gauge = meter.CreateObservableGauge(name, () => Value); } diff --git a/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs b/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs index a1d796912..6507b2508 100644 --- a/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs +++ b/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs @@ -1,24 +1,29 @@ using System; -namespace Foundatio.Metrics { - public class DiagnosticsMetricsClientOptions : SharedMetricsClientOptions { +namespace Foundatio.Metrics +{ + public class DiagnosticsMetricsClientOptions : SharedMetricsClientOptions + { public string MeterName { get; set; } public string MeterVersion { get; set; } } - public class DiagnosticsMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder { - public DiagnosticsMetricsClientOptionsBuilder MeterName(string name) { + public class DiagnosticsMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder + { + public DiagnosticsMetricsClientOptionsBuilder MeterName(string name) + { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); Target.MeterName = name; return this; } - public DiagnosticsMetricsClientOptionsBuilder MeterVersion(string version) { + public DiagnosticsMetricsClientOptionsBuilder MeterVersion(string version) + { if (String.IsNullOrEmpty(version)) throw new ArgumentNullException(nameof(version)); Target.MeterVersion = version; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/GaugeStat.cs b/src/Foundatio/Metrics/GaugeStat.cs index 0c33982c1..69b8b596e 100644 --- a/src/Foundatio/Metrics/GaugeStat.cs +++ b/src/Foundatio/Metrics/GaugeStat.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using System.Linq; -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ [DebuggerDisplay("Time: {Time} Max: {Max} Last: {Last}")] - public class GaugeStat { + public class GaugeStat + { public DateTime Time { get; set; } public int Count { get; set; } public double Total { get; set; } @@ -16,8 +18,10 @@ public class GaugeStat { } [DebuggerDisplay("Time: {StartTime}-{EndTime} Max: {Max} Last: {Last}")] - public class GaugeStatSummary { - public GaugeStatSummary(string name, ICollection stats, DateTime start, DateTime end) { + public class GaugeStatSummary + { + public GaugeStatSummary(string name, ICollection stats, DateTime start, DateTime end) + { Name = name; Stats = stats; Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; @@ -41,8 +45,9 @@ public GaugeStatSummary(string name, ICollection stats, DateTime star public double Max { get; } public double Average { get; } - public override string ToString() { + public override string ToString() + { return $"Counter: {Name} Time: {StartTime}-{EndTime} Max: {Max} Last: {Last}"; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/IHaveSubMetricName.cs b/src/Foundatio/Metrics/IHaveSubMetricName.cs index 60d037ba7..34e62ff00 100644 --- a/src/Foundatio/Metrics/IHaveSubMetricName.cs +++ b/src/Foundatio/Metrics/IHaveSubMetricName.cs @@ -1,5 +1,7 @@ -namespace Foundatio.Metrics { - public interface IHaveSubMetricName { +namespace Foundatio.Metrics +{ + public interface IHaveSubMetricName + { string SubMetricName { get; } } } diff --git a/src/Foundatio/Metrics/IMetricsClient.cs b/src/Foundatio/Metrics/IMetricsClient.cs index 5d2c55b27..75bf44ccd 100644 --- a/src/Foundatio/Metrics/IMetricsClient.cs +++ b/src/Foundatio/Metrics/IMetricsClient.cs @@ -2,36 +2,44 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ [Obsolete("IMetricsClient will be removed, use System.Diagnostics.Metrics.Meter instead.")] - public interface IMetricsClient : IDisposable { + public interface IMetricsClient : IDisposable + { void Counter(string name, int value = 1); void Gauge(string name, double value); void Timer(string name, int milliseconds); } - public interface IBufferedMetricsClient : IMetricsClient { + public interface IBufferedMetricsClient : IMetricsClient + { Task FlushAsync(); } - public static class MetricsClientExtensions { - public static IDisposable StartTimer(this IMetricsClient client, string name) { + public static class MetricsClientExtensions + { + public static IDisposable StartTimer(this IMetricsClient client, string name) + { return new MetricTimer(name, client); } - public static async Task TimeAsync(this IMetricsClient client, Func action, string name) { + public static async Task TimeAsync(this IMetricsClient client, Func action, string name) + { using (client.StartTimer(name)) await action().AnyContext(); } - public static void Time(this IMetricsClient client, Action action, string name) { + public static void Time(this IMetricsClient client, Action action, string name) + { using (client.StartTimer(name)) action(); } - public static async Task TimeAsync(this IMetricsClient client, Func> func, string name) { + public static async Task TimeAsync(this IMetricsClient client, Func> func, string name) + { using (client.StartTimer(name)) return await func().AnyContext(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/IMetricsClientStats.cs b/src/Foundatio/Metrics/IMetricsClientStats.cs index 4a1bbc333..d36cfe7e1 100644 --- a/src/Foundatio/Metrics/IMetricsClientStats.cs +++ b/src/Foundatio/Metrics/IMetricsClientStats.cs @@ -1,27 +1,33 @@ using System; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.Queues; +using Foundatio.Utility; -namespace Foundatio.Metrics { - public interface IMetricsClientStats { +namespace Foundatio.Metrics +{ + public interface IMetricsClientStats + { Task GetCounterStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); Task GetGaugeStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); Task GetTimerStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); } - public static class MetricsClientStatsExtensions { - public static async Task GetCounterCountAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) { + public static class MetricsClientStatsExtensions + { + public static async Task GetCounterCountAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) + { var result = await stats.GetCounterStatsAsync(name, utcStart, utcEnd, 1).AnyContext(); return result.Count; } - public static async Task GetLastGaugeValueAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) { + public static async Task GetLastGaugeValueAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) + { var result = await stats.GetGaugeStatsAsync(name, utcStart, utcEnd, 1).AnyContext(); return result.Last; } - public static async Task GetQueueStatsAsync(this IMetricsClientStats stats, string name, string subMetricName = null, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20) { + public static async Task GetQueueStatsAsync(this IMetricsClientStats stats, string name, string subMetricName = null, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20) + { if (subMetricName == null) subMetricName = String.Empty; else @@ -39,7 +45,8 @@ public static async Task GetQueueStatsAsync(this IMetricsClien await Task.WhenAll(countTask, workingTask, deadletterTask, enqueuedTask, queueTimeTask, dequeuedTask, completedTask, abandonedTask, processTimeTask).AnyContext(); - return new QueueStatSummary { + return new QueueStatSummary + { Count = countTask.Result, Working = workingTask.Result, Deadletter = deadletterTask.Result, @@ -52,4 +59,4 @@ public static async Task GetQueueStatsAsync(this IMetricsClien }; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/InMemoryMetricsClient.cs b/src/Foundatio/Metrics/InMemoryMetricsClient.cs index da60a6563..12ef43271 100644 --- a/src/Foundatio/Metrics/InMemoryMetricsClient.cs +++ b/src/Foundatio/Metrics/InMemoryMetricsClient.cs @@ -1,17 +1,20 @@ using System; using Foundatio.Caching; -namespace Foundatio.Metrics { - public class InMemoryMetricsClient : CacheBucketMetricsClientBase { - public InMemoryMetricsClient() : this(o => o) {} - - public InMemoryMetricsClient(InMemoryMetricsClientOptions options) +namespace Foundatio.Metrics +{ + public class InMemoryMetricsClient : CacheBucketMetricsClientBase + { + public InMemoryMetricsClient() : this(o => o) { } + + public InMemoryMetricsClient(InMemoryMetricsClientOptions options) : base(new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = options?.LoggerFactory }), options) { } public InMemoryMetricsClient(Builder config) : this(config(new InMemoryMetricsClientOptionsBuilder()).Build()) { } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _cache.Dispose(); } diff --git a/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs b/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs index 395409b45..9687aa3d2 100644 --- a/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs +++ b/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs @@ -1,5 +1,6 @@ -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ public class InMemoryMetricsClientOptions : SharedMetricsClientOptions { } - public class InMemoryMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder {} -} \ No newline at end of file + public class InMemoryMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder { } +} diff --git a/src/Foundatio/Metrics/MetricKey.cs b/src/Foundatio/Metrics/MetricKey.cs index 18cdb9938..1887280c0 100644 --- a/src/Foundatio/Metrics/MetricKey.cs +++ b/src/Foundatio/Metrics/MetricKey.cs @@ -1,8 +1,11 @@ using System; -namespace Foundatio.Metrics { - public struct MetricKey : IEquatable { - public MetricKey(DateTime startTimeUtc, TimeSpan duration, string name) { +namespace Foundatio.Metrics +{ + public struct MetricKey : IEquatable + { + public MetricKey(DateTime startTimeUtc, TimeSpan duration, string name) + { StartTimeUtc = startTimeUtc; Duration = duration; Name = name; @@ -14,29 +17,35 @@ public MetricKey(DateTime startTimeUtc, TimeSpan duration, string name) { public DateTime EndTimeUtc => StartTimeUtc.Add(Duration); - public bool Equals(MetricKey other) { + public bool Equals(MetricKey other) + { return StartTimeUtc == other.StartTimeUtc && Duration == other.Duration && String.Equals(Name, other.Name); } - public override bool Equals(object obj) { + public override bool Equals(object obj) + { if (obj is null) return false; return obj is MetricKey key && Equals(key); } - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { return (StartTimeUtc.GetHashCode() * 397) ^ (Duration.GetHashCode() * 397) ^ (Name?.GetHashCode() ?? 0); } } - public static bool operator ==(MetricKey left, MetricKey right) { + public static bool operator ==(MetricKey left, MetricKey right) + { return left.Equals(right); } - public static bool operator !=(MetricKey left, MetricKey right) { + public static bool operator !=(MetricKey left, MetricKey right) + { return !left.Equals(right); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/MetricTimer.cs b/src/Foundatio/Metrics/MetricTimer.cs index ff84b479d..b578a838d 100644 --- a/src/Foundatio/Metrics/MetricTimer.cs +++ b/src/Foundatio/Metrics/MetricTimer.cs @@ -3,20 +3,24 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Metrics { - public class MetricTimer : IDisposable { +namespace Foundatio.Metrics +{ + public class MetricTimer : IDisposable + { private readonly string _name; private readonly Stopwatch _stopWatch; private bool _disposed; private readonly IMetricsClient _client; - public MetricTimer(string name, IMetricsClient client) { + public MetricTimer(string name, IMetricsClient client) + { _name = name; _client = client; _stopWatch = Stopwatch.StartNew(); } - public void Dispose() { + public void Dispose() + { if (_disposed) return; @@ -25,4 +29,4 @@ public void Dispose() { _client.Timer(_name, (int)_stopWatch.ElapsedMilliseconds); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/NullMetricsClient.cs b/src/Foundatio/Metrics/NullMetricsClient.cs index 409f4b004..8f1ebab0f 100644 --- a/src/Foundatio/Metrics/NullMetricsClient.cs +++ b/src/Foundatio/Metrics/NullMetricsClient.cs @@ -1,11 +1,13 @@ using System.Threading.Tasks; -namespace Foundatio.Metrics { - public class NullMetricsClient : IMetricsClient { +namespace Foundatio.Metrics +{ + public class NullMetricsClient : IMetricsClient + { public static readonly IMetricsClient Instance = new NullMetricsClient(); - public void Counter(string name, int value = 1) {} - public void Gauge(string name, double value) {} - public void Timer(string name, int milliseconds) {} - public void Dispose() {} + public void Counter(string name, int value = 1) { } + public void Gauge(string name, double value) { } + public void Timer(string name, int milliseconds) { } + public void Dispose() { } } } diff --git a/src/Foundatio/Metrics/SharedMetricsClientOptions.cs b/src/Foundatio/Metrics/SharedMetricsClientOptions.cs index 6c5e42f4f..1af0cd8a8 100644 --- a/src/Foundatio/Metrics/SharedMetricsClientOptions.cs +++ b/src/Foundatio/Metrics/SharedMetricsClientOptions.cs @@ -1,20 +1,25 @@ using System; -namespace Foundatio.Metrics { - public class SharedMetricsClientOptions : SharedOptions { +namespace Foundatio.Metrics +{ + public class SharedMetricsClientOptions : SharedOptions + { public bool Buffered { get; set; } = true; public string Prefix { get; set; } } - public class SharedMetricsClientOptionsBuilder : SharedOptionsBuilder - where TOption: SharedMetricsClientOptions ,new() - where TBuilder: SharedMetricsClientOptionsBuilder { - public TBuilder Buffered(bool buffered) { + public class SharedMetricsClientOptionsBuilder : SharedOptionsBuilder + where TOption : SharedMetricsClientOptions, new() + where TBuilder : SharedMetricsClientOptionsBuilder + { + public TBuilder Buffered(bool buffered) + { Target.Buffered = buffered; return (TBuilder)this; } - public TBuilder Prefix(string prefix) { + public TBuilder Prefix(string prefix) + { Target.Prefix = prefix; return (TBuilder)this; } @@ -23,4 +28,4 @@ public TBuilder Prefix(string prefix) { public TBuilder DisableBuffer() => Buffered(false); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/StatsDMetricsClient.cs b/src/Foundatio/Metrics/StatsDMetricsClient.cs index dba48ed04..813db11b9 100644 --- a/src/Foundatio/Metrics/StatsDMetricsClient.cs +++ b/src/Foundatio/Metrics/StatsDMetricsClient.cs @@ -8,17 +8,20 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] [Obsolete("StatsDMetricsClient will be removed, use System.Diagnostics.Metrics.Meter instead.")] - public class StatsDMetricsClient : IMetricsClient { + public class StatsDMetricsClient : IMetricsClient + { private readonly object _lock = new(); private Socket _socket; private readonly IPEndPoint _endPoint; private readonly StatsDMetricsClientOptions _options; private readonly ILogger _logger; - public StatsDMetricsClient(StatsDMetricsClientOptions options) { + public StatsDMetricsClient(StatsDMetricsClientOptions options) + { _options = options; _logger = options.LoggerFactory?.CreateLogger() ?? NullLogger.Instance; _endPoint = GetIPEndPointFromHostName(options.ServerName, options.Port, false); @@ -27,52 +30,63 @@ public StatsDMetricsClient(StatsDMetricsClientOptions options) { options.Prefix = options.Prefix.EndsWith(".") ? options.Prefix : String.Concat(options.Prefix, "."); } - public StatsDMetricsClient(Builder config) + public StatsDMetricsClient(Builder config) : this(config(new StatsDMetricsClientOptionsBuilder()).Build()) { } - public void Counter(string name, int value = 1) { + public void Counter(string name, int value = 1) + { Send(BuildMetric("c", name, value.ToString(CultureInfo.InvariantCulture))); } - public void Gauge(string name, double value) { + public void Gauge(string name, double value) + { Send(BuildMetric("g", name, value.ToString(CultureInfo.InvariantCulture))); } - public void Timer(string name, int milliseconds) { + public void Timer(string name, int milliseconds) + { Send(BuildMetric("ms", name, milliseconds.ToString(CultureInfo.InvariantCulture))); } - private string BuildMetric(string type, string statName, string value) { + private string BuildMetric(string type, string statName, string value) + { return String.Concat(_options.Prefix, statName, ":", value, "|", type); } - private void Send(string metric) { + private void Send(string metric) + { if (String.IsNullOrEmpty(metric)) return; - try { + try + { var data = Encoding.ASCII.GetBytes(metric); EnsureSocket(); - lock (_lock) { + lock (_lock) + { _logger.LogTrace("Sending metric: {Metric}", metric); _socket.SendTo(data, _endPoint); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "An error occurred while sending the metrics: {Message}", ex.Message); CloseSocket(); } } - private void EnsureSocket() { + private void EnsureSocket() + { _logger.LogTrace("EnsureSocket"); if (_socket != null) return; - lock (_lock) { + lock (_lock) + { if (_socket != null) return; - + _logger.LogTrace("Creating socket"); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) @@ -80,39 +94,49 @@ private void EnsureSocket() { } } - private void CloseSocket() { + private void CloseSocket() + { _logger.LogTrace("CloseSocket"); - + if (_socket == null) return; - lock (_lock) { + lock (_lock) + { if (_socket == null) return; _logger.LogTrace("Closing socket"); - try { + try + { _socket.Close(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "An error occurred while calling Close() on the socket"); - } finally { + } + finally + { _socket = null; } } } - - private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP) { + + private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP) + { var addresses = Dns.GetHostAddresses(hostName); - if (addresses.Length == 0) { + if (addresses.Length == 0) + { throw new ArgumentException( - "Unable to retrieve address from specified host name.", + "Unable to retrieve address from specified host name.", nameof(hostName) ); } - if (throwIfMoreThanOneIP && addresses.Length > 1) { + if (throwIfMoreThanOneIP && addresses.Length > 1) + { throw new ArgumentException( - "There is more that one IP address to the specified host.", + "There is more that one IP address to the specified host.", nameof(hostName) ); } @@ -120,8 +144,9 @@ private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool thr return new IPEndPoint(addresses[0], port); } - public void Dispose() { + public void Dispose() + { CloseSocket(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs b/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs index 459af9df3..6bbd0116f 100644 --- a/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs +++ b/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs @@ -1,13 +1,17 @@ using System; -namespace Foundatio.Metrics { - public class StatsDMetricsClientOptions : SharedMetricsClientOptions { +namespace Foundatio.Metrics +{ + public class StatsDMetricsClientOptions : SharedMetricsClientOptions + { public string ServerName { get; set; } public int Port { get; set; } = 8125; } - public class StatsDMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder { - public StatsDMetricsClientOptionsBuilder Server(string serverName, int port = 8125) { + public class StatsDMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder + { + public StatsDMetricsClientOptionsBuilder Server(string serverName, int port = 8125) + { if (String.IsNullOrEmpty(serverName)) throw new ArgumentNullException(nameof(serverName)); Target.ServerName = serverName; diff --git a/src/Foundatio/Metrics/TimingStat.cs b/src/Foundatio/Metrics/TimingStat.cs index 70df03812..2f51b3aea 100644 --- a/src/Foundatio/Metrics/TimingStat.cs +++ b/src/Foundatio/Metrics/TimingStat.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using System.Linq; -namespace Foundatio.Metrics { +namespace Foundatio.Metrics +{ [DebuggerDisplay("Time: {Time} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}")] - public class TimingStat { + public class TimingStat + { public DateTime Time { get; set; } public int Count { get; set; } public long TotalDuration { get; set; } @@ -15,8 +17,10 @@ public class TimingStat { } [DebuggerDisplay("Time: {StartTime}-{EndTime} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}")] - public class TimingStatSummary { - public TimingStatSummary(string name, ICollection stats, DateTime start, DateTime end) { + public class TimingStatSummary + { + public TimingStatSummary(string name, ICollection stats, DateTime start, DateTime end) + { Name = name; Stats = stats; Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; @@ -38,8 +42,9 @@ public TimingStatSummary(string name, ICollection stats, DateTime st public long TotalDuration { get; } public double AverageDuration { get; } - public override string ToString() { + public override string ToString() + { return $"Timing: {Name} Time: {StartTime}-{EndTime} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}"; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs index 250bd26e8..19b7405d0 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs @@ -6,7 +6,8 @@ // Original idea by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266923.aspx -namespace Foundatio.AsyncEx { +namespace Foundatio.AsyncEx +{ /// /// An async-compatible auto-reset event. /// diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs index 5c85e2e23..9a449e4f8 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs @@ -4,7 +4,8 @@ using System.Threading.Tasks; using Foundatio.AsyncEx.Synchronous; -namespace Foundatio.AsyncEx { +namespace Foundatio.AsyncEx +{ /// /// An async-compatible condition variable. This type uses Mesa-style semantics (the notifying tasks do not yield). /// diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncCountdownEvent.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncCountdownEvent.cs index a7fafba5f..0b2893427 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncCountdownEvent.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncCountdownEvent.cs @@ -5,7 +5,8 @@ // Original idea by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266930.aspx -namespace Foundatio.AsyncEx { +namespace Foundatio.AsyncEx +{ /// /// An async-compatible countdown event. /// diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLazy.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLazy.cs index bb47fceea..78f393aa6 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLazy.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLazy.cs @@ -1,7 +1,7 @@ using System; -using System.Threading.Tasks; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading.Tasks; namespace Foundatio.AsyncEx { @@ -166,9 +166,9 @@ public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) /// public void Start() { -// ReSharper disable UnusedVariable + // ReSharper disable UnusedVariable var unused = Task; -// ReSharper restore UnusedVariable + // ReSharper restore UnusedVariable } internal enum LazyState diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLock.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLock.cs index 1de910c84..210cf30c0 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLock.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncLock.cs @@ -70,7 +70,7 @@ public sealed class AsyncLock /// Creates a new async-compatible mutual exclusion lock. /// public AsyncLock() - :this(null) + : this(null) { } diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncManualResetEvent.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncManualResetEvent.cs index 4a2390bd0..758004234 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncManualResetEvent.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncManualResetEvent.cs @@ -1,5 +1,5 @@ -using System.Threading; -using System.Diagnostics; +using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Foundatio.AsyncEx.Synchronous; diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs index b1c411ea0..f66a7efe3 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs @@ -1,8 +1,8 @@ -using Foundatio.AsyncEx.Synchronous; -using System; +using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Foundatio.AsyncEx.Synchronous; // Original idea from Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock.aspx diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncSemaphore.cs b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncSemaphore.cs index b12d04416..919616043 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/AsyncSemaphore.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/AsyncSemaphore.cs @@ -6,7 +6,8 @@ // Original idea from Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266983.aspx -namespace Foundatio.AsyncEx { +namespace Foundatio.AsyncEx +{ /// /// An async-compatible semaphore. Alternatively, you could use SemaphoreSlim. /// diff --git a/src/Foundatio/Nito.AsyncEx.Coordination/IdManager.cs b/src/Foundatio/Nito.AsyncEx.Coordination/IdManager.cs index f29ad8aba..b39be89fa 100644 --- a/src/Foundatio/Nito.AsyncEx.Coordination/IdManager.cs +++ b/src/Foundatio/Nito.AsyncEx.Coordination/IdManager.cs @@ -8,14 +8,14 @@ namespace Foundatio.AsyncEx /// The type for which ids are generated. // ReSharper disable UnusedTypeParameter internal static class IdManager -// ReSharper restore UnusedTypeParameter + // ReSharper restore UnusedTypeParameter { /// /// The last id generated for this type. This is 0 if no ids have been generated. /// // ReSharper disable StaticFieldInGenericType private static int _lastId; -// ReSharper restore StaticFieldInGenericType + // ReSharper restore StaticFieldInGenericType /// /// Returns the id, allocating it if necessary. diff --git a/src/Foundatio/Nito.AsyncEx.Tasks/AwaitableDisposable.cs b/src/Foundatio/Nito.AsyncEx.Tasks/AwaitableDisposable.cs index 5f4a2397d..a8acc958a 100644 --- a/src/Foundatio/Nito.AsyncEx.Tasks/AwaitableDisposable.cs +++ b/src/Foundatio/Nito.AsyncEx.Tasks/AwaitableDisposable.cs @@ -1,6 +1,6 @@ using System; -using System.Threading.Tasks; using System.Runtime.CompilerServices; +using System.Threading.Tasks; namespace Foundatio.AsyncEx { diff --git a/src/Foundatio/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs b/src/Foundatio/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs index d5195eb03..79695e260 100644 --- a/src/Foundatio/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs +++ b/src/Foundatio/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs @@ -43,4 +43,4 @@ public void Dispose() _registration?.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Nito.AsyncEx.Tasks/ExceptionHelpers.cs b/src/Foundatio/Nito.AsyncEx.Tasks/ExceptionHelpers.cs index f95480dc3..363d68950 100644 --- a/src/Foundatio/Nito.AsyncEx.Tasks/ExceptionHelpers.cs +++ b/src/Foundatio/Nito.AsyncEx.Tasks/ExceptionHelpers.cs @@ -16,4 +16,4 @@ public static Exception PrepareForRethrow(Exception exception) // https://connect.microsoft.com/VisualStudio/feedback/details/689516/exceptiondispatchinfo-api-modifications (http://www.webcitation.org/6XQ7RoJmO) return exception; } -} \ No newline at end of file +} diff --git a/src/Foundatio/Nito.AsyncEx.Tasks/TaskExtensions.cs b/src/Foundatio/Nito.AsyncEx.Tasks/TaskExtensions.cs index 7ad93b7f1..09d3c79bb 100644 --- a/src/Foundatio/Nito.AsyncEx.Tasks/TaskExtensions.cs +++ b/src/Foundatio/Nito.AsyncEx.Tasks/TaskExtensions.cs @@ -5,7 +5,8 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.AsyncEx { +namespace Foundatio.AsyncEx +{ /// /// Provides extension methods for the and types. /// @@ -34,4 +35,4 @@ private static async Task DoWaitAsync(Task task, CancellationToken cancellationT await await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Nito.Collections.Deque/Deque.cs b/src/Foundatio/Nito.Collections.Deque/Deque.cs index 7fd804125..76b86ab6d 100644 --- a/src/Foundatio/Nito.Collections.Deque/Deque.cs +++ b/src/Foundatio/Nito.Collections.Deque/Deque.cs @@ -503,7 +503,7 @@ public int Capacity /// /// The number of elements contained in this deque. public int Count { get; private set; } - + /// /// Applies the offset to , resulting in a buffer index. /// diff --git a/src/Foundatio/Queues/DuplicateDetectionQueueBehavior.cs b/src/Foundatio/Queues/DuplicateDetectionQueueBehavior.cs index f90be0e5d..5383d7acb 100644 --- a/src/Foundatio/Queues/DuplicateDetectionQueueBehavior.cs +++ b/src/Foundatio/Queues/DuplicateDetectionQueueBehavior.cs @@ -3,46 +3,54 @@ using Foundatio.Caching; using Microsoft.Extensions.Logging; -namespace Foundatio.Queues { - public class DuplicateDetectionQueueBehavior : QueueBehaviorBase where T : class { +namespace Foundatio.Queues +{ + public class DuplicateDetectionQueueBehavior : QueueBehaviorBase where T : class + { private readonly ICacheClient _cacheClient; private readonly ILoggerFactory _loggerFactory; private readonly TimeSpan _detectionWindow; - public DuplicateDetectionQueueBehavior(ICacheClient cacheClient, ILoggerFactory loggerFactory, TimeSpan? detectionWindow = null) { + public DuplicateDetectionQueueBehavior(ICacheClient cacheClient, ILoggerFactory loggerFactory, TimeSpan? detectionWindow = null) + { _cacheClient = cacheClient; _loggerFactory = loggerFactory; _detectionWindow = detectionWindow ?? TimeSpan.FromMinutes(10); } - - protected override async Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) { + + protected override async Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) + { string uniqueIdentifier = GetUniqueIdentifier(enqueuingEventArgs.Data); if (String.IsNullOrEmpty(uniqueIdentifier)) return; - + bool success = await _cacheClient.AddAsync(uniqueIdentifier, true, _detectionWindow); - if (!success) { + if (!success) + { var logger = _loggerFactory.CreateLogger(); logger.LogInformation("Discarding queue entry due to duplicate {UniqueIdentifier}", uniqueIdentifier); enqueuingEventArgs.Cancel = true; } } - protected override async Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) { + protected override async Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) + { string uniqueIdentifier = GetUniqueIdentifier(dequeuedEventArgs.Entry.Value); if (String.IsNullOrEmpty(uniqueIdentifier)) return; - + await _cacheClient.RemoveAsync(uniqueIdentifier); } - private string GetUniqueIdentifier(T data) { + private string GetUniqueIdentifier(T data) + { var haveUniqueIdentifier = data as IHaveUniqueIdentifier; return haveUniqueIdentifier?.UniqueIdentifier; } } - public interface IHaveUniqueIdentifier { + public interface IHaveUniqueIdentifier + { string UniqueIdentifier { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/IQueue.cs b/src/Foundatio/Queues/IQueue.cs index 2403eeb71..52785c1fc 100644 --- a/src/Foundatio/Queues/IQueue.cs +++ b/src/Foundatio/Queues/IQueue.cs @@ -7,8 +7,10 @@ using Foundatio.Serializer; using Foundatio.Utility; -namespace Foundatio.Queues { - public interface IQueue : IQueue where T : class { +namespace Foundatio.Queues +{ + public interface IQueue : IQueue where T : class + { AsyncEvent> Enqueuing { get; } AsyncEvent> Enqueued { get; } AsyncEvent> Dequeued { get; } @@ -40,19 +42,22 @@ public interface IQueue : IQueue where T : class { Task StartWorkingAsync(Func, CancellationToken, Task> handler, bool autoComplete = false, CancellationToken cancellationToken = default); } - public interface IQueue : IHaveSerializer, IDisposable { + public interface IQueue : IHaveSerializer, IDisposable + { Task GetQueueStatsAsync(); Task DeleteQueueAsync(); string QueueId { get; } } - public static class QueueExtensions { + public static class QueueExtensions + { public static Task StartWorkingAsync(this IQueue queue, Func, Task> handler, bool autoComplete = false, CancellationToken cancellationToken = default) where T : class => queue.StartWorkingAsync((entry, token) => handler(entry), autoComplete, cancellationToken); } [DebuggerDisplay("Queued={Queued}, Working={Working}, Deadletter={Deadletter}, Enqueued={Enqueued}, Dequeued={Dequeued}, Completed={Completed}, Abandoned={Abandoned}, Errors={Errors}, Timeouts={Timeouts}")] - public class QueueStats { + public class QueueStats + { public long Queued { get; set; } public long Working { get; set; } public long Deadletter { get; set; } @@ -63,42 +68,49 @@ public class QueueStats { public long Errors { get; set; } public long Timeouts { get; set; } } - - public class QueueEntryOptions { + + public class QueueEntryOptions + { public string UniqueId { get; set; } public string CorrelationId { get; set; } public TimeSpan? DeliveryDelay { get; set; } public IDictionary Properties { get; set; } = new Dictionary(); } - public class EnqueuingEventArgs : CancelEventArgs where T : class { + public class EnqueuingEventArgs : CancelEventArgs where T : class + { public IQueue Queue { get; set; } public T Data { get; set; } public QueueEntryOptions Options { get; set; } } - public class EnqueuedEventArgs : EventArgs where T : class { + public class EnqueuedEventArgs : EventArgs where T : class + { public IQueue Queue { get; set; } public IQueueEntry Entry { get; set; } } - public class DequeuedEventArgs : EventArgs where T : class { + public class DequeuedEventArgs : EventArgs where T : class + { public IQueue Queue { get; set; } public IQueueEntry Entry { get; set; } } - public class LockRenewedEventArgs : EventArgs where T : class { + public class LockRenewedEventArgs : EventArgs where T : class + { public IQueue Queue { get; set; } public IQueueEntry Entry { get; set; } } - public class CompletedEventArgs : EventArgs where T : class { + public class CompletedEventArgs : EventArgs where T : class + { public IQueue Queue { get; set; } public IQueueEntry Entry { get; set; } } - public class AbandonedEventArgs : EventArgs where T : class { + public class AbandonedEventArgs : EventArgs where T : class + { public IQueue Queue { get; set; } public IQueueEntry Entry { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/IQueueActivity.cs b/src/Foundatio/Queues/IQueueActivity.cs index a5d2638c1..9effe8ce3 100644 --- a/src/Foundatio/Queues/IQueueActivity.cs +++ b/src/Foundatio/Queues/IQueueActivity.cs @@ -1,8 +1,10 @@ using System; -namespace Foundatio.Queues { - public interface IQueueActivity { +namespace Foundatio.Queues +{ + public interface IQueueActivity + { DateTime? LastEnqueueActivity { get; } DateTime? LastDequeueActivity { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/IQueueEntry.cs b/src/Foundatio/Queues/IQueueEntry.cs index a159cbe08..2fdfe423d 100644 --- a/src/Foundatio/Queues/IQueueEntry.cs +++ b/src/Foundatio/Queues/IQueueEntry.cs @@ -3,8 +3,10 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Queues { - public interface IQueueEntry { +namespace Foundatio.Queues +{ + public interface IQueueEntry + { string Id { get; } string CorrelationId { get; } IDictionary Properties { get; } @@ -20,8 +22,9 @@ public interface IQueueEntry { Task CompleteAsync(); ValueTask DisposeAsync(); } - - public interface IQueueEntry : IQueueEntry where T : class { + + public interface IQueueEntry : IQueueEntry where T : class + { T Value { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/InMemoryQueue.cs b/src/Foundatio/Queues/InMemoryQueue.cs index 049ab75a4..133b131f3 100644 --- a/src/Foundatio/Queues/InMemoryQueue.cs +++ b/src/Foundatio/Queues/InMemoryQueue.cs @@ -6,12 +6,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; using Foundatio.AsyncEx; +using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Queues { - public class InMemoryQueue : QueueBase> where T : class { +namespace Foundatio.Queues +{ + public class InMemoryQueue : QueueBase> where T : class + { private readonly ConcurrentQueue> _queue = new(); private readonly ConcurrentDictionary> _dequeued = new(); private readonly ConcurrentQueue> _deadletterQueue = new(); @@ -25,25 +27,30 @@ public class InMemoryQueue : QueueBase> where T : private int _workerErrorCount; private int _workerItemTimeoutCount; - public InMemoryQueue() : this(o => o) {} + public InMemoryQueue() : this(o => o) { } - public InMemoryQueue(InMemoryQueueOptions options) : base(options) { + public InMemoryQueue(InMemoryQueueOptions options) : base(options) + { InitializeMaintenance(); } public InMemoryQueue(Builder, InMemoryQueueOptions> config) : this(config(new InMemoryQueueOptionsBuilder()).Build()) { } - protected override Task EnsureQueueCreatedAsync(CancellationToken cancellationToken = default) { + protected override Task EnsureQueueCreatedAsync(CancellationToken cancellationToken = default) + { return Task.CompletedTask; } - protected override Task GetQueueStatsImplAsync() { + protected override Task GetQueueStatsImplAsync() + { return Task.FromResult(GetMetricsQueueStats()); } - protected override QueueStats GetMetricsQueueStats() { - return new QueueStats { + protected override QueueStats GetMetricsQueueStats() + { + return new QueueStats + { Queued = _queue.Count, Working = _dequeued.Count, Deadletter = _deadletterQueue.Count, @@ -56,23 +63,28 @@ protected override QueueStats GetMetricsQueueStats() { }; } - public IReadOnlyCollection> GetEntries() { + public IReadOnlyCollection> GetEntries() + { return new ReadOnlyCollection>(_queue.ToList()); } - public IReadOnlyCollection> GetDequeuedEntries() { + public IReadOnlyCollection> GetDequeuedEntries() + { return new ReadOnlyCollection>(_dequeued.Values.ToList()); } - public IReadOnlyCollection> GetCompletedEntries() { + public IReadOnlyCollection> GetCompletedEntries() + { return new ReadOnlyCollection>(_completedQueue.ToList()); } - public IReadOnlyCollection> GetDeadletterEntries() { + public IReadOnlyCollection> GetDeadletterEntries() + { return new ReadOnlyCollection>(_deadletterQueue.ToList()); } - protected override async Task EnqueueImplAsync(T data, QueueEntryOptions options) { + protected override async Task EnqueueImplAsync(T data, QueueEntryOptions options) + { string id = Guid.NewGuid().ToString("N"); _logger.LogTrace("Queue {Name} enqueue item: {Id}", _options.Name, id); @@ -84,8 +96,10 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions Interlocked.Increment(ref _enqueuedCount); - if (options?.DeliveryDelay != null && options.DeliveryDelay.Value > TimeSpan.Zero) { - _ = Run.DelayedAsync(options.DeliveryDelay.Value, async () => { + if (options?.DeliveryDelay != null && options.DeliveryDelay.Value > TimeSpan.Zero) + { + _ = Run.DelayedAsync(options.DeliveryDelay.Value, async () => + { _queue.Enqueue(entry); _logger.LogTrace("Enqueue: Set Event"); @@ -96,7 +110,7 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions }, _queueDisposedCancellationTokenSource.Token); return id; } - + _queue.Enqueue(entry); _logger.LogTrace("Enqueue: Set Event"); @@ -110,38 +124,51 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions private readonly List _workers = new(); - protected override void StartWorkingImpl(Func, CancellationToken, Task> handler, bool autoComplete, CancellationToken cancellationToken) { + protected override void StartWorkingImpl(Func, CancellationToken, Task> handler, bool autoComplete, CancellationToken cancellationToken) + { if (handler == null) throw new ArgumentNullException(nameof(handler)); _logger.LogTrace("Queue {Name} start working", _options.Name); - _workers.Add(Task.Run(async () => { + _workers.Add(Task.Run(async () => + { using var linkedCancellationToken = GetLinkedDisposableCancellationTokenSource(cancellationToken); _logger.LogTrace("WorkerLoop Start {Name}", _options.Name); - while (!linkedCancellationToken.IsCancellationRequested) { + while (!linkedCancellationToken.IsCancellationRequested) + { _logger.LogTrace("WorkerLoop Signaled {Name}", _options.Name); IQueueEntry queueEntry = null; - try { + try + { queueEntry = await DequeueImplAsync(linkedCancellationToken.Token).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error on Dequeue: {Message}", ex.Message); } if (linkedCancellationToken.IsCancellationRequested || queueEntry == null) return; - try { + try + { await handler(queueEntry, linkedCancellationToken.Token).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Worker error: {Message}", ex.Message); - if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) { - try { + if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { + try + { await Run.WithRetriesAsync(() => queueEntry.AbandonAsync(), 3, TimeSpan.Zero, cancellationToken).AnyContext(); - } catch (Exception abandonEx) { + } + catch (Exception abandonEx) + { _logger.LogError(abandonEx, "Worker error abandoning queue entry: {Message}", abandonEx.Message); } } @@ -149,10 +176,14 @@ protected override void StartWorkingImpl(Func, CancellationToken, Interlocked.Increment(ref _workerErrorCount); } - if (autoComplete && !queueEntry.IsAbandoned && !queueEntry.IsCompleted) { - try { + if (autoComplete && !queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { + try + { await Run.WithRetriesAsync(() => queueEntry.CompleteAsync(), cancellationToken: linkedCancellationToken.Token, logger: _logger).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Worker error attempting to auto complete entry: {Message}", ex.Message); } } @@ -162,18 +193,22 @@ protected override void StartWorkingImpl(Func, CancellationToken, }, GetLinkedDisposableCancellationTokenSource(cancellationToken).Token)); } - protected override async Task> DequeueImplAsync(CancellationToken linkedCancellationToken) { + protected override async Task> DequeueImplAsync(CancellationToken linkedCancellationToken) + { _logger.LogTrace("Queue {Name} dequeuing item... Queue count: {Count}", _options.Name, _queue.Count); - while (_queue.Count == 0 && !linkedCancellationToken.IsCancellationRequested) { + while (_queue.Count == 0 && !linkedCancellationToken.IsCancellationRequested) + { _logger.LogTrace("Waiting to dequeue item..."); var sw = Stopwatch.StartNew(); - try { + try + { using var timeoutCancellationTokenSource = new CancellationTokenSource(10000); using var dequeueCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(linkedCancellationToken, timeoutCancellationTokenSource.Token); await _autoResetEvent.WaitAsync(dequeueCancellationTokenSource.Token).AnyContext(); - } catch (OperationCanceledException) { } + } + catch (OperationCanceledException) { } sw.Stop(); _logger.LogTrace("Waited for dequeue: {Elapsed:g}", sw.Elapsed); @@ -194,7 +229,7 @@ protected override async Task> DequeueImplAsync(CancellationToken Interlocked.Increment(ref _dequeuedCount); _logger.LogTrace("Dequeue: Got Item"); - + await entry.RenewLockAsync(); await OnDequeuedAsync(entry).AnyContext(); ScheduleNextMaintenance(SystemClock.UtcNow.Add(_options.WorkItemTimeout)); @@ -202,8 +237,9 @@ protected override async Task> DequeueImplAsync(CancellationToken return entry; } - public override async Task RenewLockAsync(IQueueEntry entry) { - _logger.LogDebug("Queue {Name} renew lock item: {Id}", _options.Name, entry.Id); + public override async Task RenewLockAsync(IQueueEntry entry) + { + _logger.LogDebug("Queue {Name} renew lock item: {Id}", _options.Name, entry.Id); if (!_dequeued.TryGetValue(entry.Id, out var targetEntry)) return; @@ -214,7 +250,8 @@ public override async Task RenewLockAsync(IQueueEntry entry) { _logger.LogTrace("Renew lock done: {Id}", entry.Id); } - public override async Task CompleteAsync(IQueueEntry entry) { + public override async Task CompleteAsync(IQueueEntry entry) + { _logger.LogDebug("Queue {Name} complete item: {Id}", _options.Name, entry.Id); if (entry.IsAbandoned || entry.IsCompleted) throw new InvalidOperationException("Queue entry has already been completed or abandoned"); @@ -222,7 +259,8 @@ public override async Task CompleteAsync(IQueueEntry entry) { if (!_dequeued.TryRemove(entry.Id, out var info) || info == null) throw new Exception("Unable to remove item from the dequeued list"); - if (_options.CompletedEntryRetentionLimit > 0) { + if (_options.CompletedEntryRetentionLimit > 0) + { _completedQueue.Enqueue(info); while (_completedQueue.Count > _options.CompletedEntryRetentionLimit) _completedQueue.TryDequeue(out _); @@ -234,18 +272,22 @@ public override async Task CompleteAsync(IQueueEntry entry) { _logger.LogTrace("Complete done: {Id}", entry.Id); } - public override async Task AbandonAsync(IQueueEntry entry) { + public override async Task AbandonAsync(IQueueEntry entry) + { _logger.LogDebug("Queue {Name}:{QueueId} abandon item: {Id}", _options.Name, QueueId, entry.Id); if (entry.IsAbandoned || entry.IsCompleted) throw new InvalidOperationException("Queue entry has already been completed or abandoned"); - if (!_dequeued.TryRemove(entry.Id, out var targetEntry) || targetEntry == null) { - foreach (var kvp in _queue) { + if (!_dequeued.TryRemove(entry.Id, out var targetEntry) || targetEntry == null) + { + foreach (var kvp in _queue) + { if (kvp.Id == entry.Id) throw new Exception("Unable to remove item from the dequeued list (item is in queue)"); } - foreach (var kvp in _deadletterQueue) { + foreach (var kvp in _deadletterQueue) + { if (kvp.Id == entry.Id) throw new Exception("Unable to remove item from the dequeued list (item is in dead letter)"); } @@ -257,25 +299,35 @@ public override async Task AbandonAsync(IQueueEntry entry) { Interlocked.Increment(ref _abandonedCount); _logger.LogTrace("Abandon complete: {Id}", entry.Id); - try { + try + { await OnAbandonedAsync(entry).AnyContext(); - } finally { - if (targetEntry.Attempts < _options.Retries + 1) { - if (_options.RetryDelay > TimeSpan.Zero) { + } + finally + { + if (targetEntry.Attempts < _options.Retries + 1) + { + if (_options.RetryDelay > TimeSpan.Zero) + { _logger.LogTrace("Adding item to wait list for future retry: {Id}", entry.Id); var unawaited = Run.DelayedAsync(GetRetryDelay(targetEntry.Attempts), () => RetryAsync(targetEntry), _queueDisposedCancellationTokenSource.Token); - } else { + } + else + { _logger.LogTrace("Adding item back to queue for retry: {Id}", entry.Id); _ = Task.Run(() => RetryAsync(targetEntry)); } - } else { + } + else + { _logger.LogTrace("Exceeded retry limit moving to deadletter: {Id}", entry.Id); _deadletterQueue.Enqueue(targetEntry); } } } - private Task RetryAsync(QueueEntry entry) { + private Task RetryAsync(QueueEntry entry) + { _logger.LogTrace("Queue {Name} retrying item: {Id} Attempts: {Attempts}", _options.Name, entry.Id, entry.Attempts); entry.Reset(); @@ -284,17 +336,20 @@ private Task RetryAsync(QueueEntry entry) { return Task.CompletedTask; } - private TimeSpan GetRetryDelay(int attempts) { + private TimeSpan GetRetryDelay(int attempts) + { int maxMultiplier = _options.RetryMultipliers.Length > 0 ? _options.RetryMultipliers.Last() : 1; int multiplier = attempts <= _options.RetryMultipliers.Length ? _options.RetryMultipliers[attempts - 1] : maxMultiplier; return TimeSpan.FromMilliseconds((int)(_options.RetryDelay.TotalMilliseconds * multiplier)); } - protected override Task> GetDeadletterItemsImplAsync(CancellationToken cancellationToken) { + protected override Task> GetDeadletterItemsImplAsync(CancellationToken cancellationToken) + { return Task.FromResult(_deadletterQueue.Select(i => i.Value)); } - public override Task DeleteQueueAsync() { + public override Task DeleteQueueAsync() + { _logger.LogTrace("Deleting queue: {Name}", _options.Name); _queue.Clear(); @@ -309,22 +364,29 @@ public override Task DeleteQueueAsync() { return Task.CompletedTask; } - protected override async Task DoMaintenanceAsync() { + protected override async Task DoMaintenanceAsync() + { var utcNow = SystemClock.UtcNow; var minAbandonAt = DateTime.MaxValue; - try { - foreach (var entry in _dequeued.Values.ToList()) { + try + { + foreach (var entry in _dequeued.Values.ToList()) + { var abandonAt = entry.RenewedTimeUtc.Add(_options.WorkItemTimeout); - if (abandonAt < utcNow) { + if (abandonAt < utcNow) + { _logger.LogInformation("DoMaintenance Abandon: {Id}", entry.Id); await AbandonAsync(entry).AnyContext(); Interlocked.Increment(ref _workerItemTimeoutCount); - } else if (abandonAt < minAbandonAt) + } + else if (abandonAt < minAbandonAt) minAbandonAt = abandonAt; } - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "DoMaintenance Error: {Message}", ex.Message); } @@ -332,14 +394,16 @@ public override Task DeleteQueueAsync() { return minAbandonAt; } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _queue.Clear(); _deadletterQueue.Clear(); _dequeued.Clear(); _logger.LogTrace("Got {WorkerCount} workers to cleanup", _workers.Count); - foreach (var worker in _workers) { + foreach (var worker in _workers) + { if (worker.IsCompleted) continue; @@ -349,4 +413,4 @@ public override void Dispose() { } } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/InMemoryQueueOptions.cs b/src/Foundatio/Queues/InMemoryQueueOptions.cs index 225033b54..1f95d368c 100644 --- a/src/Foundatio/Queues/InMemoryQueueOptions.cs +++ b/src/Foundatio/Queues/InMemoryQueueOptions.cs @@ -1,25 +1,30 @@ using System; -namespace Foundatio.Queues { - public class InMemoryQueueOptions : SharedQueueOptions where T : class { +namespace Foundatio.Queues +{ + public class InMemoryQueueOptions : SharedQueueOptions where T : class + { public TimeSpan RetryDelay { get; set; } = TimeSpan.FromMinutes(1); public int CompletedEntryRetentionLimit { get; set; } = 100; public int[] RetryMultipliers { get; set; } = { 1, 3, 5, 10 }; } - public class InMemoryQueueOptionsBuilder : SharedQueueOptionsBuilder, InMemoryQueueOptionsBuilder> where T: class { - public InMemoryQueueOptionsBuilder RetryDelay(TimeSpan retryDelay) { + public class InMemoryQueueOptionsBuilder : SharedQueueOptionsBuilder, InMemoryQueueOptionsBuilder> where T : class + { + public InMemoryQueueOptionsBuilder RetryDelay(TimeSpan retryDelay) + { if (retryDelay == null) throw new ArgumentNullException(nameof(retryDelay)); - + if (retryDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(retryDelay)); - + Target.RetryDelay = retryDelay; return this; } - public InMemoryQueueOptionsBuilder CompletedEntryRetentionLimit(int retentionCount) { + public InMemoryQueueOptionsBuilder CompletedEntryRetentionLimit(int retentionCount) + { if (retentionCount < 0) throw new ArgumentOutOfRangeException(nameof(retentionCount)); @@ -27,17 +32,19 @@ public InMemoryQueueOptionsBuilder CompletedEntryRetentionLimit(int retention return this; } - public InMemoryQueueOptionsBuilder RetryMultipliers(int[] multipliers) { + public InMemoryQueueOptionsBuilder RetryMultipliers(int[] multipliers) + { if (multipliers == null) throw new ArgumentNullException(nameof(multipliers)); - - foreach (int multiplier in multipliers) { + + foreach (int multiplier in multipliers) + { if (multiplier < 1) throw new ArgumentOutOfRangeException(nameof(multipliers)); } - + Target.RetryMultipliers = multipliers; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/MetricsQueueBehavior.cs b/src/Foundatio/Queues/MetricsQueueBehavior.cs index 5cf846c00..e4e14fd44 100644 --- a/src/Foundatio/Queues/MetricsQueueBehavior.cs +++ b/src/Foundatio/Queues/MetricsQueueBehavior.cs @@ -5,16 +5,19 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Queues { +namespace Foundatio.Queues +{ [Obsolete("MetricsQueueBehavior is no longer needed. Metrics are built into queues.")] - public class MetricsQueueBehavior : QueueBehaviorBase where T : class { + public class MetricsQueueBehavior : QueueBehaviorBase where T : class + { private readonly string _metricsPrefix; private readonly IMetricsClient _metricsClient; private readonly ILogger _logger; private readonly ScheduledTimer _timer; private readonly TimeSpan _reportInterval; - public MetricsQueueBehavior(IMetricsClient metrics, string metricsPrefix = null, TimeSpan? reportCountsInterval = null, ILoggerFactory loggerFactory = null) { + public MetricsQueueBehavior(IMetricsClient metrics, string metricsPrefix = null, TimeSpan? reportCountsInterval = null, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger>() ?? NullLogger>.Instance; _metricsClient = metrics ?? NullMetricsClient.Instance; @@ -30,22 +33,27 @@ public MetricsQueueBehavior(IMetricsClient metrics, string metricsPrefix = null, _timer = new ScheduledTimer(ReportQueueCountAsync, loggerFactory: loggerFactory); } - private async Task ReportQueueCountAsync() { - try { + private async Task ReportQueueCountAsync() + { + try + { var stats = await _queue.GetQueueStatsAsync().AnyContext(); _logger.LogTrace("Reporting queue count"); _metricsClient.Gauge(GetFullMetricName("count"), stats.Queued); _metricsClient.Gauge(GetFullMetricName("working"), stats.Working); _metricsClient.Gauge(GetFullMetricName("deadletter"), stats.Deadletter); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error reporting queue metrics"); } return null; } - protected override Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) { + protected override Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) + { _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); string subMetricName = GetSubMetricName(enqueuedEventArgs.Entry.Value); @@ -56,7 +64,8 @@ protected override Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedE return Task.CompletedTask; } - protected override Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) { + protected override Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) + { _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); var metadata = dequeuedEventArgs.Entry as IQueueEntryMetadata; @@ -80,7 +89,8 @@ protected override Task OnDequeued(object sender, DequeuedEventArgs dequeuedE return Task.CompletedTask; } - protected override Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) { + protected override Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) + { _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); if (!(completedEventArgs.Entry is IQueueEntryMetadata metadata)) @@ -100,11 +110,12 @@ protected override Task OnCompleted(object sender, CompletedEventArgs complet if (!String.IsNullOrEmpty(subMetricName)) _metricsClient.Timer(GetFullMetricName(subMetricName, "totaltime"), totalTime); _metricsClient.Timer(GetFullMetricName("totaltime"), totalTime); - + return Task.CompletedTask; } - protected override Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) { + protected override Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) + { _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); if (!(abandonedEventArgs.Entry is IQueueEntryMetadata metadata)) @@ -122,22 +133,26 @@ protected override Task OnAbandoned(object sender, AbandonedEventArgs abandon return Task.CompletedTask; } - protected string GetSubMetricName(T data) { + protected string GetSubMetricName(T data) + { var haveStatName = data as IHaveSubMetricName; return haveStatName?.SubMetricName; } - protected string GetFullMetricName(string name) { + protected string GetFullMetricName(string name) + { return String.Concat(_metricsPrefix, ".", name); } - protected string GetFullMetricName(string customMetricName, string name) { + protected string GetFullMetricName(string customMetricName, string name) + { return String.IsNullOrEmpty(customMetricName) ? GetFullMetricName(name) : String.Concat(_metricsPrefix, ".", customMetricName.ToLower(), ".", name); } - public override void Dispose() { + public override void Dispose() + { _timer?.Dispose(); base.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/QueueBase.cs b/src/Foundatio/Queues/QueueBase.cs index 13a1a1638..80e22eaa8 100644 --- a/src/Foundatio/Queues/QueueBase.cs +++ b/src/Foundatio/Queues/QueueBase.cs @@ -11,8 +11,10 @@ using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.Queues { - public abstract class QueueBase : MaintenanceBase, IQueue, IQueueActivity where T : class where TOptions : SharedQueueOptions { +namespace Foundatio.Queues +{ + public abstract class QueueBase : MaintenanceBase, IQueue, IQueueActivity where T : class where TOptions : SharedQueueOptions + { protected readonly TOptions _options; private readonly string _metricsPrefix; protected readonly ISerializer _serializer; @@ -35,7 +37,8 @@ public abstract class QueueBase : MaintenanceBase, IQueue, IQueu protected readonly CancellationTokenSource _queueDisposedCancellationTokenSource; private bool _isDisposed; - protected QueueBase(TOptions options) : base(options?.LoggerFactory) { + protected QueueBase(TOptions options) : base(options?.LoggerFactory) + { _options = options ?? throw new ArgumentNullException(nameof(options)); _metricsPrefix = $"foundatio.{typeof(T).Name.ToLowerInvariant()}"; @@ -55,11 +58,15 @@ protected QueueBase(TOptions options) : base(options?.LoggerFactory) { _totalTimeHistogram = FoundatioDiagnostics.Meter.CreateHistogram(GetFullMetricName("totaltime"), description: "Total time in queue", unit: "ms"); _abandonedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("abandoned"), description: "Number of abandoned items"); - var queueMetricValues = new InstrumentsValues(() => { - try { + var queueMetricValues = new InstrumentsValues(() => + { + try + { var stats = GetMetricsQueueStats(); return (stats.Queued, stats.Working, stats.Deadletter); - } catch { + } + catch + { return (0, 0, 0); } }); @@ -74,7 +81,8 @@ protected QueueBase(TOptions options) : base(options?.LoggerFactory) { public DateTime? LastDequeueActivity { get; protected set; } ISerializer IHaveSerializer.Serializer => _serializer; - public void AttachBehavior(IQueueBehavior behavior) { + public void AttachBehavior(IQueueBehavior behavior) + { if (behavior == null) return; @@ -85,17 +93,19 @@ public void AttachBehavior(IQueueBehavior behavior) { protected abstract Task EnsureQueueCreatedAsync(CancellationToken cancellationToken = default); protected abstract Task EnqueueImplAsync(T data, QueueEntryOptions options); - public async Task EnqueueAsync(T data, QueueEntryOptions options = null) { + public async Task EnqueueAsync(T data, QueueEntryOptions options = null) + { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); LastEnqueueActivity = SystemClock.UtcNow; options ??= new QueueEntryOptions(); - + return await EnqueueImplAsync(data, options).AnyContext(); } protected abstract Task> DequeueImplAsync(CancellationToken linkedCancellationToken); - public async Task> DequeueAsync(CancellationToken cancellationToken) { + public async Task> DequeueAsync(CancellationToken cancellationToken) + { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); LastDequeueActivity = SystemClock.UtcNow; @@ -103,7 +113,8 @@ public async Task> DequeueAsync(CancellationToken cancellationTok return await DequeueImplAsync(linkedCancellationToken.Token).AnyContext(); } - public virtual async Task> DequeueAsync(TimeSpan? timeout = null) { + public virtual async Task> DequeueAsync(TimeSpan? timeout = null) + { using var timeoutCancellationTokenSource = timeout.ToCancellationTokenSource(TimeSpan.FromSeconds(30)); return await DequeueAsync(timeoutCancellationTokenSource.Token).AnyContext(); } @@ -115,25 +126,29 @@ public virtual async Task> DequeueAsync(TimeSpan? timeout = null) public abstract Task AbandonAsync(IQueueEntry queueEntry); protected abstract Task> GetDeadletterItemsImplAsync(CancellationToken cancellationToken); - public async Task> GetDeadletterItemsAsync(CancellationToken cancellationToken = default) { + public async Task> GetDeadletterItemsAsync(CancellationToken cancellationToken = default) + { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); return await GetDeadletterItemsImplAsync(cancellationToken).AnyContext(); } protected abstract Task GetQueueStatsImplAsync(); - public Task GetQueueStatsAsync() { + public Task GetQueueStatsAsync() + { return GetQueueStatsImplAsync(); } - protected virtual QueueStats GetMetricsQueueStats() { + protected virtual QueueStats GetMetricsQueueStats() + { return GetQueueStatsAsync().GetAwaiter().GetResult(); } public abstract Task DeleteQueueAsync(); protected abstract void StartWorkingImpl(Func, CancellationToken, Task> handler, bool autoComplete, CancellationToken cancellationToken); - public async Task StartWorkingAsync(Func, CancellationToken, Task> handler, bool autoComplete = false, CancellationToken cancellationToken = default) { + public async Task StartWorkingAsync(Func, CancellationToken, Task> handler, bool autoComplete = false, CancellationToken cancellationToken = default) + { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); StartWorkingImpl(handler, autoComplete, cancellationToken); } @@ -142,8 +157,10 @@ public async Task StartWorkingAsync(Func, CancellationToken, Task public AsyncEvent> Enqueuing { get; } = new AsyncEvent>(); - protected virtual async Task OnEnqueuingAsync(T data, QueueEntryOptions options) { - if (String.IsNullOrEmpty(options.CorrelationId)) { + protected virtual async Task OnEnqueuingAsync(T data, QueueEntryOptions options) + { + if (String.IsNullOrEmpty(options.CorrelationId)) + { options.CorrelationId = Activity.Current?.Id; if (!String.IsNullOrEmpty(Activity.Current?.TraceStateString)) options.Properties.Add("TraceState", Activity.Current.TraceStateString); @@ -161,7 +178,8 @@ protected virtual async Task OnEnqueuingAsync(T data, QueueEntryOptions op public AsyncEvent> Enqueued { get; } = new AsyncEvent>(true); - protected virtual Task OnEnqueuedAsync(IQueueEntry entry) { + protected virtual Task OnEnqueuedAsync(IQueueEntry entry) + { LastEnqueueActivity = SystemClock.UtcNow; var tags = GetQueueEntryTags(entry); @@ -178,7 +196,8 @@ protected virtual Task OnEnqueuedAsync(IQueueEntry entry) { public AsyncEvent> Dequeued { get; } = new AsyncEvent>(true); - protected virtual Task OnDequeuedAsync(IQueueEntry entry) { + protected virtual Task OnDequeuedAsync(IQueueEntry entry) + { LastDequeueActivity = SystemClock.UtcNow; var tags = GetQueueEntryTags(entry); @@ -186,7 +205,8 @@ protected virtual Task OnDequeuedAsync(IQueueEntry entry) { IncrementSubCounter(entry.Value, "dequeued", tags); var metadata = entry as IQueueEntryMetadata; - if (metadata != null && (metadata.EnqueuedTimeUtc != DateTime.MinValue || metadata.DequeuedTimeUtc != DateTime.MinValue)) { + if (metadata != null && (metadata.EnqueuedTimeUtc != DateTime.MinValue || metadata.DequeuedTimeUtc != DateTime.MinValue)) + { var start = metadata.EnqueuedTimeUtc; var end = metadata.DequeuedTimeUtc; int time = (int)(end - start).TotalMilliseconds; @@ -203,15 +223,17 @@ protected virtual Task OnDequeuedAsync(IQueueEntry entry) { return dequeued.InvokeAsync(this, args); } - protected virtual TagList GetQueueEntryTags(IQueueEntry entry) { + protected virtual TagList GetQueueEntryTags(IQueueEntry entry) + { return _emptyTags; } public AsyncEvent> LockRenewed { get; } = new AsyncEvent>(true); - protected virtual Task OnLockRenewedAsync(IQueueEntry entry) { + protected virtual Task OnLockRenewedAsync(IQueueEntry entry) + { LastDequeueActivity = SystemClock.UtcNow; - + var lockRenewed = LockRenewed; if (lockRenewed == null) return Task.CompletedTask; @@ -222,7 +244,8 @@ protected virtual Task OnLockRenewedAsync(IQueueEntry entry) { public AsyncEvent> Completed { get; } = new AsyncEvent>(true); - protected virtual async Task OnCompletedAsync(IQueueEntry entry) { + protected virtual async Task OnCompletedAsync(IQueueEntry entry) + { var now = SystemClock.UtcNow; LastDequeueActivity = now; @@ -230,21 +253,25 @@ protected virtual async Task OnCompletedAsync(IQueueEntry entry) { _completedCounter.Add(1, tags); IncrementSubCounter(entry.Value, "completed", tags); - if (entry is QueueEntry metadata) { - if (metadata.EnqueuedTimeUtc > DateTime.MinValue) { + if (entry is QueueEntry metadata) + { + if (metadata.EnqueuedTimeUtc > DateTime.MinValue) + { metadata.TotalTime = now.Subtract(metadata.EnqueuedTimeUtc); _totalTimeHistogram.Record((int)metadata.TotalTime.TotalMilliseconds, tags); RecordSubHistogram(entry.Value, "totaltime", (int)metadata.TotalTime.TotalMilliseconds, tags); } - if (metadata.DequeuedTimeUtc > DateTime.MinValue) { + if (metadata.DequeuedTimeUtc > DateTime.MinValue) + { metadata.ProcessingTime = now.Subtract(metadata.DequeuedTimeUtc); _processTimeHistogram.Record((int)metadata.ProcessingTime.TotalMilliseconds, tags); RecordSubHistogram(entry.Value, "processtime", (int)metadata.ProcessingTime.TotalMilliseconds, tags); } } - if (Completed != null) { + if (Completed != null) + { var args = new CompletedEventArgs { Queue = this, Entry = entry }; await Completed.InvokeAsync(this, args).AnyContext(); } @@ -252,32 +279,37 @@ protected virtual async Task OnCompletedAsync(IQueueEntry entry) { public AsyncEvent> Abandoned { get; } = new AsyncEvent>(true); - protected virtual async Task OnAbandonedAsync(IQueueEntry entry) { + protected virtual async Task OnAbandonedAsync(IQueueEntry entry) + { LastDequeueActivity = SystemClock.UtcNow; var tags = GetQueueEntryTags(entry); _abandonedCounter.Add(1, tags); IncrementSubCounter(entry.Value, "abandoned", tags); - if (entry is QueueEntry metadata && metadata.DequeuedTimeUtc > DateTime.MinValue) { + if (entry is QueueEntry metadata && metadata.DequeuedTimeUtc > DateTime.MinValue) + { metadata.ProcessingTime = SystemClock.UtcNow.Subtract(metadata.DequeuedTimeUtc); _processTimeHistogram.Record((int)metadata.ProcessingTime.TotalMilliseconds, tags); RecordSubHistogram(entry.Value, "processtime", (int)metadata.ProcessingTime.TotalMilliseconds, tags); } - if (Abandoned != null) { + if (Abandoned != null) + { var args = new AbandonedEventArgs { Queue = this, Entry = entry }; await Abandoned.InvokeAsync(this, args).AnyContext(); } } - protected string GetSubMetricName(T data) { + protected string GetSubMetricName(T data) + { var haveStatName = data as IHaveSubMetricName; return haveStatName?.SubMetricName; } protected readonly ConcurrentDictionary> _counters = new(); - private void IncrementSubCounter(T data, string name, in TagList tags) { + private void IncrementSubCounter(T data, string name, in TagList tags) + { if (data is not IHaveSubMetricName) return; @@ -290,7 +322,8 @@ private void IncrementSubCounter(T data, string name, in TagList tags) { } protected readonly ConcurrentDictionary> _histograms = new(); - private void RecordSubHistogram(T data, string name, int value, in TagList tags) { + private void RecordSubHistogram(T data, string name, int value, in TagList tags) + { if (data is not IHaveSubMetricName) return; @@ -302,24 +335,29 @@ private void RecordSubHistogram(T data, string name, int value, in TagList tags) _histograms.GetOrAdd(fullName, FoundatioDiagnostics.Meter.CreateHistogram(fullName)).Record(value, tags); } - protected string GetFullMetricName(string name) { + protected string GetFullMetricName(string name) + { return String.Concat(_metricsPrefix, ".", name); } - protected string GetFullMetricName(string customMetricName, string name) { + protected string GetFullMetricName(string customMetricName, string name) + { return String.IsNullOrEmpty(customMetricName) ? GetFullMetricName(name) : String.Concat(_metricsPrefix, ".", customMetricName.ToLower(), ".", name); } - protected CancellationTokenSource GetLinkedDisposableCancellationTokenSource(CancellationToken cancellationToken) { + protected CancellationTokenSource GetLinkedDisposableCancellationTokenSource(CancellationToken cancellationToken) + { return CancellationTokenSource.CreateLinkedTokenSource(_queueDisposedCancellationTokenSource.Token, cancellationToken); } - public override void Dispose() { - if (_isDisposed) { + public override void Dispose() + { + if (_isDisposed) + { _logger.LogTrace("Queue {Name} ({Id}) dispose was already called.", _options.Name, QueueId); return; } - + _isDisposed = true; _logger.LogTrace("Queue {Name} ({Id}) dispose", _options.Name, QueueId); _queueDisposedCancellationTokenSource?.Cancel(); @@ -339,4 +377,4 @@ public override void Dispose() { _behaviors.Clear(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/QueueBehaviour.cs b/src/Foundatio/Queues/QueueBehaviour.cs index 16dc57417..8c3bcee2f 100644 --- a/src/Foundatio/Queues/QueueBehaviour.cs +++ b/src/Foundatio/Queues/QueueBehaviour.cs @@ -2,16 +2,20 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Foundatio.Queues { - public interface IQueueBehavior where T : class { +namespace Foundatio.Queues +{ + public interface IQueueBehavior where T : class + { void Attach(IQueue queue); } - public abstract class QueueBehaviorBase : IQueueBehavior, IDisposable where T : class { + public abstract class QueueBehaviorBase : IQueueBehavior, IDisposable where T : class + { protected IQueue _queue; private readonly List _disposables = new(); - public virtual void Attach(IQueue queue) { + public virtual void Attach(IQueue queue) + { _queue = queue; _disposables.Add(_queue.Enqueuing.AddHandler(OnEnqueuing)); @@ -22,31 +26,38 @@ public virtual void Attach(IQueue queue) { _disposables.Add(_queue.Abandoned.AddHandler(OnAbandoned)); } - protected virtual Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) { + protected virtual Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) + { return Task.CompletedTask; } - protected virtual Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) { + protected virtual Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) + { return Task.CompletedTask; } - protected virtual Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) { + protected virtual Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) + { return Task.CompletedTask; } - protected virtual Task OnLockRenewed(object sender, LockRenewedEventArgs dequeuedEventArgs) { + protected virtual Task OnLockRenewed(object sender, LockRenewedEventArgs dequeuedEventArgs) + { return Task.CompletedTask; } - protected virtual Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) { + protected virtual Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) + { return Task.CompletedTask; } - protected virtual Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) { + protected virtual Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) + { return Task.CompletedTask; } - public virtual void Dispose() { + public virtual void Dispose() + { foreach (var disposable in _disposables) disposable.Dispose(); } diff --git a/src/Foundatio/Queues/QueueEntry.cs b/src/Foundatio/Queues/QueueEntry.cs index d4dda389a..ad4efe673 100644 --- a/src/Foundatio/Queues/QueueEntry.cs +++ b/src/Foundatio/Queues/QueueEntry.cs @@ -3,12 +3,15 @@ using System.Threading.Tasks; using Foundatio.Utility; -namespace Foundatio.Queues { - public class QueueEntry : IQueueEntry, IQueueEntryMetadata, IAsyncDisposable where T : class { +namespace Foundatio.Queues +{ + public class QueueEntry : IQueueEntry, IQueueEntryMetadata, IAsyncDisposable where T : class + { private readonly IQueue _queue; private readonly T _original; - public QueueEntry(string id, string correlationId, T value, IQueue queue, DateTime enqueuedTimeUtc, int attempts) { + public QueueEntry(string id, string correlationId, T value, IQueue queue, DateTime enqueuedTimeUtc, int attempts) + { Id = id; CorrelationId = correlationId; _original = value; @@ -34,40 +37,48 @@ public QueueEntry(string id, string correlationId, T value, IQueue queue, Dat public TimeSpan ProcessingTime { get; set; } public TimeSpan TotalTime { get; set; } - void IQueueEntry.MarkCompleted() { + void IQueueEntry.MarkCompleted() + { IsCompleted = true; } - void IQueueEntry.MarkAbandoned() { + void IQueueEntry.MarkAbandoned() + { IsAbandoned = true; } - public Task RenewLockAsync() { + public Task RenewLockAsync() + { RenewedTimeUtc = SystemClock.UtcNow; return _queue.RenewLockAsync(this); } - public Task CompleteAsync() { + public Task CompleteAsync() + { return _queue.CompleteAsync(this); } - public Task AbandonAsync() { + public Task AbandonAsync() + { return _queue.AbandonAsync(this); } - public async ValueTask DisposeAsync() { + public async ValueTask DisposeAsync() + { if (!IsAbandoned && !IsCompleted) await AbandonAsync(); } - internal void Reset() { + internal void Reset() + { IsCompleted = false; IsAbandoned = false; Value = _original.DeepClone(); } } - public interface IQueueEntryMetadata { + public interface IQueueEntryMetadata + { string Id { get; } string CorrelationId { get; } IDictionary Properties { get; } @@ -78,4 +89,4 @@ public interface IQueueEntryMetadata { TimeSpan ProcessingTime { get; } TimeSpan TotalTime { get; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Queues/QueueStatSummary.cs b/src/Foundatio/Queues/QueueStatSummary.cs index 67a563332..7b8ba7fdb 100644 --- a/src/Foundatio/Queues/QueueStatSummary.cs +++ b/src/Foundatio/Queues/QueueStatSummary.cs @@ -1,7 +1,9 @@ using Foundatio.Metrics; -namespace Foundatio.Queues { - public class QueueStatSummary { +namespace Foundatio.Queues +{ + public class QueueStatSummary + { public GaugeStatSummary Count { get; set; } public GaugeStatSummary Working { get; set; } public GaugeStatSummary Deadletter { get; set; } diff --git a/src/Foundatio/Queues/SharedQueueOptions.cs b/src/Foundatio/Queues/SharedQueueOptions.cs index 5148de6a0..4d4543524 100644 --- a/src/Foundatio/Queues/SharedQueueOptions.cs +++ b/src/Foundatio/Queues/SharedQueueOptions.cs @@ -1,57 +1,65 @@ using System; using System.Collections.Generic; -namespace Foundatio.Queues { - public class SharedQueueOptions : SharedOptions where T : class { +namespace Foundatio.Queues +{ + public class SharedQueueOptions : SharedOptions where T : class + { public string Name { get; set; } = typeof(T).Name; public int Retries { get; set; } = 2; public TimeSpan WorkItemTimeout { get; set; } = TimeSpan.FromMinutes(5); public ICollection> Behaviors { get; set; } = new List>(); } - public class SharedQueueOptionsBuilder : SharedOptionsBuilder + public class SharedQueueOptionsBuilder : SharedOptionsBuilder where T : class where TOptions : SharedQueueOptions, new() - where TBuilder : SharedQueueOptionsBuilder { - public TBuilder Name(string name) { + where TBuilder : SharedQueueOptionsBuilder + { + public TBuilder Name(string name) + { if (!String.IsNullOrEmpty(name)) Target.Name = name; return (TBuilder)this; } - public TBuilder Retries(int retries) { + public TBuilder Retries(int retries) + { if (retries < 0) throw new ArgumentOutOfRangeException(nameof(retries)); - + Target.Retries = retries; return (TBuilder)this; } - public TBuilder WorkItemTimeout(TimeSpan timeout) { + public TBuilder WorkItemTimeout(TimeSpan timeout) + { if (timeout == null) throw new ArgumentNullException(nameof(timeout)); - + if (timeout < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); - + Target.WorkItemTimeout = timeout; return (TBuilder)this; } - public TBuilder Behaviors(params IQueueBehavior[] behaviors) { + public TBuilder Behaviors(params IQueueBehavior[] behaviors) + { Target.Behaviors = behaviors; return (TBuilder)this; } - public TBuilder AddBehavior(IQueueBehavior behavior) { + public TBuilder AddBehavior(IQueueBehavior behavior) + { if (behavior == null) throw new ArgumentNullException(nameof(behavior)); - + if (Target.Behaviors == null) - Target.Behaviors = new List> (); + Target.Behaviors = new List>(); Target.Behaviors.Add(behavior); return (TBuilder)this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Serializer/IHaveSerializer.cs b/src/Foundatio/Serializer/IHaveSerializer.cs index 6b4271358..c452ad418 100644 --- a/src/Foundatio/Serializer/IHaveSerializer.cs +++ b/src/Foundatio/Serializer/IHaveSerializer.cs @@ -1,5 +1,7 @@ -namespace Foundatio.Serializer { - public interface IHaveSerializer { +namespace Foundatio.Serializer +{ + public interface IHaveSerializer + { ISerializer Serializer { get; } } } diff --git a/src/Foundatio/Serializer/ISerializer.cs b/src/Foundatio/Serializer/ISerializer.cs index 25f022b23..1378c9a6d 100644 --- a/src/Foundatio/Serializer/ISerializer.cs +++ b/src/Foundatio/Serializer/ISerializer.cs @@ -2,32 +2,40 @@ using System.IO; using System.Text; -namespace Foundatio.Serializer { - public interface ISerializer { +namespace Foundatio.Serializer +{ + public interface ISerializer + { object Deserialize(Stream data, Type objectType); void Serialize(object value, Stream output); } - public interface ITextSerializer : ISerializer {} + public interface ITextSerializer : ISerializer { } - public static class DefaultSerializer { + public static class DefaultSerializer + { public static ISerializer Instance { get; set; } = new SystemTextJsonSerializer(); } - public static class SerializerExtensions { - public static T Deserialize(this ISerializer serializer, Stream data) { + public static class SerializerExtensions + { + public static T Deserialize(this ISerializer serializer, Stream data) + { return (T)serializer.Deserialize(data, typeof(T)); } - public static T Deserialize(this ISerializer serializer, byte[] data) { + public static T Deserialize(this ISerializer serializer, byte[] data) + { return (T)serializer.Deserialize(new MemoryStream(data), typeof(T)); } - public static object Deserialize(this ISerializer serializer, byte[] data, Type objectType) { + public static object Deserialize(this ISerializer serializer, byte[] data, Type objectType) + { return serializer.Deserialize(new MemoryStream(data), objectType); } - public static T Deserialize(this ISerializer serializer, string data) { + public static T Deserialize(this ISerializer serializer, string data) + { byte[] bytes; if (data == null) bytes = Array.Empty(); @@ -39,7 +47,8 @@ public static T Deserialize(this ISerializer serializer, string data) { return (T)serializer.Deserialize(new MemoryStream(bytes), typeof(T)); } - public static object Deserialize(this ISerializer serializer, string data, Type objectType) { + public static object Deserialize(this ISerializer serializer, string data, Type objectType) + { byte[] bytes; if (data == null) bytes = Array.Empty(); @@ -51,18 +60,20 @@ public static object Deserialize(this ISerializer serializer, string data, Type return serializer.Deserialize(new MemoryStream(bytes), objectType); } - public static string SerializeToString(this ISerializer serializer, T value) { - if (value == null) + public static string SerializeToString(this ISerializer serializer, T value) + { + if (value == null) return null; var bytes = serializer.SerializeToBytes(value); if (serializer is ITextSerializer) return Encoding.UTF8.GetString(bytes); - return Convert.ToBase64String(bytes); + return Convert.ToBase64String(bytes); } - public static byte[] SerializeToBytes(this ISerializer serializer, T value) { + public static byte[] SerializeToBytes(this ISerializer serializer, T value) + { if (value == null) return null; @@ -72,4 +83,4 @@ public static byte[] SerializeToBytes(this ISerializer serializer, T value) { return stream.ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Serializer/SystemTextJsonSerializer.cs b/src/Foundatio/Serializer/SystemTextJsonSerializer.cs index 9d6c5dd33..ea9a6a259 100644 --- a/src/Foundatio/Serializer/SystemTextJsonSerializer.cs +++ b/src/Foundatio/Serializer/SystemTextJsonSerializer.cs @@ -3,40 +3,53 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Foundatio.Serializer { - public class SystemTextJsonSerializer : ITextSerializer { +namespace Foundatio.Serializer +{ + public class SystemTextJsonSerializer : ITextSerializer + { private readonly JsonSerializerOptions _serializeOptions; private readonly JsonSerializerOptions _deserializeOptions; - public SystemTextJsonSerializer(JsonSerializerOptions serializeOptions = null, JsonSerializerOptions deserializeOptions = null) { - if (serializeOptions != null) { + public SystemTextJsonSerializer(JsonSerializerOptions serializeOptions = null, JsonSerializerOptions deserializeOptions = null) + { + if (serializeOptions != null) + { _serializeOptions = serializeOptions; - } else { + } + else + { _serializeOptions = new JsonSerializerOptions(); } - if (deserializeOptions != null) { + if (deserializeOptions != null) + { _deserializeOptions = deserializeOptions; - } else { + } + else + { _deserializeOptions = new JsonSerializerOptions(); _deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter()); } } - public void Serialize(object data, Stream outputStream) { + public void Serialize(object data, Stream outputStream) + { var writer = new Utf8JsonWriter(outputStream); JsonSerializer.Serialize(writer, data, data.GetType(), _serializeOptions); writer.Flush(); } - public object Deserialize(Stream inputStream, Type objectType) { + public object Deserialize(Stream inputStream, Type objectType) + { using var reader = new StreamReader(inputStream); return JsonSerializer.Deserialize(reader.ReadToEnd(), objectType, _deserializeOptions); } } - public class ObjectToInferredTypesConverter : JsonConverter { - public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + public class ObjectToInferredTypesConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { if (reader.TokenType == JsonTokenType.True) return true; @@ -54,7 +67,8 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS return document.RootElement.Clone(); } - public override void Write(Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options) { + public override void Write(Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options) + { throw new InvalidOperationException(); } } diff --git a/src/Foundatio/Storage/ActionableStream.cs b/src/Foundatio/Storage/ActionableStream.cs index 291936988..615b48656 100644 --- a/src/Foundatio/Storage/ActionableStream.cs +++ b/src/Foundatio/Storage/ActionableStream.cs @@ -3,21 +3,27 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Storage { - public class ActionableStream : Stream { +namespace Foundatio.Storage +{ + public class ActionableStream : Stream + { private readonly Action _disposeAction; private readonly Stream _stream; - protected override void Dispose(bool disposing) { - try { + protected override void Dispose(bool disposing) + { + try + { _disposeAction.Invoke(); - } catch { /* ignore if these are already disposed; this is to make sure they are */ } - + } + catch { /* ignore if these are already disposed; this is to make sure they are */ } + _stream.Dispose(); base.Dispose(disposing); } - public ActionableStream(Stream stream, Action disposeAction) { + public ActionableStream(Stream stream, Action disposeAction) + { _stream = stream ?? throw new ArgumentNullException(); _disposeAction = disposeAction; } @@ -30,44 +36,54 @@ public ActionableStream(Stream stream, Action disposeAction) { public override long Length => _stream.Length; - public override long Position { + public override long Position + { get => _stream.Position; set => _stream.Position = value; } - public override long Seek(long offset, SeekOrigin origin) { + public override long Seek(long offset, SeekOrigin origin) + { return _stream.Seek(offset, origin); } - public override void SetLength(long value) { + public override void SetLength(long value) + { _stream.SetLength(value); } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { return _stream.CopyToAsync(destination, bufferSize, cancellationToken); } - public override void Flush() { + public override void Flush() + { _stream.Flush(); } - public override Task FlushAsync(CancellationToken cancellationToken) { + public override Task FlushAsync(CancellationToken cancellationToken) + { return _stream.FlushAsync(cancellationToken); } - public override int Read(byte[] buffer, int offset, int count) { + public override int Read(byte[] buffer, int offset, int count) + { return _stream.Read(buffer, offset, count); } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { return _stream.ReadAsync(buffer, offset, count, cancellationToken); } - public override void Write(byte[] buffer, int offset, int count) { + public override void Write(byte[] buffer, int offset, int count) + { _stream.Write(buffer, offset, count); } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { return _stream.WriteAsync(buffer, offset, count, cancellationToken); } } diff --git a/src/Foundatio/Storage/FolderFileStorage.cs b/src/Foundatio/Storage/FolderFileStorage.cs index 0b47ec52a..146ba77f4 100644 --- a/src/Foundatio/Storage/FolderFileStorage.cs +++ b/src/Foundatio/Storage/FolderFileStorage.cs @@ -11,13 +11,16 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Storage { - public class FolderFileStorage : IFileStorage { +namespace Foundatio.Storage +{ + public class FolderFileStorage : IFileStorage + { private readonly AsyncLock _lock = new(); private readonly ISerializer _serializer; protected readonly ILogger _logger; - public FolderFileStorage(FolderFileStorageOptions options) { + public FolderFileStorage(FolderFileStorageOptions options) + { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -48,8 +51,10 @@ public FolderFileStorage(Builder GetFileStreamAsync(string path, CancellationToken cancellationToken = default) => GetFileStreamAsync(path, StreamMode.Read, cancellationToken); - public Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) { - var stream = streamMode switch { + public Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) + { + var stream = streamMode switch + { StreamMode.Read => GetFileStreamAsync(path, FileAccess.Read), StreamMode.Write => GetFileStreamAsync(path, FileAccess.Write), _ => throw new NotSupportedException($"Stream mode {streamMode} is not supported."), @@ -58,29 +63,36 @@ public Task GetFileStreamAsync(string path, StreamMode streamMode, Cance return Task.FromResult(stream); } - public Stream GetFileStreamAsync(string path, FileAccess fileAccess) { + public Stream GetFileStreamAsync(string path, FileAccess fileAccess) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); var fullPath = Path.Combine(Folder, normalizedPath); - if (fileAccess != FileAccess.Read) { + if (fileAccess != FileAccess.Read) + { CreateFileStream(fullPath).Dispose(); } var fileMode = GetFileModeForFileAccess(fileAccess); - try { + try + { return File.Open(fullPath, fileMode, fileAccess); - } catch (IOException ex) when (ex is FileNotFoundException or DirectoryNotFoundException) { + } + catch (IOException ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { _logger.LogError(ex, "Unable to get file stream for {Path}: {Message}", normalizedPath, ex.Message); return null; } } - private FileMode GetFileModeForFileAccess(FileAccess fileAccess) { - return fileAccess switch { + private FileMode GetFileModeForFileAccess(FileAccess fileAccess) + { + return fileAccess switch + { FileAccess.Read => FileMode.Open, FileAccess.Write => FileMode.Create, FileAccess.ReadWrite => FileMode.OpenOrCreate, @@ -88,7 +100,8 @@ private FileMode GetFileModeForFileAccess(FileAccess fileAccess) { }; } - public Task GetFileInfoAsync(string path) { + public Task GetFileInfoAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -96,12 +109,14 @@ public Task GetFileInfoAsync(string path) { _logger.LogTrace("Getting file stream for {Path}", normalizedPath); var info = new FileInfo(Path.Combine(Folder, normalizedPath)); - if (!info.Exists) { + if (!info.Exists) + { _logger.LogError("Unable to get file info for {Path}: File Not Found", normalizedPath); return Task.FromResult(null); } - return Task.FromResult(new FileSpec { + return Task.FromResult(new FileSpec + { Path = normalizedPath.Replace(Folder, String.Empty), Created = info.CreationTimeUtc, Modified = info.LastWriteTimeUtc, @@ -109,7 +124,8 @@ public Task GetFileInfoAsync(string path) { }); } - public Task ExistsAsync(string path) { + public Task ExistsAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -118,7 +134,8 @@ public Task ExistsAsync(string path) { return Task.FromResult(File.Exists(Path.Combine(Folder, normalizedPath))); } - public async Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) { + public async Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (stream == null) @@ -128,23 +145,30 @@ public async Task SaveFileAsync(string path, Stream stream, CancellationTo _logger.LogTrace("Saving {Path}", normalizedPath); string file = Path.Combine(Folder, normalizedPath); - try { + try + { using var fileStream = CreateFileStream(file); await stream.CopyToAsync(fileStream).AnyContext(); return true; - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error saving {Path}: {Message}", normalizedPath, ex.Message); return false; } } - private Stream CreateFileStream(string filePath) { - try { + private Stream CreateFileStream(string filePath) + { + try + { return File.Create(filePath); - } catch (DirectoryNotFoundException) { } + } + catch (DirectoryNotFoundException) { } string directory = Path.GetDirectoryName(filePath); - if (directory != null) { + if (directory != null) + { _logger.LogInformation("Creating {Directory} directory", directory); Directory.CreateDirectory(directory); } @@ -152,7 +176,8 @@ private Stream CreateFileStream(string filePath) { return File.Create(filePath); } - public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { + public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(newPath)) @@ -162,19 +187,25 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio string normalizedNewPath = newPath.NormalizePath(); _logger.LogInformation("Renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); - try { - using (await _lock.LockAsync().AnyContext()) { + try + { + using (await _lock.LockAsync().AnyContext()) + { string directory = Path.GetDirectoryName(normalizedNewPath); - if (directory != null) { + if (directory != null) + { _logger.LogInformation("Creating {Directory} directory", directory); Directory.CreateDirectory(Path.Combine(Folder, directory)); } string oldFullPath = Path.Combine(Folder, normalizedPath); string newFullPath = Path.Combine(Folder, normalizedNewPath); - try { + try + { File.Move(oldFullPath, newFullPath); - } catch (IOException ex) { + } + catch (IOException ex) + { _logger.LogDebug(ex, "Error renaming {Path} to {NewPath}: Deleting {NewFullPath}", normalizedPath, normalizedNewPath, newFullPath); File.Delete(newFullPath); @@ -182,7 +213,9 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio File.Move(oldFullPath, newFullPath); } } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); return false; } @@ -190,7 +223,8 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio return true; } - public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { + public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(targetPath)) @@ -200,17 +234,22 @@ public async Task CopyFileAsync(string path, string targetPath, Cancellati string normalizedTargetPath = targetPath.NormalizePath(); _logger.LogInformation("Copying {Path} to {TargetPath}", normalizedPath, normalizedTargetPath); - try { - using (await _lock.LockAsync().AnyContext()) { + try + { + using (await _lock.LockAsync().AnyContext()) + { string directory = Path.GetDirectoryName(normalizedTargetPath); - if (directory != null) { + if (directory != null) + { _logger.LogInformation("Creating {Directory} directory", directory); Directory.CreateDirectory(Path.Combine(Folder, directory)); } File.Copy(Path.Combine(Folder, normalizedPath), Path.Combine(Folder, normalizedTargetPath)); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error copying {Path} to {TargetPath}: {Message}", normalizedPath, normalizedTargetPath, ex.Message); return false; } @@ -218,16 +257,20 @@ public async Task CopyFileAsync(string path, string targetPath, Cancellati return true; } - public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { + public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Deleting {Path}", normalizedPath); - try { + try + { File.Delete(Path.Combine(Folder, normalizedPath)); - } catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) { + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { _logger.LogError(ex, "Unable to delete {Path}: {Message}", normalizedPath, ex.Message); return Task.FromResult(false); } @@ -235,11 +278,14 @@ public Task DeleteFileAsync(string path, CancellationToken cancellationTok return Task.FromResult(true); } - public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { + public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) + { int count = 0; - if (String.IsNullOrEmpty(searchPattern) || searchPattern == "*") { - if (Directory.Exists(Folder)) { + if (String.IsNullOrEmpty(searchPattern) || searchPattern == "*") + { + if (Directory.Exists(Folder)) + { _logger.LogInformation("Deleting {Directory} directory", Folder); count += Directory.EnumerateFiles(Folder, "*,*", SearchOption.AllDirectories).Count(); Directory.Delete(Folder, true); @@ -251,9 +297,11 @@ public Task DeleteFilesAsync(string searchPattern = null, CancellationToken searchPattern = searchPattern.NormalizePath(); string path = Path.Combine(Folder, searchPattern); - if (path[path.Length - 1] == Path.DirectorySeparatorChar || path.EndsWith(Path.DirectorySeparatorChar + "*")) { + if (path[path.Length - 1] == Path.DirectorySeparatorChar || path.EndsWith(Path.DirectorySeparatorChar + "*")) + { string directory = Path.GetDirectoryName(path); - if (Directory.Exists(directory)) { + if (Directory.Exists(directory)) + { _logger.LogInformation("Deleting {Directory} directory", directory); count += Directory.EnumerateFiles(directory, "*,*", SearchOption.AllDirectories).Count(); Directory.Delete(directory, true); @@ -264,7 +312,8 @@ public Task DeleteFilesAsync(string searchPattern = null, CancellationToken return Task.FromResult(0); } - if (Directory.Exists(path)) { + if (Directory.Exists(path)) + { _logger.LogInformation("Deleting {Directory} directory", path); count += Directory.EnumerateFiles(path, "*,*", SearchOption.AllDirectories).Count(); Directory.Delete(path, true); @@ -273,7 +322,8 @@ public Task DeleteFilesAsync(string searchPattern = null, CancellationToken } _logger.LogInformation("Deleting files matching {SearchPattern}", searchPattern); - foreach (string file in Directory.EnumerateFiles(Folder, searchPattern, SearchOption.AllDirectories)) { + foreach (string file in Directory.EnumerateFiles(Folder, searchPattern, SearchOption.AllDirectories)) + { _logger.LogTrace("Deleting {Path}", file); File.Delete(file); count++; @@ -284,7 +334,8 @@ public Task DeleteFilesAsync(string searchPattern = null, CancellationToken } - public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) { + public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) + { if (pageSize <= 0) return PagedFileListResult.Empty; @@ -293,7 +344,8 @@ public async Task GetPagedFileListAsync(int pageSize = 100, searchPattern = searchPattern.NormalizePath(); - if (!Directory.Exists(Path.GetDirectoryName(Path.Combine(Folder, searchPattern)))) { + if (!Directory.Exists(Path.GetDirectoryName(Path.Combine(Folder, searchPattern)))) + { _logger.LogTrace("Returning empty file list matching {SearchPattern}: Directory Not Found", searchPattern); return PagedFileListResult.Empty; } @@ -303,7 +355,8 @@ public async Task GetPagedFileListAsync(int pageSize = 100, return result; } - private NextPageResult GetFiles(string searchPattern, int page, int pageSize) { + private NextPageResult GetFiles(string searchPattern, int page, int pageSize) + { var list = new List(); int pagingLimit = pageSize; int skip = (page - 1) * pagingLimit; @@ -311,12 +364,14 @@ private NextPageResult GetFiles(string searchPattern, int page, int pageSize) { pagingLimit++; _logger.LogTrace(s => s.Property("Limit", pagingLimit).Property("Skip", skip), "Getting file list matching {SearchPattern}...", searchPattern); - foreach (string path in Directory.EnumerateFiles(Folder, searchPattern, SearchOption.AllDirectories).Skip(skip).Take(pagingLimit)) { + foreach (string path in Directory.EnumerateFiles(Folder, searchPattern, SearchOption.AllDirectories).Skip(skip).Take(pagingLimit)) + { var info = new FileInfo(path); if (!info.Exists) continue; - list.Add(new FileSpec { + list.Add(new FileSpec + { Path = info.FullName.Replace(Folder, String.Empty), Created = info.CreationTimeUtc, Modified = info.LastWriteTimeUtc, @@ -325,12 +380,14 @@ private NextPageResult GetFiles(string searchPattern, int page, int pageSize) { } bool hasMore = false; - if (list.Count == pagingLimit) { + if (list.Count == pagingLimit) + { hasMore = true; list.RemoveAt(pagingLimit - 1); } - return new NextPageResult { + return new NextPageResult + { Success = true, HasMore = hasMore, Files = list, diff --git a/src/Foundatio/Storage/FolderFileStorageOptions.cs b/src/Foundatio/Storage/FolderFileStorageOptions.cs index efa179754..5335bb6cc 100644 --- a/src/Foundatio/Storage/FolderFileStorageOptions.cs +++ b/src/Foundatio/Storage/FolderFileStorageOptions.cs @@ -1,12 +1,16 @@ using System; -namespace Foundatio.Storage { - public class FolderFileStorageOptions : SharedOptions { +namespace Foundatio.Storage +{ + public class FolderFileStorageOptions : SharedOptions + { public string Folder { get; set; } } - public class FolderFileStorageOptionsBuilder : SharedOptionsBuilder { - public FolderFileStorageOptionsBuilder Folder(string folder) { + public class FolderFileStorageOptionsBuilder : SharedOptionsBuilder + { + public FolderFileStorageOptionsBuilder Folder(string folder) + { if (string.IsNullOrEmpty(folder)) throw new ArgumentNullException(nameof(folder)); Target.Folder = folder; diff --git a/src/Foundatio/Storage/IFileStorage.cs b/src/Foundatio/Storage/IFileStorage.cs index 9518d3d7a..dadfa9fee 100644 --- a/src/Foundatio/Storage/IFileStorage.cs +++ b/src/Foundatio/Storage/IFileStorage.cs @@ -9,8 +9,10 @@ using Foundatio.Serializer; using Foundatio.Utility; -namespace Foundatio.Storage { - public interface IFileStorage : IHaveSerializer, IDisposable { +namespace Foundatio.Storage +{ + public interface IFileStorage : IHaveSerializer, IDisposable + { [Obsolete($"Use {nameof(GetFileStreamAsync)} with {nameof(FileAccess)} instead to define read or write behaviour of stream")] Task GetFileStreamAsync(string path, CancellationToken cancellationToken = default); /// @@ -31,34 +33,40 @@ public interface IFileStorage : IHaveSerializer, IDisposable { Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default); } - public interface IHasNextPageFunc { + public interface IHasNextPageFunc + { Func> NextPageFunc { get; set; } } - public class NextPageResult { + public class NextPageResult + { public bool Success { get; set; } public bool HasMore { get; set; } public IReadOnlyCollection Files { get; set; } public Func> NextPageFunc { get; set; } } - public class PagedFileListResult : IHasNextPageFunc { + public class PagedFileListResult : IHasNextPageFunc + { private static readonly IReadOnlyCollection _empty = new ReadOnlyCollection(Array.Empty()); public static readonly PagedFileListResult Empty = new(_empty); - public PagedFileListResult(IReadOnlyCollection files) { + public PagedFileListResult(IReadOnlyCollection files) + { Files = files; HasMore = false; ((IHasNextPageFunc)this).NextPageFunc = null; } - public PagedFileListResult(IReadOnlyCollection files, bool hasMore, Func> nextPageFunc) { + public PagedFileListResult(IReadOnlyCollection files, bool hasMore, Func> nextPageFunc) + { Files = files; HasMore = hasMore; ((IHasNextPageFunc)this).NextPageFunc = nextPageFunc; } - public PagedFileListResult(Func> nextPageFunc) { + public PagedFileListResult(Func> nextPageFunc) + { ((IHasNextPageFunc)this).NextPageFunc = nextPageFunc; } @@ -67,16 +75,20 @@ public PagedFileListResult(Func> nextP protected IDictionary Data { get; } = new DataDictionary(); Func> IHasNextPageFunc.NextPageFunc { get; set; } - public async Task NextPageAsync() { + public async Task NextPageAsync() + { if (((IHasNextPageFunc)this).NextPageFunc == null) return false; var result = await ((IHasNextPageFunc)this).NextPageFunc(this).AnyContext(); - if (result.Success) { + if (result.Success) + { Files = result.Files; HasMore = result.HasMore; ((IHasNextPageFunc)this).NextPageFunc = result.NextPageFunc; - } else { + } + else + { Files = _empty; HasMore = false; ((IHasNextPageFunc)this).NextPageFunc = null; @@ -87,7 +99,8 @@ public async Task NextPageAsync() { } [DebuggerDisplay("Path = {Path}, Created = {Created}, Modified = {Modified}, Size = {Size} bytes")] - public class FileSpec { + public class FileSpec + { public string Path { get; set; } public DateTime Created { get; set; } public DateTime Modified { get; set; } @@ -99,8 +112,10 @@ public class FileSpec { // TODO: Add metadata object for custom properties } - public static class FileStorageExtensions { - public static Task SaveObjectAsync(this IFileStorage storage, string path, T data, CancellationToken cancellationToken = default) { + public static class FileStorageExtensions + { + public static Task SaveObjectAsync(this IFileStorage storage, string path, T data, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -108,7 +123,8 @@ public static Task SaveObjectAsync(this IFileStorage storage, string pa return storage.SaveFileAsync(path, new MemoryStream(bytes), cancellationToken); } - public static async Task GetObjectAsync(this IFileStorage storage, string path, CancellationToken cancellationToken = default) { + public static async Task GetObjectAsync(this IFileStorage storage, string path, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -119,7 +135,8 @@ public static async Task GetObjectAsync(this IFileStorage storage, string return default; } - public static async Task DeleteFilesAsync(this IFileStorage storage, IEnumerable files) { + public static async Task DeleteFilesAsync(this IFileStorage storage, IEnumerable files) + { if (files == null) throw new ArgumentNullException(nameof(files)); @@ -127,7 +144,8 @@ public static async Task DeleteFilesAsync(this IFileStorage storage, IEnumerable await storage.DeleteFileAsync(file.Path).AnyContext(); } - public static async Task GetFileContentsAsync(this IFileStorage storage, string path) { + public static async Task GetFileContentsAsync(this IFileStorage storage, string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -138,7 +156,8 @@ public static async Task GetFileContentsAsync(this IFileStorage storage, return null; } - public static async Task GetFileContentsRawAsync(this IFileStorage storage, string path) { + public static async Task GetFileContentsRawAsync(this IFileStorage storage, string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -149,25 +168,29 @@ public static async Task GetFileContentsRawAsync(this IFileStorage stora var buffer = new byte[16 * 1024]; using var ms = new MemoryStream(); int read; - while ((read = await stream.ReadAsync(buffer, 0, buffer.Length).AnyContext()) > 0) { + while ((read = await stream.ReadAsync(buffer, 0, buffer.Length).AnyContext()) > 0) + { await ms.WriteAsync(buffer, 0, read).AnyContext(); } return ms.ToArray(); } - public static Task SaveFileAsync(this IFileStorage storage, string path, string contents) { + public static Task SaveFileAsync(this IFileStorage storage, string path, string contents) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return storage.SaveFileAsync(path, new MemoryStream(Encoding.UTF8.GetBytes(contents ?? String.Empty))); } - public static async Task> GetFileListAsync(this IFileStorage storage, string searchPattern = null, int? limit = null, CancellationToken cancellationToken = default) { + public static async Task> GetFileListAsync(this IFileStorage storage, string searchPattern = null, int? limit = null, CancellationToken cancellationToken = default) + { var files = new List(); limit ??= Int32.MaxValue; var result = await storage.GetPagedFileListAsync(limit.Value, searchPattern, cancellationToken).AnyContext(); - do { + do + { files.AddRange(result.Files); } while (result.HasMore && files.Count < limit.Value && await result.NextPageAsync().AnyContext()); diff --git a/src/Foundatio/Storage/InMemoryFileStorage.cs b/src/Foundatio/Storage/InMemoryFileStorage.cs index 2500d20de..6d8023d4f 100644 --- a/src/Foundatio/Storage/InMemoryFileStorage.cs +++ b/src/Foundatio/Storage/InMemoryFileStorage.cs @@ -12,8 +12,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Storage { - public class InMemoryFileStorage : IFileStorage { +namespace Foundatio.Storage +{ + public class InMemoryFileStorage : IFileStorage + { private readonly Dictionary> _storage = new(StringComparer.OrdinalIgnoreCase); private readonly AsyncLock _lock = new(); private readonly ISerializer _serializer; @@ -21,7 +23,8 @@ public class InMemoryFileStorage : IFileStorage { public InMemoryFileStorage() : this(o => o) { } - public InMemoryFileStorage(InMemoryFileStorageOptions options) { + public InMemoryFileStorage(InMemoryFileStorageOptions options) + { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -42,15 +45,18 @@ public InMemoryFileStorage(Builder GetFileStreamAsync(string path, CancellationToken cancellationToken = default) => GetFileStreamAsync(path, StreamMode.Read, cancellationToken); - public async Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) { + public async Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Getting file stream for {Path}", normalizedPath); - using (await _lock.LockAsync().AnyContext()) { - if (!_storage.ContainsKey(normalizedPath)) { + using (await _lock.LockAsync().AnyContext()) + { + if (!_storage.ContainsKey(normalizedPath)) + { _logger.LogError("Unable to get file stream for {Path}: File Not Found", normalizedPath); return null; } @@ -59,7 +65,8 @@ public async Task GetFileStreamAsync(string path, StreamMode streamMode, } } - public async Task GetFileInfoAsync(string path) { + public async Task GetFileInfoAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -73,25 +80,29 @@ public async Task GetFileInfoAsync(string path) { return null; } - public async Task ExistsAsync(string path) { + public async Task ExistsAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Checking if {Path} exists", normalizedPath); - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { return _storage.ContainsKey(normalizedPath); } } - private static byte[] ReadBytes(Stream input) { + private static byte[] ReadBytes(Stream input) + { using var ms = new MemoryStream(); input.CopyTo(ms); return ms.ToArray(); } - public async Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) { + public async Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (stream == null) @@ -104,8 +115,10 @@ public async Task SaveFileAsync(string path, Stream stream, CancellationTo if (contents.Length > MaxFileSize) throw new ArgumentException($"File size {contents.Length.ToFileSizeDisplay()} exceeds the maximum size of {MaxFileSize.ToFileSizeDisplay()}."); - using (await _lock.LockAsync().AnyContext()) { - _storage[normalizedPath] = Tuple.Create(new FileSpec { + using (await _lock.LockAsync().AnyContext()) + { + _storage[normalizedPath] = Tuple.Create(new FileSpec + { Created = SystemClock.UtcNow, Modified = SystemClock.UtcNow, Path = normalizedPath, @@ -119,7 +132,8 @@ public async Task SaveFileAsync(string path, Stream stream, CancellationTo return true; } - public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { + public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(newPath)) @@ -129,8 +143,10 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio string normalizedNewPath = newPath.NormalizePath(); _logger.LogInformation("Renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); - using (await _lock.LockAsync().AnyContext()) { - if (!_storage.ContainsKey(normalizedPath)) { + using (await _lock.LockAsync().AnyContext()) + { + if (!_storage.ContainsKey(normalizedPath)) + { _logger.LogDebug("Error renaming {Path} to {NewPath}: File not found", normalizedPath, normalizedNewPath); return false; } @@ -144,7 +160,8 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio return true; } - public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { + public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(targetPath)) @@ -154,8 +171,10 @@ public async Task CopyFileAsync(string path, string targetPath, Cancellati string normalizedTargetPath = targetPath.NormalizePath(); _logger.LogInformation("Copying {Path} to {TargetPath}", normalizedPath, normalizedTargetPath); - using (await _lock.LockAsync().AnyContext()) { - if (!_storage.ContainsKey(normalizedPath)) { + using (await _lock.LockAsync().AnyContext()) + { + if (!_storage.ContainsKey(normalizedPath)) + { _logger.LogDebug("Error copying {Path} to {TargetPath}: File not found", normalizedPath, normalizedTargetPath); return false; } @@ -168,15 +187,18 @@ public async Task CopyFileAsync(string path, string targetPath, Cancellati return true; } - public async Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { + public async Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Deleting {Path}", normalizedPath); - using (await _lock.LockAsync().AnyContext()) { - if (!_storage.ContainsKey(normalizedPath)) { + using (await _lock.LockAsync().AnyContext()) + { + if (!_storage.ContainsKey(normalizedPath)) + { _logger.LogError("Unable to delete {Path}: File not found", normalizedPath); return false; } @@ -187,9 +209,12 @@ public async Task DeleteFileAsync(string path, CancellationToken cancellat return true; } - public async Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { - if (String.IsNullOrEmpty(searchPattern) || searchPattern == "*") { - using (await _lock.LockAsync().AnyContext()) { + public async Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) + { + if (String.IsNullOrEmpty(searchPattern) || searchPattern == "*") + { + using (await _lock.LockAsync().AnyContext()) + { _storage.Clear(); } @@ -206,11 +231,13 @@ public async Task DeleteFilesAsync(string searchPattern = null, Cancellatio var regex = new Regex($"^{Regex.Escape(searchPattern).Replace("\\*", ".*?")}$"); - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { var keys = _storage.Keys.Where(k => regex.IsMatch(k)).Select(k => _storage[k].Item1).ToList(); _logger.LogInformation("Deleting {FileCount} files matching {SearchPattern} (Regex={SearchPatternRegex})", keys.Count, searchPattern, regex); - foreach (var key in keys) { + foreach (var key in keys) + { _logger.LogTrace("Deleting {Path}", key.Path); _storage.Remove(key.Path); count++; @@ -222,7 +249,8 @@ public async Task DeleteFilesAsync(string searchPattern = null, Cancellatio return count; } - public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) { + public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) + { if (pageSize <= 0) return PagedFileListResult.Empty; @@ -236,7 +264,8 @@ public async Task GetPagedFileListAsync(int pageSize = 100, return result; } - private async Task GetFilesAsync(string searchPattern, int page, int pageSize, CancellationToken cancellationToken = default) { + private async Task GetFilesAsync(string searchPattern, int page, int pageSize, CancellationToken cancellationToken = default) + { var list = new List(); int pagingLimit = pageSize; int skip = (page - 1) * pagingLimit; @@ -245,18 +274,21 @@ private async Task GetFilesAsync(string searchPattern, int page, var regex = new Regex($"^{Regex.Escape(searchPattern).Replace("\\*", ".*?")}$"); - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { _logger.LogTrace(s => s.Property("Limit", pagingLimit).Property("Skip", skip), "Getting file list matching {SearchPattern}...", regex); list.AddRange(_storage.Keys.Where(k => regex.IsMatch(k)).Select(k => _storage[k].Item1.DeepClone()).Skip(skip).Take(pagingLimit).ToList()); } bool hasMore = false; - if (list.Count == pagingLimit) { + if (list.Count == pagingLimit) + { hasMore = true; list.RemoveAt(pagingLimit - 1); } - return new NextPageResult { + return new NextPageResult + { Success = true, HasMore = hasMore, Files = list, @@ -264,8 +296,9 @@ private async Task GetFilesAsync(string searchPattern, int page, }; } - public void Dispose() { + public void Dispose() + { _storage?.Clear(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Storage/InMemoryFileStorageOptions.cs b/src/Foundatio/Storage/InMemoryFileStorageOptions.cs index 088c3f376..5609c3e9d 100644 --- a/src/Foundatio/Storage/InMemoryFileStorageOptions.cs +++ b/src/Foundatio/Storage/InMemoryFileStorageOptions.cs @@ -1,16 +1,21 @@ -namespace Foundatio.Storage { - public class InMemoryFileStorageOptions : SharedOptions { +namespace Foundatio.Storage +{ + public class InMemoryFileStorageOptions : SharedOptions + { public long MaxFileSize { get; set; } = 1024 * 1024 * 256; public int MaxFiles { get; set; } = 100; } - public class InMemoryFileStorageOptionsBuilder : SharedOptionsBuilder { - public InMemoryFileStorageOptionsBuilder MaxFileSize(long maxFileSize) { + public class InMemoryFileStorageOptionsBuilder : SharedOptionsBuilder + { + public InMemoryFileStorageOptionsBuilder MaxFileSize(long maxFileSize) + { Target.MaxFileSize = maxFileSize; return this; } - public InMemoryFileStorageOptionsBuilder MaxFiles(int maxFiles) { + public InMemoryFileStorageOptionsBuilder MaxFiles(int maxFiles) + { Target.MaxFiles = maxFiles; return this; } diff --git a/src/Foundatio/Storage/ScopedFileStorage.cs b/src/Foundatio/Storage/ScopedFileStorage.cs index 1b3515ad8..bf4e6fd36 100644 --- a/src/Foundatio/Storage/ScopedFileStorage.cs +++ b/src/Foundatio/Storage/ScopedFileStorage.cs @@ -5,11 +5,14 @@ using Foundatio.Serializer; using Foundatio.Utility; -namespace Foundatio.Storage { - public class ScopedFileStorage : IFileStorage { +namespace Foundatio.Storage +{ + public class ScopedFileStorage : IFileStorage + { private readonly string _pathPrefix; - public ScopedFileStorage(IFileStorage storage, string scope) { + public ScopedFileStorage(IFileStorage storage, string scope) + { UnscopedStorage = storage; Scope = !String.IsNullOrWhiteSpace(scope) ? scope.Trim() : null; _pathPrefix = Scope != null ? String.Concat(Scope, "/") : String.Empty; @@ -24,14 +27,16 @@ public ScopedFileStorage(IFileStorage storage, string scope) { public Task GetFileStreamAsync(string path, CancellationToken cancellationToken = default) => GetFileStreamAsync(path, StreamMode.Read, cancellationToken); - public Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) { + public Task GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return UnscopedStorage.GetFileStreamAsync(String.Concat(_pathPrefix, path), cancellationToken); } - public async Task GetFileInfoAsync(string path) { + public async Task GetFileInfoAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -42,14 +47,16 @@ public async Task GetFileInfoAsync(string path) { return file; } - public Task ExistsAsync(string path) { + public Task ExistsAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return UnscopedStorage.ExistsAsync(String.Concat(_pathPrefix, path)); } - public Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) { + public Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -59,7 +66,8 @@ public Task SaveFileAsync(string path, Stream stream, CancellationToken ca return UnscopedStorage.SaveFileAsync(String.Concat(_pathPrefix, path), stream, cancellationToken); } - public Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { + public Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(newPath)) @@ -68,7 +76,8 @@ public Task RenameFileAsync(string path, string newPath, CancellationToken return UnscopedStorage.RenameFileAsync(String.Concat(_pathPrefix, path), String.Concat(_pathPrefix, newPath), cancellationToken); } - public Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { + public Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(targetPath)) @@ -77,19 +86,22 @@ public Task CopyFileAsync(string path, string targetPath, CancellationToke return UnscopedStorage.CopyFileAsync(String.Concat(_pathPrefix, path), String.Concat(_pathPrefix, targetPath), cancellationToken); } - public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { + public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return UnscopedStorage.DeleteFileAsync(String.Concat(_pathPrefix, path), cancellationToken); } - public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { + public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) + { searchPattern = !String.IsNullOrEmpty(searchPattern) ? String.Concat(_pathPrefix, searchPattern) : String.Concat(_pathPrefix, "*"); return UnscopedStorage.DeleteFilesAsync(searchPattern, cancellation); } - public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) { + public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) + { if (pageSize <= 0) return PagedFileListResult.Empty; @@ -102,13 +114,15 @@ public async Task GetPagedFileListAsync(int pageSize = 100, return new PagedFileListResult(unscopedResult.Files, unscopedResult.HasMore, unscopedResult.HasMore ? _ => NextPage(unscopedResult) : null); } - private async Task NextPage(PagedFileListResult result) { + private async Task NextPage(PagedFileListResult result) + { var success = await result.NextPageAsync().AnyContext(); foreach (var file in result.Files) file.Path = file.Path.Substring(_pathPrefix.Length); - return new NextPageResult { + return new NextPageResult + { Success = success, HasMore = result.HasMore, Files = result.Files, diff --git a/src/Foundatio/Storage/StreamMode.cs b/src/Foundatio/Storage/StreamMode.cs index 9c058edeb..dd567f574 100644 --- a/src/Foundatio/Storage/StreamMode.cs +++ b/src/Foundatio/Storage/StreamMode.cs @@ -3,7 +3,8 @@ /// /// Tells what the stream will be used for /// -public enum StreamMode { +public enum StreamMode +{ Read, Write } diff --git a/src/Foundatio/Utility/AsyncEvent.cs b/src/Foundatio/Utility/AsyncEvent.cs index c06ed9a96..f157a9695 100644 --- a/src/Foundatio/Utility/AsyncEvent.cs +++ b/src/Foundatio/Utility/AsyncEvent.cs @@ -3,19 +3,23 @@ using System.Linq; using System.Threading.Tasks; -namespace Foundatio.Utility { - public class AsyncEvent : IObservable, IDisposable where TEventArgs : EventArgs { +namespace Foundatio.Utility +{ + public class AsyncEvent : IObservable, IDisposable where TEventArgs : EventArgs + { private readonly List> _invocationList = new(); private readonly object _lockObject = new(); private readonly bool _parallelInvoke; - public AsyncEvent(bool parallelInvoke = false) { + public AsyncEvent(bool parallelInvoke = false) + { _parallelInvoke = parallelInvoke; } public bool HasHandlers => _invocationList.Count > 0; - public IDisposable AddHandler(Func callback) { + public IDisposable AddHandler(Func callback) + { if (callback == null) throw new NullReferenceException("callback is null"); @@ -25,14 +29,17 @@ public IDisposable AddHandler(Func callback) { return new EventHandlerDisposable(this, callback); } - public IDisposable AddSyncHandler(Action callback) { - return AddHandler((sender, args) => { + public IDisposable AddSyncHandler(Action callback) + { + return AddHandler((sender, args) => + { callback(sender, args); return Task.CompletedTask; }); } - public void RemoveHandler(Func callback) { + public void RemoveHandler(Func callback) + { if (callback == null) throw new NullReferenceException("callback is null"); @@ -40,7 +47,8 @@ public void RemoveHandler(Func callback) { _invocationList.Remove(callback); } - public async Task InvokeAsync(object sender, TEventArgs eventArgs) { + public async Task InvokeAsync(object sender, TEventArgs eventArgs) + { List> tmpInvocationList; lock (_lockObject) @@ -53,25 +61,30 @@ public async Task InvokeAsync(object sender, TEventArgs eventArgs) { await callback(sender, eventArgs).AnyContext(); } - public IDisposable Subscribe(IObserver observer) { + public IDisposable Subscribe(IObserver observer) + { return AddSyncHandler((sender, args) => observer.OnNext(args)); } - public void Dispose() { + public void Dispose() + { lock (_lockObject) _invocationList.Clear(); } - private class EventHandlerDisposable : IDisposable where T : EventArgs { + private class EventHandlerDisposable : IDisposable where T : EventArgs + { private readonly AsyncEvent _event; private readonly Func _callback; - public EventHandlerDisposable(AsyncEvent @event, Func callback) { + public EventHandlerDisposable(AsyncEvent @event, Func callback) + { _event = @event; _callback = callback; } - public void Dispose() { + public void Dispose() + { _event.RemoveHandler(_callback); } } diff --git a/src/Foundatio/Utility/ConnectionStringParser.cs b/src/Foundatio/Utility/ConnectionStringParser.cs index a4ef473f0..68397490d 100644 --- a/src/Foundatio/Utility/ConnectionStringParser.cs +++ b/src/Foundatio/Utility/ConnectionStringParser.cs @@ -7,8 +7,10 @@ using System.Text.RegularExpressions; using Foundatio.Utility; -namespace Foundatio.Utility { - public static class ConnectionStringParser { +namespace Foundatio.Utility +{ + public static class ConnectionStringParser + { // borrowed from https://github.com/dotnet/corefx/blob/release/2.2/src/Common/src/System/Data/Common/DbConnectionOptions.Common.cs private const string ConnectionStringPattern = // may not contain embedded null except trailing last value "([\\s;]*" // leading whitespace and extra semicolons @@ -30,7 +32,8 @@ public static class ConnectionStringParser { private static readonly Regex _connectionStringRegex = new(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); - private static Dictionary Parse(string connectionString, IDictionary synonyms) { + private static Dictionary Parse(string connectionString, IDictionary synonyms) + { var parseTable = new Dictionary(StringComparer.OrdinalIgnoreCase); const int keyIndex = 1, valueIndex = 2; @@ -39,18 +42,21 @@ private static Dictionary Parse(string connectionString, IDictio if (null == connectionString) return parseTable; - + var match = _connectionStringRegex.Match(connectionString); if (!match.Success || (match.Length != connectionString.Length)) throw new ArgumentException($"Format of the initialization string does not conform to specification starting at index {match.Length}"); int indexValue = 0; var keyValues = match.Groups[valueIndex].Captures; - foreach (Capture keyPair in match.Groups[keyIndex].Captures) { + foreach (Capture keyPair in match.Groups[keyIndex].Captures) + { string keyName = keyPair.Value.Replace("==", "="); string keyValue = keyValues[indexValue++].Value; - if (0 < keyValue.Length) { - switch (keyValue[0]) { + if (0 < keyValue.Length) + { + switch (keyValue[0]) + { case '\"': keyValue = keyValue.Substring(1, keyValue.Length - 2).Replace("\"\"", "\""); break; @@ -58,7 +64,9 @@ private static Dictionary Parse(string connectionString, IDictio keyValue = keyValue.Substring(1, keyValue.Length - 2).Replace("\'\'", "\'"); break; } - } else { + } + else + { keyValue = null; } @@ -66,7 +74,7 @@ private static Dictionary Parse(string connectionString, IDictio if (!IsKeyNameValid(realKeyName)) throw new ArgumentException($"Keyword not supported: '{keyName}'"); - + if (!parseTable.ContainsKey(realKeyName)) parseTable[realKeyName] = keyValue; // last key-value pair wins (or first) } @@ -74,24 +82,28 @@ private static Dictionary Parse(string connectionString, IDictio return parseTable; } - private static bool IsKeyNameValid(string keyName) { + private static bool IsKeyNameValid(string keyName) + { if (String.IsNullOrEmpty(keyName)) return false; - + return keyName[0] != ';' && !Char.IsWhiteSpace(keyName[0]) && keyName.IndexOf('\u0000') == -1; } - public static Dictionary ParseConnectionString(this string connectionString, IDictionary synonyms = null) { + public static Dictionary ParseConnectionString(this string connectionString, IDictionary synonyms = null) + { return Parse(connectionString, synonyms); } - public static string BuildConnectionString(this IDictionary options, IEnumerable excludedKeys = null) { + public static string BuildConnectionString(this IDictionary options, IEnumerable excludedKeys = null) + { if (options == null || options.Count == 0) return null; var excludes = new HashSet(excludedKeys ?? new string[] { }, StringComparer.OrdinalIgnoreCase); var builder = new StringBuilder(); - foreach (var option in options) { + foreach (var option in options) + { if (excludes.Contains(option.Key)) continue; @@ -104,4 +116,4 @@ public static string BuildConnectionString(this IDictionary opti return builder.ToString().TrimEnd(';'); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/DataDictionary.cs b/src/Foundatio/Utility/DataDictionary.cs index b35e5ce37..245066a11 100644 --- a/src/Foundatio/Utility/DataDictionary.cs +++ b/src/Foundatio/Utility/DataDictionary.cs @@ -2,45 +2,56 @@ using System.Collections.Generic; using Foundatio.Serializer; -namespace Foundatio.Utility { - public interface IHaveData { +namespace Foundatio.Utility +{ + public interface IHaveData + { IDictionary Data { get; } } - public class DataDictionary : Dictionary { + public class DataDictionary : Dictionary + { public static readonly DataDictionary Empty = new(); - public DataDictionary() : base(StringComparer.OrdinalIgnoreCase) {} + public DataDictionary() : base(StringComparer.OrdinalIgnoreCase) { } - public DataDictionary(IEnumerable> values) : base(StringComparer.OrdinalIgnoreCase) { - if (values != null) { + public DataDictionary(IEnumerable> values) : base(StringComparer.OrdinalIgnoreCase) + { + if (values != null) + { foreach (var kvp in values) Add(kvp.Key, kvp.Value); } } } - public static class DataDictionaryExtensions { - public static object GetValueOrDefault(this IDictionary dictionary, string key) { + public static class DataDictionaryExtensions + { + public static object GetValueOrDefault(this IDictionary dictionary, string key) + { return dictionary.TryGetValue(key, out object value) ? value : null; } - public static object GetValueOrDefault(this IDictionary dictionary, string key, object defaultValue) { + public static object GetValueOrDefault(this IDictionary dictionary, string key, object defaultValue) + { return dictionary.TryGetValue(key, out object value) ? value : defaultValue; } - public static object GetValueOrDefault(this IDictionary dictionary, string key, Func defaultValueProvider) { + public static object GetValueOrDefault(this IDictionary dictionary, string key, Func defaultValueProvider) + { return dictionary.TryGetValue(key, out object value) ? value : defaultValueProvider(); } - public static T GetValue(this IDictionary dictionary, string key) { + public static T GetValue(this IDictionary dictionary, string key) + { if (!dictionary.ContainsKey(key)) throw new KeyNotFoundException($"Key \"{key}\" not found in the dictionary"); return dictionary.GetValueOrDefault(key); } - public static T GetValueOrDefault(this IDictionary dictionary, string key, T defaultValue = default) { + public static T GetValueOrDefault(this IDictionary dictionary, string key, T defaultValue = default) + { if (!dictionary.ContainsKey(key)) return defaultValue; @@ -51,18 +62,22 @@ public static T GetValueOrDefault(this IDictionary dictionary if (data == null) return defaultValue; - try { + try + { return data.ToType(); - } catch { } + } + catch { } return defaultValue; } - public static string GetString(this IDictionary dictionary, string name) { + public static string GetString(this IDictionary dictionary, string name) + { return dictionary.GetString(name, String.Empty); } - public static string GetString(this IDictionary dictionary, string name, string @default) { + public static string GetString(this IDictionary dictionary, string name, string @default) + { if (!dictionary.TryGetValue(name, out object value)) return @default; @@ -73,7 +88,8 @@ public static string GetString(this IDictionary dictionary, stri } } - public static class HaveDataExtensions { + public static class HaveDataExtensions + { /// /// Will get a value from the data dictionary and attempt to convert it to the target type using various type conversions /// as well as using either the passed in or one accessed from the accessor. @@ -84,7 +100,8 @@ public static class HaveDataExtensions { /// The default value to return if the value doesn't exist /// The serializer to use to convert the type from or array /// The value from the data dictionary converted to the desired type - public static T GetDataOrDefault(this IHaveData target, string key, T defaultValue = default, ISerializer serializer = null) { + public static T GetDataOrDefault(this IHaveData target, string key, T defaultValue = default, ISerializer serializer = null) + { if (serializer == null && target is IHaveSerializer haveSerializer) serializer = haveSerializer.Serializer; @@ -104,7 +121,8 @@ public static T GetDataOrDefault(this IHaveData target, string key, T default /// The value from the data dictionary converted to the desired type /// The serializer to use to convert the type from or array /// Whether or not we successfully got and converted the data - public static bool TryGetData(this IHaveData target, string key, out T value, ISerializer serializer = null) { + public static bool TryGetData(this IHaveData target, string key, out T value, ISerializer serializer = null) + { if (serializer == null && target is IHaveSerializer haveSerializer) serializer = haveSerializer.Serializer; diff --git a/src/Foundatio/Utility/DisposableAction.cs b/src/Foundatio/Utility/DisposableAction.cs index 813c9c824..3ebaebebe 100644 --- a/src/Foundatio/Utility/DisposableAction.cs +++ b/src/Foundatio/Utility/DisposableAction.cs @@ -1,10 +1,12 @@ using System; -namespace Foundatio.Utility { +namespace Foundatio.Utility +{ /// /// A class that will call an when Disposed. /// - public sealed class DisposableAction : IDisposable { + public sealed class DisposableAction : IDisposable + { private readonly Action _exitAction; private bool _disposed; @@ -12,14 +14,16 @@ public sealed class DisposableAction : IDisposable { /// Initializes a new instance of the class. /// /// The exit action. - public DisposableAction(Action exitAction) { + public DisposableAction(Action exitAction) + { _exitAction = exitAction; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - void IDisposable.Dispose() { + void IDisposable.Dispose() + { if (_disposed) return; @@ -27,4 +31,4 @@ void IDisposable.Dispose() { _disposed = true; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/EmptyDisposable.cs b/src/Foundatio/Utility/EmptyDisposable.cs index 720d14105..727a19755 100644 --- a/src/Foundatio/Utility/EmptyDisposable.cs +++ b/src/Foundatio/Utility/EmptyDisposable.cs @@ -2,12 +2,15 @@ using System.Threading.Tasks; using Foundatio.Lock; -namespace Foundatio.Utility { - public class EmptyDisposable : IDisposable { - public void Dispose() {} +namespace Foundatio.Utility +{ + public class EmptyDisposable : IDisposable + { + public void Dispose() { } } - public class EmptyLock : ILock { + public class EmptyLock : ILock + { public string LockId => String.Empty; public string Resource => String.Empty; @@ -18,20 +21,24 @@ public class EmptyLock : ILock { public int RenewalCount => 0; - public ValueTask DisposeAsync() { + public ValueTask DisposeAsync() + { return new ValueTask(); } - public Task RenewAsync(TimeSpan? lockExtension = null) { + public Task RenewAsync(TimeSpan? lockExtension = null) + { return Task.CompletedTask; } - public Task ReleaseAsync() { + public Task ReleaseAsync() + { return Task.CompletedTask; } } - public static class Disposable { + public static class Disposable + { public static IDisposable Empty = new EmptyDisposable(); public static ILock EmptyLock = new EmptyLock(); } diff --git a/src/Foundatio/Utility/FoundatioDiagnostics.cs b/src/Foundatio/Utility/FoundatioDiagnostics.cs index 7c1425f00..b8389bc76 100644 --- a/src/Foundatio/Utility/FoundatioDiagnostics.cs +++ b/src/Foundatio/Utility/FoundatioDiagnostics.cs @@ -5,9 +5,10 @@ namespace Foundatio; -internal static class FoundatioDiagnostics { +internal static class FoundatioDiagnostics +{ internal static readonly AssemblyName AssemblyName = typeof(FoundatioDiagnostics).Assembly.GetName(); internal static readonly string AssemblyVersion = typeof(FoundatioDiagnostics).Assembly.GetCustomAttribute()?.InformationalVersion ?? AssemblyName.Version.ToString(); internal static readonly ActivitySource ActivitySource = new(AssemblyName.Name, AssemblyVersion); internal static readonly Meter Meter = new("Foundatio", AssemblyVersion); -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/IAsyncDisposable.cs b/src/Foundatio/Utility/IAsyncDisposable.cs index 71c14e5fd..f5971b300 100644 --- a/src/Foundatio/Utility/IAsyncDisposable.cs +++ b/src/Foundatio/Utility/IAsyncDisposable.cs @@ -2,20 +2,27 @@ using System.Runtime.ExceptionServices; using System.Threading.Tasks; -namespace Foundatio.Utility { - public static class Async { +namespace Foundatio.Utility +{ + public static class Async + { public static async Task Using(TResource resource, Func> body) - where TResource : IAsyncDisposable { + where TResource : IAsyncDisposable + { Exception exception = null; var result = default(TReturn); - try { + try + { result = await body(resource).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { exception = ex; } await resource.DisposeAsync().AnyContext(); - if (exception != null) { + if (exception != null) + { var info = ExceptionDispatchInfo.Capture(exception); info.Throw(); } @@ -23,25 +30,31 @@ public static async Task Using(TResource resource, return result; } - public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable { + public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable + { return Using(resource, r => body()); } - public static Task Using(TResource resource, Action body) where TResource : IAsyncDisposable { - return Using(resource, r => { + public static Task Using(TResource resource, Action body) where TResource : IAsyncDisposable + { + return Using(resource, r => + { body(); return Task.CompletedTask; }); } - public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable { - return Using(resource, async r => { + public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable + { + return Using(resource, async r => + { await body(resource).AnyContext(); return Task.CompletedTask; }); } - public static Task Using(TResource resource, Func> body) where TResource : IAsyncDisposable { + public static Task Using(TResource resource, Func> body) where TResource : IAsyncDisposable + { return Using(resource, r => body()); } } diff --git a/src/Foundatio/Utility/IAsyncLifetime.cs b/src/Foundatio/Utility/IAsyncLifetime.cs index dce9921e2..e583b99f3 100644 --- a/src/Foundatio/Utility/IAsyncLifetime.cs +++ b/src/Foundatio/Utility/IAsyncLifetime.cs @@ -2,8 +2,10 @@ using System.Runtime.ExceptionServices; using System.Threading.Tasks; -namespace Foundatio.Utility { - public interface IAsyncLifetime : IAsyncDisposable { +namespace Foundatio.Utility +{ + public interface IAsyncLifetime : IAsyncDisposable + { Task InitializeAsync(); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/IHaveLogger.cs b/src/Foundatio/Utility/IHaveLogger.cs index e5dda2d65..4ca17e0fc 100644 --- a/src/Foundatio/Utility/IHaveLogger.cs +++ b/src/Foundatio/Utility/IHaveLogger.cs @@ -1,13 +1,17 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Utility { - public interface IHaveLogger { +namespace Foundatio.Utility +{ + public interface IHaveLogger + { ILogger Logger { get; } } - public static class LoggerExtensions { - public static ILogger GetLogger(this object target) { + public static class LoggerExtensions + { + public static ILogger GetLogger(this object target) + { return target is IHaveLogger accessor ? accessor.Logger ?? NullLogger.Instance : NullLogger.Instance; } } diff --git a/src/Foundatio/Utility/InstrumentsValues.cs b/src/Foundatio/Utility/InstrumentsValues.cs index 758a95f06..5866f5261 100644 --- a/src/Foundatio/Utility/InstrumentsValues.cs +++ b/src/Foundatio/Utility/InstrumentsValues.cs @@ -5,13 +5,15 @@ namespace Foundatio.Utility; public class InstrumentsValues where T1 : struct where T2 : struct - where T3 : struct { + where T3 : struct +{ private T1? _value1; private T2? _value2; private T3? _value3; private Func<(T1, T2, T3)> UpdateValues { get; set; } - public InstrumentsValues(Func<(T1, T2, T3)> readValues) { + public InstrumentsValues(Func<(T1, T2, T3)> readValues) + { _value1 = null; _value2 = null; _value3 = null; @@ -19,8 +21,10 @@ public InstrumentsValues(Func<(T1, T2, T3)> readValues) { UpdateValues = readValues; } - public T1 GetValue1() { - if (!_value1.HasValue) { + public T1 GetValue1() + { + if (!_value1.HasValue) + { (_value1, _value2, _value3) = UpdateValues(); } @@ -29,8 +33,10 @@ public T1 GetValue1() { return value; } - public T2 GetValue2() { - if (!_value2.HasValue) { + public T2 GetValue2() + { + if (!_value2.HasValue) + { (_value1, _value2, _value3) = UpdateValues(); } @@ -39,8 +45,10 @@ public T2 GetValue2() { return value; } - public T3 GetValue3() { - if (!_value3.HasValue) { + public T3 GetValue3() + { + if (!_value3.HasValue) + { (_value1, _value2, _value3) = UpdateValues(); } diff --git a/src/Foundatio/Utility/MaintenanceBase.cs b/src/Foundatio/Utility/MaintenanceBase.cs index 44e6f8237..520c2e39a 100644 --- a/src/Foundatio/Utility/MaintenanceBase.cs +++ b/src/Foundatio/Utility/MaintenanceBase.cs @@ -3,30 +3,37 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Utility { - public class MaintenanceBase : IDisposable { +namespace Foundatio.Utility +{ + public class MaintenanceBase : IDisposable + { private ScheduledTimer _maintenanceTimer; private readonly ILoggerFactory _loggerFactory; protected readonly ILogger _logger; - public MaintenanceBase(ILoggerFactory loggerFactory) { + public MaintenanceBase(ILoggerFactory loggerFactory) + { _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; _logger = _loggerFactory.CreateLogger(GetType()); } - protected void InitializeMaintenance(TimeSpan? dueTime = null, TimeSpan? intervalTime = null) { + protected void InitializeMaintenance(TimeSpan? dueTime = null, TimeSpan? intervalTime = null) + { _maintenanceTimer = new ScheduledTimer(DoMaintenanceAsync, dueTime, intervalTime, _loggerFactory); } - protected void ScheduleNextMaintenance(DateTime utcDate) { + protected void ScheduleNextMaintenance(DateTime utcDate) + { _maintenanceTimer.ScheduleNext(utcDate); } - protected virtual Task DoMaintenanceAsync() { + protected virtual Task DoMaintenanceAsync() + { return Task.FromResult(DateTime.MaxValue); } - public virtual void Dispose() { + public virtual void Dispose() + { _maintenanceTimer?.Dispose(); } } diff --git a/src/Foundatio/Utility/OptionsBuilder.cs b/src/Foundatio/Utility/OptionsBuilder.cs index 3d4399d54..20cae9fe5 100644 --- a/src/Foundatio/Utility/OptionsBuilder.cs +++ b/src/Foundatio/Utility/OptionsBuilder.cs @@ -1,28 +1,35 @@ using System; -namespace Foundatio { - public interface IOptionsBuilder { +namespace Foundatio +{ + public interface IOptionsBuilder + { object Target { get; } } - public interface IOptionsBuilder : IOptionsBuilder { + public interface IOptionsBuilder : IOptionsBuilder + { T Build(); } - public static class OptionsBuilderExtensions { - public static T Target(this IOptionsBuilder builder) { + public static class OptionsBuilderExtensions + { + public static T Target(this IOptionsBuilder builder) + { return (T)builder.Target; } } - public class OptionsBuilder : IOptionsBuilder where T : class, new() { + public class OptionsBuilder : IOptionsBuilder where T : class, new() + { public T Target { get; } = new T(); object IOptionsBuilder.Target => Target; - public virtual T Build() { + public virtual T Build() + { return Target; } } - public delegate TBuilder Builder(TBuilder builder) where TBuilder: class, IOptionsBuilder, new(); + public delegate TBuilder Builder(TBuilder builder) where TBuilder : class, IOptionsBuilder, new(); } diff --git a/src/Foundatio/Utility/PathHelper.cs b/src/Foundatio/Utility/PathHelper.cs index 02db1c66f..479fa5fd7 100644 --- a/src/Foundatio/Utility/PathHelper.cs +++ b/src/Foundatio/Utility/PathHelper.cs @@ -1,11 +1,14 @@ using System; using System.IO; -namespace Foundatio.Utility { - public static class PathHelper { +namespace Foundatio.Utility +{ + public static class PathHelper + { private const string DATA_DIRECTORY = "|DataDirectory|"; - public static string ExpandPath(string path) { + public static string ExpandPath(string path) + { if (String.IsNullOrEmpty(path)) return path; @@ -32,19 +35,23 @@ public static string ExpandPath(string path) { return fullPath; } - public static string GetDataDirectory() { - try { + public static string GetDataDirectory() + { + try + { string dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string; if (String.IsNullOrEmpty(dataDirectory)) dataDirectory = AppContext.BaseDirectory; if (!String.IsNullOrEmpty(dataDirectory)) return Path.GetFullPath(dataDirectory); - } catch (Exception) { + } + catch (Exception) + { return null; } return null; } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/Run.cs b/src/Foundatio/Utility/Run.cs index 0ac382d6e..31806b19f 100644 --- a/src/Foundatio/Utility/Run.cs +++ b/src/Foundatio/Utility/Run.cs @@ -4,13 +4,17 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Foundatio.Utility { - public static class Run { - public static Task DelayedAsync(TimeSpan delay, Func action, CancellationToken cancellationToken = default) { +namespace Foundatio.Utility +{ + public static class Run + { + public static Task DelayedAsync(TimeSpan delay, Func action, CancellationToken cancellationToken = default) + { if (cancellationToken.IsCancellationRequested) return Task.CompletedTask; - return Task.Run(async () => { + return Task.Run(async () => + { if (delay.Ticks > 0) await SystemClock.SleepAsync(delay, cancellationToken).AnyContext(); @@ -21,18 +25,22 @@ public static Task DelayedAsync(TimeSpan delay, Func action, CancellationT }, cancellationToken); } - public static Task InParallelAsync(int iterations, Func work) { + public static Task InParallelAsync(int iterations, Func work) + { return Task.WhenAll(Enumerable.Range(1, iterations).Select(i => Task.Run(() => work(i)))); } - public static Task WithRetriesAsync(Func action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) { - return WithRetriesAsync(async () => { + public static Task WithRetriesAsync(Func action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) + { + return WithRetriesAsync(async () => + { await action().AnyContext(); return null; }, maxAttempts, retryInterval, cancellationToken, logger); } - public static async Task WithRetriesAsync(Func> action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) { + public static async Task WithRetriesAsync(Func> action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) + { if (action == null) throw new ArgumentNullException(nameof(action)); @@ -42,13 +50,17 @@ public static async Task WithRetriesAsync(Func> action, int maxAtt if (retryInterval != null) currentBackoffTime = (int)retryInterval.Value.TotalMilliseconds; - do { + do + { if (attempts > 1 && logger != null && logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Retrying {Attempts} attempt after {Delay:g}...", attempts.ToOrdinal(), SystemClock.UtcNow.Subtract(startTime)); - try { + try + { return await action().AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (attempts >= maxAttempts) throw; @@ -68,4 +80,4 @@ public static async Task WithRetriesAsync(Func> action, int maxAtt private static readonly int[] _defaultBackoffIntervals = new int[] { 100, 1000, 2000, 2000, 5000, 5000, 10000, 30000, 60000 }; } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/ScheduledTimer.cs b/src/Foundatio/Utility/ScheduledTimer.cs index b972b31e3..5308e928f 100644 --- a/src/Foundatio/Utility/ScheduledTimer.cs +++ b/src/Foundatio/Utility/ScheduledTimer.cs @@ -6,8 +6,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Foundatio.Utility { - public class ScheduledTimer : IDisposable { +namespace Foundatio.Utility +{ + public class ScheduledTimer : IDisposable + { private DateTime _next = DateTime.MaxValue; private DateTime _last = DateTime.MinValue; private readonly Timer _timer; @@ -18,7 +20,8 @@ public class ScheduledTimer : IDisposable { private bool _isRunning = false; private bool _shouldRunAgainImmediately = false; - public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = null, TimeSpan? minimumIntervalTime = null, ILoggerFactory loggerFactory = null) { + public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = null, TimeSpan? minimumIntervalTime = null, ILoggerFactory loggerFactory = null) + { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _timerCallback = timerCallback ?? throw new ArgumentNullException(nameof(timerCallback)); _minimumInterval = minimumIntervalTime ?? TimeSpan.Zero; @@ -27,41 +30,48 @@ public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = n _timer = new Timer(s => Task.Run(RunCallbackAsync), null, dueTimeMs, Timeout.Infinite); } - public void ScheduleNext(DateTime? utcDate = null) { + public void ScheduleNext(DateTime? utcDate = null) + { var utcNow = SystemClock.UtcNow; if (!utcDate.HasValue || utcDate.Value < utcNow) utcDate = utcNow; bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (isTraceLogLevelEnabled) _logger.LogTrace("ScheduleNext called: value={NextRun:O}", utcDate.Value); - if (utcDate == DateTime.MaxValue) { + if (utcDate == DateTime.MaxValue) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Ignoring MaxValue"); return; } // already have an earlier scheduled time - if (_next > utcNow && utcDate > _next) { + if (_next > utcNow && utcDate > _next) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Ignoring because already scheduled for earlier time: {PreviousTicks} Next: {NextTicks}", utcDate.Value.Ticks, _next.Ticks); return; } // ignore duplicate times - if (_next == utcDate) { + if (_next == utcDate) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Ignoring because already scheduled for same time"); return; } - using (_lock.Lock()) { + using (_lock.Lock()) + { // already have an earlier scheduled time - if (_next > utcNow && utcDate > _next) { + if (_next > utcNow && utcDate > _next) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Ignoring because already scheduled for earlier time: {PreviousTicks} Next: {NextTicks}", utcDate.Value.Ticks, _next.Ticks); return; } // ignore duplicate times - if (_next == utcDate) { + if (_next == utcDate) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Ignoring because already scheduled for same time"); return; } @@ -79,9 +89,11 @@ public void ScheduleNext(DateTime? utcDate = null) { } } - private async Task RunCallbackAsync() { + private async Task RunCallbackAsync() + { bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (_isRunning) { + if (_isRunning) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Exiting run callback because its already running, will run again immediately"); @@ -90,8 +102,10 @@ private async Task RunCallbackAsync() { } if (isTraceLogLevelEnabled) _logger.LogTrace("Starting RunCallbackAsync"); - using (await _lock.LockAsync().AnyContext()) { - if (_isRunning) { + using (await _lock.LockAsync().AnyContext()) + { + if (_isRunning) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Exiting run callback because its already running, will run again immediately"); @@ -102,24 +116,31 @@ private async Task RunCallbackAsync() { _last = SystemClock.UtcNow; } - try { + try + { _isRunning = true; DateTime? next = null; var sw = Stopwatch.StartNew(); - try { + try + { next = await _timerCallback().AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error running scheduled timer callback: {Message}", ex.Message); _shouldRunAgainImmediately = true; - } finally { + } + finally + { sw.Stop(); if (isTraceLogLevelEnabled) _logger.LogTrace("Callback took: {Elapsed:g}", sw.Elapsed); } - if (_minimumInterval > TimeSpan.Zero) { + if (_minimumInterval > TimeSpan.Zero) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Sleeping for minimum interval: {Interval:g}", _minimumInterval); await SystemClock.SleepAsync(_minimumInterval).AnyContext(); if (isTraceLogLevelEnabled) _logger.LogTrace("Finished sleeping"); @@ -130,10 +151,14 @@ private async Task RunCallbackAsync() { ScheduleNext(nextRun); else if (next.HasValue) ScheduleNext(next.Value); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error running schedule next callback: {Message}", ex.Message); - } finally { + } + finally + { _isRunning = false; _shouldRunAgainImmediately = false; } @@ -141,8 +166,9 @@ private async Task RunCallbackAsync() { if (isTraceLogLevelEnabled) _logger.LogTrace("Finished RunCallbackAsync"); } - public void Dispose() { + public void Dispose() + { _timer?.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/SharedOptions.cs b/src/Foundatio/Utility/SharedOptions.cs index 37b564bd1..f51b4d6af 100644 --- a/src/Foundatio/Utility/SharedOptions.cs +++ b/src/Foundatio/Utility/SharedOptions.cs @@ -2,21 +2,26 @@ using Foundatio.Serializer; using Microsoft.Extensions.Logging; -namespace Foundatio { - public class SharedOptions { +namespace Foundatio +{ + public class SharedOptions + { public ISerializer Serializer { get; set; } public ILoggerFactory LoggerFactory { get; set; } } public class SharedOptionsBuilder : OptionsBuilder where TOption : SharedOptions, new() - where TBuilder : SharedOptionsBuilder { - public TBuilder Serializer(ISerializer serializer) { + where TBuilder : SharedOptionsBuilder + { + public TBuilder Serializer(ISerializer serializer) + { Target.Serializer = serializer; return (TBuilder)this; } - public TBuilder LoggerFactory(ILoggerFactory loggerFactory) { + public TBuilder LoggerFactory(ILoggerFactory loggerFactory) + { Target.LoggerFactory = loggerFactory; return (TBuilder)this; } diff --git a/src/Foundatio/Utility/SystemClock.cs b/src/Foundatio/Utility/SystemClock.cs index 9b92ddc61..844a4b07d 100644 --- a/src/Foundatio/Utility/SystemClock.cs +++ b/src/Foundatio/Utility/SystemClock.cs @@ -2,8 +2,10 @@ using System.Threading; using System.Threading.Tasks; -namespace Foundatio.Utility { - public interface ISystemClock { +namespace Foundatio.Utility +{ + public interface ISystemClock + { DateTime Now(); DateTime UtcNow(); DateTimeOffset OffsetNow(); @@ -13,7 +15,8 @@ public interface ISystemClock { TimeSpan TimeZoneOffset(); } - public class RealSystemClock : ISystemClock { + public class RealSystemClock : ISystemClock + { public static readonly RealSystemClock Instance = new(); public DateTime Now() => DateTime.Now; @@ -25,16 +28,18 @@ public class RealSystemClock : ISystemClock { public TimeSpan TimeZoneOffset() => DateTimeOffset.Now.Offset; } - internal class TestSystemClockImpl : ISystemClock, IDisposable { + internal class TestSystemClockImpl : ISystemClock, IDisposable + { private DateTime? _fixedUtc = null; private TimeSpan _offset = TimeSpan.Zero; private TimeSpan _timeZoneOffset = DateTimeOffset.Now.Offset; private bool _fakeSleep = false; private ISystemClock _originalClock; - - public TestSystemClockImpl() {} - public TestSystemClockImpl(ISystemClock originalTime) { + public TestSystemClockImpl() { } + + public TestSystemClockImpl(ISystemClock originalTime) + { _originalClock = originalTime; } @@ -49,9 +54,11 @@ public TestSystemClockImpl(ISystemClock originalTime) { public void SubtractTime(TimeSpan amount) => _offset = _offset.Subtract(amount); public void UseFakeSleep() => _fakeSleep = true; public void UseRealSleep() => _fakeSleep = false; - - public void Sleep(int milliseconds) { - if (!_fakeSleep) { + + public void Sleep(int milliseconds) + { + if (!_fakeSleep) + { Thread.Sleep(milliseconds); return; } @@ -60,7 +67,8 @@ public void Sleep(int milliseconds) { Thread.Sleep(1); } - public Task SleepAsync(int milliseconds, CancellationToken ct) { + public Task SleepAsync(int milliseconds, CancellationToken ct) + { if (!_fakeSleep) return Task.Delay(milliseconds, ct); @@ -68,54 +76,70 @@ public Task SleepAsync(int milliseconds, CancellationToken ct) { return Task.CompletedTask; } - public void Freeze() { + public void Freeze() + { SetFrozenTime(Now()); } - public void Unfreeze() { + public void Unfreeze() + { SetTime(Now()); } - public void SetFrozenTime(DateTime time) { + public void SetFrozenTime(DateTime time) + { SetTime(time, true); } - public void SetTime(DateTime time, bool freeze = false) { + public void SetTime(DateTime time, bool freeze = false) + { var now = DateTime.Now; - if (freeze) { + if (freeze) + { if (time.Kind == DateTimeKind.Unspecified) time = time.ToUniversalTime(); - if (time.Kind == DateTimeKind.Utc) { + if (time.Kind == DateTimeKind.Utc) + { _fixedUtc = time; - } else if (time.Kind == DateTimeKind.Local) { + } + else if (time.Kind == DateTimeKind.Local) + { _fixedUtc = new DateTime(time.Ticks - TimeZoneOffset().Ticks, DateTimeKind.Utc); } - } else { + } + else + { _fixedUtc = null; if (time.Kind == DateTimeKind.Unspecified) time = time.ToUniversalTime(); - if (time.Kind == DateTimeKind.Utc) { + if (time.Kind == DateTimeKind.Utc) + { _offset = now.ToUniversalTime().Subtract(time); - } else if (time.Kind == DateTimeKind.Local) { + } + else if (time.Kind == DateTimeKind.Local) + { _offset = now.Subtract(time); } } } - - public void Dispose() { + + public void Dispose() + { if (_originalClock == null) return; - + var originalClock = Interlocked.Exchange(ref _originalClock, null); if (originalClock != null) SystemClock.Instance = originalClock; } - - public static TestSystemClockImpl Instance { - get { + + public static TestSystemClockImpl Instance + { + get + { if (!(SystemClock.Instance is TestSystemClockImpl testClock)) throw new ArgumentException("You must first install TestSystemClock using TestSystemClock.Install"); @@ -124,7 +148,8 @@ public static TestSystemClockImpl Instance { } } - public class TestSystemClock { + public class TestSystemClock + { public static void SetTimeZoneOffset(TimeSpan offset) => TestSystemClockImpl.Instance.SetTimeZoneOffset(offset); public static void AddTime(TimeSpan amount) => TestSystemClockImpl.Instance.AddTime(amount); public static void SubtractTime(TimeSpan amount) => TestSystemClockImpl.Instance.SubtractTime(amount); @@ -135,23 +160,27 @@ public class TestSystemClock { public static void SetFrozenTime(DateTime time) => TestSystemClockImpl.Instance.SetFrozenTime(time); public static void SetTime(DateTime time, bool freeze = false) => TestSystemClockImpl.Instance.SetTime(time, freeze); - public static IDisposable Install() { + public static IDisposable Install() + { var testClock = new TestSystemClockImpl(SystemClock.Instance); SystemClock.Instance = testClock; - + return testClock; } } - - public static class SystemClock { + + public static class SystemClock + { private static AsyncLocal _instance; - - public static ISystemClock Instance { + + public static ISystemClock Instance + { get => _instance?.Value ?? RealSystemClock.Instance; - set { + set + { if (_instance == null) _instance = new AsyncLocal(); - + _instance.Value = value; } } @@ -164,39 +193,44 @@ public static ISystemClock Instance { public static void Sleep(int milliseconds) => Instance.Sleep(milliseconds); public static Task SleepAsync(int milliseconds, CancellationToken cancellationToken = default) => Instance.SleepAsync(milliseconds, cancellationToken); - + #region Extensions - + public static void Sleep(TimeSpan delay) => Instance.Sleep(delay); - + public static Task SleepAsync(TimeSpan delay, CancellationToken cancellationToken = default) => Instance.SleepAsync(delay, cancellationToken); - - public static Task SleepSafeAsync(int milliseconds, CancellationToken cancellationToken = default) { + + public static Task SleepSafeAsync(int milliseconds, CancellationToken cancellationToken = default) + { return Instance.SleepSafeAsync(milliseconds, cancellationToken); } - + public static Task SleepSafeAsync(TimeSpan delay, CancellationToken cancellationToken = default) => Instance.SleepSafeAsync(delay, cancellationToken); - + #endregion } - - public static class TimeExtensions { + + public static class TimeExtensions + { public static void Sleep(this ISystemClock time, TimeSpan delay) => time.Sleep((int)delay.TotalMilliseconds); - + public static Task SleepAsync(this ISystemClock time, TimeSpan delay, CancellationToken cancellationToken = default) => time.SleepAsync((int)delay.TotalMilliseconds, cancellationToken); - - public static async Task SleepSafeAsync(this ISystemClock time, int milliseconds, CancellationToken cancellationToken = default) { - try { + + public static async Task SleepSafeAsync(this ISystemClock time, int milliseconds, CancellationToken cancellationToken = default) + { + try + { await time.SleepAsync(milliseconds, cancellationToken).AnyContext(); - } catch (OperationCanceledException) {} + } + catch (OperationCanceledException) { } } - + public static Task SleepSafeAsync(this ISystemClock time, TimeSpan delay, CancellationToken cancellationToken = default) => time.SleepSafeAsync((int)delay.TotalMilliseconds, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Foundatio/Utility/TimeUnit.cs b/src/Foundatio/Utility/TimeUnit.cs index 58f196069..a607bf2f9 100644 --- a/src/Foundatio/Utility/TimeUnit.cs +++ b/src/Foundatio/Utility/TimeUnit.cs @@ -1,8 +1,11 @@ using System; -namespace Foundatio.Utility { - public static class TimeUnit { - public static TimeSpan Parse(string value) { +namespace Foundatio.Utility +{ + public static class TimeUnit + { + public static TimeSpan Parse(string value) + { if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); @@ -13,7 +16,8 @@ public static TimeSpan Parse(string value) { throw new ArgumentException($"Unable to parse value '{value}' as a valid time value"); } - public static bool TryParse(string value, out TimeSpan? time) { + public static bool TryParse(string value, out TimeSpan? time) + { time = null; if (String.IsNullOrEmpty(value)) return false; @@ -22,40 +26,48 @@ public static bool TryParse(string value, out TimeSpan? time) { return time.HasValue; } - private static TimeSpan? ParseTime(string value) { + private static TimeSpan? ParseTime(string value) + { // compare using the original value as uppercase M could mean months. var normalized = value.ToLowerInvariant().Trim(); - if (value.EndsWith("m")) { + if (value.EndsWith("m")) + { int minutes = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); return new TimeSpan(0, minutes, 0); } - if (normalized.EndsWith("h")) { + if (normalized.EndsWith("h")) + { int hours = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); return new TimeSpan(hours, 0, 0); } - if (normalized.EndsWith("d")) { + if (normalized.EndsWith("d")) + { int days = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); return new TimeSpan(days, 0, 0, 0); } - if (normalized.EndsWith("nanos")) { + if (normalized.EndsWith("nanos")) + { long nanoseconds = Int64.Parse(normalized.Substring(0, normalized.Length - 5)); return new TimeSpan((int)Math.Round(nanoseconds / 100d)); } - if (normalized.EndsWith("micros")) { + if (normalized.EndsWith("micros")) + { long microseconds = Int64.Parse(normalized.Substring(0, normalized.Length - 6)); return new TimeSpan(microseconds * 10); } - if (normalized.EndsWith("ms")) { + if (normalized.EndsWith("ms")) + { int milliseconds = Int32.Parse(normalized.Substring(0, normalized.Length - 2)); return new TimeSpan(0, 0, 0, 0, milliseconds); } - if (normalized.EndsWith("s")) { + if (normalized.EndsWith("s")) + { int seconds = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); return new TimeSpan(0, 0, seconds); } diff --git a/src/Foundatio/Utility/TypeHelper.cs b/src/Foundatio/Utility/TypeHelper.cs index e37def50d..2261b41fc 100644 --- a/src/Foundatio/Utility/TypeHelper.cs +++ b/src/Foundatio/Utility/TypeHelper.cs @@ -5,8 +5,10 @@ using System.Reflection; using Microsoft.Extensions.Logging; -namespace Foundatio.Utility { - public static class TypeHelper { +namespace Foundatio.Utility +{ + public static class TypeHelper + { public static readonly Type ObjectType = typeof(object); public static readonly Type StringType = typeof(string); public static readonly Type CharType = typeof(char); @@ -28,18 +30,21 @@ public static class TypeHelper { public static readonly Type UInt64Type = typeof(ulong); public static readonly Type DoubleType = typeof(double); - public static Type ResolveType(string fullTypeName, Type expectedBase = null, ILogger logger = null) { + public static Type ResolveType(string fullTypeName, Type expectedBase = null, ILogger logger = null) + { if (String.IsNullOrEmpty(fullTypeName)) return null; var type = Type.GetType(fullTypeName); - if (type == null) { + if (type == null) + { if (logger != null && logger.IsEnabled(LogLevel.Error)) logger.LogError("Unable to resolve type: {TypeFullName}.", fullTypeName); return null; } - if (expectedBase != null && !expectedBase.IsAssignableFrom(type)) { + if (expectedBase != null && !expectedBase.IsAssignableFrom(type)) + { if (logger != null && logger.IsEnabled(LogLevel.Error)) logger.LogError("Type {TypeFullName} must be assignable to type: {ExpectedFullName}.", fullTypeName, expectedBase.FullName); return null; @@ -66,9 +71,11 @@ public static Type ResolveType(string fullTypeName, Type expectedBase = null, IL { UInt64Type, "ulong" } }; - public static string GetTypeDisplayName(Type type) { + public static string GetTypeDisplayName(Type type) + { string fullName = null; - if (type.GetTypeInfo().IsGenericType) { + if (type.GetTypeInfo().IsGenericType) + { fullName = type.GetGenericTypeDefinition().FullName; // Nested types (public or private) have a '+' in their full name @@ -78,11 +85,13 @@ public static string GetTypeDisplayName(Type type) { // Examples: // ConsoleApp.Program+Foo`1+Bar // ConsoleApp.Program+Foo`1+Bar`1 - for (int i = 0; i < parts.Length; i++) { + for (int i = 0; i < parts.Length; i++) + { string partName = parts[i]; int backTickIndex = partName.IndexOf('`'); - if (backTickIndex >= 0) { + if (backTickIndex >= 0) + { // Since '.' is typically used to filter log messages in a hierarchy kind of scenario, // do not include any generic type information as part of the name. // Example: @@ -107,16 +116,20 @@ public static string GetTypeDisplayName(Type type) { return fullName; } - public static IEnumerable GetDerivedTypes(IEnumerable assemblies = null) { + public static IEnumerable GetDerivedTypes(IEnumerable assemblies = null) + { if (assemblies == null) assemblies = AppDomain.CurrentDomain.GetAssemblies(); var types = new List(); - foreach (var assembly in assemblies) { - try { + foreach (var assembly in assemblies) + { + try + { types.AddRange(from type in assembly.GetTypes() where type.IsClass && !type.IsNotPublic && !type.IsAbstract && typeof(TAction).IsAssignableFrom(type) select type); } - catch (ReflectionTypeLoadException ex) { + catch (ReflectionTypeLoadException ex) + { string loaderMessages = String.Join(", ", ex.LoaderExceptions.ToList().Select(le => le.Message)); Trace.TraceInformation("Unable to search types from assembly \"{0}\" for plugins of type \"{1}\": {2}", assembly.FullName, typeof(TAction).Name, loaderMessages); } diff --git a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs index 994da153f..d992d0e5d 100644 --- a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs +++ b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs @@ -7,135 +7,163 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Caching { - public class InMemoryCacheClientTests : CacheClientTestsBase { - public InMemoryCacheClientTests(ITestOutputHelper output) : base(output) {} - - protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { +namespace Foundatio.Tests.Caching +{ + public class InMemoryCacheClientTests : CacheClientTestsBase + { + public InMemoryCacheClientTests(ITestOutputHelper output) : base(output) { } + + protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return new InMemoryCacheClient(o => o.LoggerFactory(Log).CloneValues(true).ShouldThrowOnSerializationError(shouldThrowOnSerializationError)); } [Fact] - public override Task CanGetAllAsync() { + public override Task CanGetAllAsync() + { return base.CanGetAllAsync(); } [Fact] - public override Task CanGetAllWithOverlapAsync() { + public override Task CanGetAllWithOverlapAsync() + { return base.CanGetAllWithOverlapAsync(); } [Fact] - public override Task CanSetAsync() { + public override Task CanSetAsync() + { return base.CanSetAsync(); } [Fact] - public override Task CanSetAndGetValueAsync() { + public override Task CanSetAndGetValueAsync() + { return base.CanSetAndGetValueAsync(); } [Fact] - public override Task CanAddAsync() { + public override Task CanAddAsync() + { return base.CanAddAsync(); } [Fact] - public override Task CanAddConcurrentlyAsync() { + public override Task CanAddConcurrentlyAsync() + { return base.CanAddConcurrentlyAsync(); } [Fact] - public override Task CanGetAsync() { + public override Task CanGetAsync() + { return base.CanGetAsync(); } [Fact] - public override Task CanTryGetAsync() { + public override Task CanTryGetAsync() + { return base.CanTryGetAsync(); } [Fact] - public override Task CanUseScopedCachesAsync() { + public override Task CanUseScopedCachesAsync() + { return base.CanUseScopedCachesAsync(); } [Fact] - public override Task CanSetAndGetObjectAsync() { + public override Task CanSetAndGetObjectAsync() + { return base.CanSetAndGetObjectAsync(); } [Fact] - public override Task CanRemoveByPrefixAsync() { + public override Task CanRemoveByPrefixAsync() + { return base.CanRemoveByPrefixAsync(); } [Theory] [InlineData(50)] [InlineData(500)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) { + public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + { return base.CanRemoveByPrefixMultipleEntriesAsync(count); } [Fact] - public override Task CanSetExpirationAsync() { + public override Task CanSetExpirationAsync() + { return base.CanSetExpirationAsync(); } [Fact] - public override Task CanSetMinMaxExpirationAsync() { + public override Task CanSetMinMaxExpirationAsync() + { return base.CanSetMinMaxExpirationAsync(); } [Fact] - public override Task CanIncrementAsync() { + public override Task CanIncrementAsync() + { return base.CanIncrementAsync(); } [Fact] - public override Task CanIncrementAndExpireAsync() { + public override Task CanIncrementAndExpireAsync() + { return base.CanIncrementAndExpireAsync(); } [Fact] - public override Task CanReplaceIfEqual() { + public override Task CanReplaceIfEqual() + { return base.CanReplaceIfEqual(); } [Fact] - public override Task CanRemoveIfEqual() { + public override Task CanRemoveIfEqual() + { return base.CanRemoveIfEqual(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() { + public override Task CanGetAndSetDateTimeAsync() + { return base.CanGetAndSetDateTimeAsync(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() { + public override Task CanRoundTripLargeNumbersAsync() + { return base.CanRoundTripLargeNumbersAsync(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() { + public override Task CanRoundTripLargeNumbersWithExpirationAsync() + { return base.CanRoundTripLargeNumbersWithExpirationAsync(); } [Fact] - public override Task CanManageListsAsync() { + public override Task CanManageListsAsync() + { return base.CanManageListsAsync(); } [Fact] - public async Task CanSetMaxItems() { + public async Task CanSetMaxItems() + { Log.MinimumLevel = LogLevel.Trace; // run in tight loop so that the code is warmed up and we can catch timing issues - for (int x = 0; x < 5; x++) { + for (int x = 0; x < 5; x++) + { var cache = new InMemoryCacheClient(o => o.MaxItems(10).CloneValues(true)); - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); for (int i = 0; i < cache.MaxItems; i++) @@ -162,7 +190,8 @@ public async Task CanSetMaxItems() { } [Fact] - public async Task SetAllShouldExpire() { + public async Task SetAllShouldExpire() + { var client = GetCacheClient(); var expiry = TimeSpan.FromMilliseconds(50); @@ -172,4 +201,4 @@ public async Task SetAllShouldExpire() { Assert.False(await client.ExistsAsync("test")); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs b/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs index 7fc10ec99..1fc86637d 100644 --- a/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs +++ b/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs @@ -5,97 +5,118 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Caching { - public class InMemoryHybridCacheClientTests : HybridCacheClientTests { +namespace Foundatio.Tests.Caching +{ + public class InMemoryHybridCacheClientTests : HybridCacheClientTests + { public InMemoryHybridCacheClientTests(ITestOutputHelper output) : base(output) { } - protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { + protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return new InMemoryHybridCacheClient(_messageBus, Log, shouldThrowOnSerializationError); } [Fact] - public override Task CanSetAndGetValueAsync() { + public override Task CanSetAndGetValueAsync() + { return base.CanSetAndGetValueAsync(); } [Fact] - public override Task CanSetAndGetObjectAsync() { + public override Task CanSetAndGetObjectAsync() + { return base.CanSetAndGetObjectAsync(); } - + [Fact] - public override Task CanTryGetAsync() { + public override Task CanTryGetAsync() + { return base.CanTryGetAsync(); } [Fact] - public override Task CanRemoveByPrefixAsync() { + public override Task CanRemoveByPrefixAsync() + { return base.CanRemoveByPrefixAsync(); } [Theory] [InlineData(50)] [InlineData(500)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) { + public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + { return base.CanRemoveByPrefixMultipleEntriesAsync(count); } [Fact] - public override Task CanUseScopedCachesAsync() { + public override Task CanUseScopedCachesAsync() + { return base.CanUseScopedCachesAsync(); } [Fact] - public override Task CanSetExpirationAsync() { + public override Task CanSetExpirationAsync() + { return base.CanSetExpirationAsync(); } - + [Fact] - public override Task CanManageListsAsync() { + public override Task CanManageListsAsync() + { return base.CanManageListsAsync(); } [Fact(Skip = "Skip because cache invalidation loops on this with 2 in memory cache client instances")] - public override Task WillUseLocalCache() { + public override Task WillUseLocalCache() + { return base.WillUseLocalCache(); } [Fact(Skip = "Skip because cache invalidation loops on this with 2 in memory cache client instances")] - public override Task WillExpireRemoteItems() { + public override Task WillExpireRemoteItems() + { return base.WillExpireRemoteItems(); } [Fact(Skip = "Skip because cache invalidation loops on this with 2 in memory cache client instances")] - public override Task WillWorkWithSets() { + public override Task WillWorkWithSets() + { Log.MinimumLevel = LogLevel.Trace; return base.WillWorkWithSets(); } [Fact(Skip = "Performance Test")] - public override Task MeasureThroughputAsync() { + public override Task MeasureThroughputAsync() + { return base.MeasureThroughputAsync(); } [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerSimpleThroughputAsync() { + public override Task MeasureSerializerSimpleThroughputAsync() + { return base.MeasureSerializerSimpleThroughputAsync(); } [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerComplexThroughputAsync() { + public override Task MeasureSerializerComplexThroughputAsync() + { return base.MeasureSerializerComplexThroughputAsync(); } } - - public class InMemoryHybridCacheClient : HybridCacheClient { + + public class InMemoryHybridCacheClient : HybridCacheClient + { public InMemoryHybridCacheClient(IMessageBus messageBus, ILoggerFactory loggerFactory, bool shouldThrowOnSerializationError) - : base(new InMemoryCacheClient(o => o.LoggerFactory(loggerFactory).ShouldThrowOnSerializationError(shouldThrowOnSerializationError)), messageBus, new InMemoryCacheClientOptions { + : base(new InMemoryCacheClient(o => o.LoggerFactory(loggerFactory).ShouldThrowOnSerializationError(shouldThrowOnSerializationError)), messageBus, new InMemoryCacheClientOptions + { CloneValues = true, ShouldThrowOnSerializationError = shouldThrowOnSerializationError - }, loggerFactory) { + }, loggerFactory) + { } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _distributedCache.Dispose(); _messageBus.Dispose(); diff --git a/tests/Foundatio.Tests/Hosting/HostingTests.cs b/tests/Foundatio.Tests/Hosting/HostingTests.cs index be86971c7..1962ac288 100644 --- a/tests/Foundatio.Tests/Hosting/HostingTests.cs +++ b/tests/Foundatio.Tests/Hosting/HostingTests.cs @@ -14,117 +14,135 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Hosting { - public class HostingTests : TestWithLoggingBase { - public HostingTests(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Hosting +{ + public class HostingTests : TestWithLoggingBase + { + public HostingTests(ITestOutputHelper output) : base(output) { } [Fact] - public async Task WillRunSyncStartupAction() { + public async Task WillRunSyncStartupAction() + { var resetEvent = new AsyncManualResetEvent(false); var builder = new WebHostBuilder() - .ConfigureServices(s => { + .ConfigureServices(s => + { s.AddSingleton(Log); s.AddStartupAction("Hey", () => resetEvent.Set()); s.AddHealthChecks().AddCheckForStartupActions("Critical"); }) - .Configure(app => { + .Configure(app => + { app.UseReadyHealthChecks("Critical"); }); - + var server = new TestServer(builder); - + await server.WaitForReadyAsync(); await resetEvent.WaitAsync(); - + var client = server.CreateClient(); var response = await client.GetAsync("/ready"); - + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] - public async Task WillRunAsyncStartupAction() { + public async Task WillRunAsyncStartupAction() + { var resetEvent = new AsyncManualResetEvent(false); var builder = new WebHostBuilder() - .ConfigureServices(s => { + .ConfigureServices(s => + { s.AddSingleton(Log); - s.AddStartupAction("Hey", () => { + s.AddStartupAction("Hey", () => + { resetEvent.Set(); return Task.CompletedTask; }); s.AddHealthChecks().AddCheckForStartupActions("Critical"); }) - .Configure(app => { + .Configure(app => + { app.UseReadyHealthChecks("Critical"); }); - + var server = new TestServer(builder); - + await server.WaitForReadyAsync(); await resetEvent.WaitAsync(); - + var client = server.CreateClient(); var response = await client.GetAsync("/ready"); - + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] - public async Task WillRunClassStartupAction() { + public async Task WillRunClassStartupAction() + { var builder = new WebHostBuilder() - .ConfigureServices(s => { + .ConfigureServices(s => + { s.AddSingleton(Log); s.AddStartupAction("Hey"); s.AddHealthChecks().AddCheckForStartupActions("Critical"); }) - .Configure(app => { + .Configure(app => + { app.UseReadyHealthChecks("Critical"); }); - + var server = new TestServer(builder); - + await server.WaitForReadyAsync(); Assert.True(TestStartupAction.HasRun); - + var client = server.CreateClient(); var response = await client.GetAsync("/ready"); - + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - - + + [Fact] - public async Task WillStopWaitingWhenStartupActionFails() { + public async Task WillStopWaitingWhenStartupActionFails() + { var builder = new WebHostBuilder() .CaptureStartupErrors(true) - .ConfigureServices(s => { + .ConfigureServices(s => + { s.AddSingleton(Log); s.AddStartupAction("Boom", () => throw new ApplicationException("Boom")); s.AddHealthChecks().AddCheckForStartupActions("Critical"); }) - .Configure(app => { + .Configure(app => + { app.UseReadyHealthChecks("Critical"); }); - + var server = new TestServer(builder); var sw = Stopwatch.StartNew(); await Assert.ThrowsAsync(() => server.WaitForReadyAsync()); sw.Stop(); - + Assert.True(sw.Elapsed < TimeSpan.FromSeconds(2)); } [Fact] - public async Task WillHandleNoRegisteredStartupActions() { + public async Task WillHandleNoRegisteredStartupActions() + { var builder = new WebHostBuilder() .UseEnvironment(Environments.Development) .CaptureStartupErrors(true) - .ConfigureServices(s => { + .ConfigureServices(s => + { s.AddSingleton(Log); s.AddHealthChecks().AddCheckForStartupActions("Critical"); }) - .Configure(app => { + .Configure(app => + { app.UseReadyHealthChecks("Critical"); app.UseWaitForStartupActionsBeforeServingRequests(); }); @@ -140,12 +158,14 @@ public async Task WillHandleNoRegisteredStartupActions() { } - public class TestStartupAction : IStartupAction { + public class TestStartupAction : IStartupAction + { public static bool HasRun { get; private set; } - public Task RunAsync(CancellationToken shutdownToken = default) { + public Task RunAsync(CancellationToken shutdownToken = default) + { HasRun = true; return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Hosting/TestServerExtensions.cs b/tests/Foundatio.Tests/Hosting/TestServerExtensions.cs index 6d63466a5..5e7b0f9e6 100644 --- a/tests/Foundatio.Tests/Hosting/TestServerExtensions.cs +++ b/tests/Foundatio.Tests/Hosting/TestServerExtensions.cs @@ -4,15 +4,19 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; -namespace Foundatio.Tests.Hosting { - public static class TestServerExtensions { - public static async Task WaitForReadyAsync(this TestServer server, TimeSpan? maxWaitTime = null) { +namespace Foundatio.Tests.Hosting +{ + public static class TestServerExtensions + { + public static async Task WaitForReadyAsync(this TestServer server, TimeSpan? maxWaitTime = null) + { var startupContext = server.Host.Services.GetService(); maxWaitTime ??= TimeSpan.FromSeconds(5); - + var client = server.CreateClient(); var startTime = DateTime.Now; - do { + do + { if (startupContext != null && startupContext.IsStartupComplete && startupContext.Result.Success == false) throw new OperationCanceledException($"Startup action \"{startupContext.Result.FailedActionName}\" failed"); @@ -27,4 +31,4 @@ public static async Task WaitForReadyAsync(this TestServer server, TimeSpan? max } while (true); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Jobs/InMemoryJobQueueTests.cs b/tests/Foundatio.Tests/Jobs/InMemoryJobQueueTests.cs index ac2d49cd7..223e65ac9 100644 --- a/tests/Foundatio.Tests/Jobs/InMemoryJobQueueTests.cs +++ b/tests/Foundatio.Tests/Jobs/InMemoryJobQueueTests.cs @@ -8,34 +8,41 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Jobs { - public class InMemoryJobQueueTests : JobQueueTestsBase { - public InMemoryJobQueueTests(ITestOutputHelper output) : base(output) {} - - protected override IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay) { +namespace Foundatio.Tests.Jobs +{ + public class InMemoryJobQueueTests : JobQueueTestsBase + { + public InMemoryJobQueueTests(ITestOutputHelper output) : base(output) { } + + protected override IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay) + { return new InMemoryQueue(o => o.RetryDelay(retryDelay).Retries(retries).LoggerFactory(Log)); } [Fact] - public override Task CanRunMultipleQueueJobsAsync() { + public override Task CanRunMultipleQueueJobsAsync() + { return base.CanRunMultipleQueueJobsAsync(); } [RetryFact] - public override Task CanRunQueueJobWithLockFailAsync() { + public override Task CanRunQueueJobWithLockFailAsync() + { Log.SetLogLevel(LogLevel.Trace); return base.CanRunQueueJobWithLockFailAsync(); } [Fact] - public override Task CanRunQueueJobAsync() { + public override Task CanRunQueueJobAsync() + { return base.CanRunQueueJobAsync(); } [Fact] - public override Task ActivityWillFlowThroughQueueJobAsync() { + public override Task ActivityWillFlowThroughQueueJobAsync() + { return base.ActivityWillFlowThroughQueueJobAsync(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Jobs/JobTests.cs b/tests/Foundatio.Tests/Jobs/JobTests.cs index ec352e7c9..b94878032 100644 --- a/tests/Foundatio.Tests/Jobs/JobTests.cs +++ b/tests/Foundatio.Tests/Jobs/JobTests.cs @@ -6,20 +6,22 @@ using System.Threading.Tasks; using Foundatio.Caching; using Foundatio.Jobs; -using Foundatio.Xunit; using Foundatio.Metrics; using Foundatio.Utility; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; - using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Jobs { - public class JobTests : TestWithLoggingBase { - public JobTests(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Jobs +{ + public class JobTests : TestWithLoggingBase + { + public JobTests(ITestOutputHelper output) : base(output) { } [Fact] - public async Task CanCancelJob() { + public async Task CanCancelJob() + { var job = new HelloWorldJob(Log); var timeoutCancellationTokenSource = new CancellationTokenSource(1000); var resultTask = new JobRunner(job, Log).RunAsync(timeoutCancellationTokenSource.Token); @@ -28,17 +30,19 @@ public async Task CanCancelJob() { } [Fact] - public async Task CanStopLongRunningJob() { + public async Task CanStopLongRunningJob() + { var job = new LongRunningJob(Log); var runner = new JobRunner(job, Log); var cts = new CancellationTokenSource(1000); bool result = await runner.RunAsync(cts.Token); - + Assert.True(result); } [Fact] - public async Task CanStopLongRunningCronJob() { + public async Task CanStopLongRunningCronJob() + { var job = new LongRunningJob(Log); var runner = new JobRunner(job, Log); var cts = new CancellationTokenSource(1000); @@ -48,7 +52,8 @@ public async Task CanStopLongRunningCronJob() { } [Fact] - public async Task CanRunJobs() { + public async Task CanRunJobs() + { var job = new HelloWorldJob(Log); Assert.Equal(0, job.RunCount); await job.RunAsync(); @@ -58,7 +63,8 @@ public async Task CanRunJobs() { Assert.Equal(3, job.RunCount); var sw = Stopwatch.StartNew(); - using (var timeoutCancellationTokenSource = new CancellationTokenSource(100)) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource(100)) + { await job.RunContinuousAsync(cancellationToken: timeoutCancellationTokenSource.Token); } sw.Stop(); @@ -72,18 +78,21 @@ public async Task CanRunJobs() { } [Fact] - public async Task CanRunMultipleInstances() { + public async Task CanRunMultipleInstances() + { var job = new HelloWorldJob(Log); HelloWorldJob.GlobalRunCount = 0; - using (var timeoutCancellationTokenSource = new CancellationTokenSource(1000)) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource(1000)) + { await new JobRunner(job, Log, instanceCount: 5, iterationLimit: 1).RunAsync(timeoutCancellationTokenSource.Token); } Assert.Equal(5, HelloWorldJob.GlobalRunCount); HelloWorldJob.GlobalRunCount = 0; - using (var timeoutCancellationTokenSource = new CancellationTokenSource(50000)) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource(50000)) + { await new JobRunner(job, Log, instanceCount: 5, iterationLimit: 100).RunAsync(timeoutCancellationTokenSource.Token); } @@ -91,8 +100,10 @@ public async Task CanRunMultipleInstances() { } [Fact] - public async Task CanCancelContinuousJobs() { - using (TestSystemClock.Install()) { + public async Task CanCancelContinuousJobs() + { + using (TestSystemClock.Install()) + { var job = new HelloWorldJob(Log); var timeoutCancellationTokenSource = new CancellationTokenSource(100); await job.RunContinuousAsync(TimeSpan.FromSeconds(1), 5, timeoutCancellationTokenSource.Token); @@ -107,7 +118,8 @@ public async Task CanCancelContinuousJobs() { } [RetryFact] - public async Task CanRunJobsWithLocks() { + public async Task CanRunJobsWithLocks() + { var job = new WithLockingJob(Log); Assert.Equal(0, job.RunCount); await job.RunAsync(); @@ -121,12 +133,14 @@ public async Task CanRunJobsWithLocks() { } [Fact] - public async Task CanRunThrottledJobs() { + public async Task CanRunThrottledJobs() + { using var client = new InMemoryCacheClient(new InMemoryCacheClientOptions { LoggerFactory = Log }); var jobs = new List(new[] { new ThrottledJob(client, Log), new ThrottledJob(client, Log), new ThrottledJob(client, Log) }); var sw = Stopwatch.StartNew(); - using (var timeoutCancellationTokenSource = new CancellationTokenSource(1000)) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource(1000)) + { await Task.WhenAll(jobs.Select(job => job.RunContinuousAsync(TimeSpan.FromMilliseconds(1), cancellationToken: timeoutCancellationTokenSource.Token))); } sw.Stop(); @@ -136,8 +150,10 @@ public async Task CanRunThrottledJobs() { } [Fact] - public async Task CanRunJobsWithInterval() { - using (TestSystemClock.Install()) { + public async Task CanRunJobsWithInterval() + { + using (TestSystemClock.Install()) + { var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); TestSystemClock.SetFrozenTime(time); @@ -154,8 +170,10 @@ public async Task CanRunJobsWithInterval() { } [Fact] - public async Task CanRunJobsWithIntervalBetweenFailingJob() { - using (TestSystemClock.Install()) { + public async Task CanRunJobsWithIntervalBetweenFailingJob() + { + using (TestSystemClock.Install()) + { var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); TestSystemClock.SetFrozenTime(time); @@ -163,7 +181,7 @@ public async Task CanRunJobsWithIntervalBetweenFailingJob() { var job = new FailingJob(Log); var interval = TimeSpan.FromHours(.75); - + await job.RunContinuousAsync(iterationLimit: 2, interval: interval); Assert.Equal(2, job.RunCount); @@ -172,7 +190,8 @@ public async Task CanRunJobsWithIntervalBetweenFailingJob() { } [Fact(Skip = "Meant to be run manually.")] - public async Task JobLoopPerf() { + public async Task JobLoopPerf() + { const int iterations = 10000; var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { LoggerFactory = Log }); diff --git a/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs b/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs index 4f729c39f..4d5671206 100644 --- a/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs +++ b/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs @@ -7,44 +7,51 @@ using Exceptionless; using Foundatio.AsyncEx; using Foundatio.Jobs; -using Foundatio.Xunit; using Foundatio.Messaging; using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Tests.Extensions; using Foundatio.Utility; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Jobs { - public class WorkItemJobTests : TestWithLoggingBase { - public WorkItemJobTests(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Jobs +{ + public class WorkItemJobTests : TestWithLoggingBase + { + public WorkItemJobTests(ITestOutputHelper output) : base(output) { } [Fact] - public async Task CanRunWorkItem() { + public async Task CanRunWorkItem() + { using var queue = new InMemoryQueue(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); var job = new WorkItemJob(queue, messageBus, handlerRegistry, Log); - handlerRegistry.Register(async ctx => { + handlerRegistry.Register(async ctx => + { var jobData = ctx.GetData(); Assert.Equal("Test", jobData.SomeData); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) + { await SystemClock.SleepAsync(100); await ctx.ReportProgressAsync(10 * i); } }); - string jobId = await queue.EnqueueAsync(new MyWorkItem { + string jobId = await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); var countdown = new AsyncCountdownEvent(12); - await messageBus.SubscribeAsync(status => { + await messageBus.SubscribeAsync(status => + { _logger.LogInformation("Progress: {Progress}", status.Progress); Assert.Equal(jobId, status.WorkItemId); countdown.Signal(); @@ -56,7 +63,8 @@ await messageBus.SubscribeAsync(status => { } [Fact] - public async Task CanHandleMultipleWorkItemInstances() { + public async Task CanHandleMultipleWorkItemInstances() + { const int workItemCount = 1000; using var metrics = new InMemoryMetricsClient(o => o.LoggerFactory(Log)); @@ -71,7 +79,8 @@ public async Task CanHandleMultipleWorkItemInstances() { var jobIds = new ConcurrentDictionary(); - handlerRegistry.Register(async ctx => { + handlerRegistry.Register(async ctx => + { var jobData = ctx.GetData(); Assert.Equal("Test", jobData.SomeData); @@ -82,21 +91,24 @@ public async Task CanHandleMultipleWorkItemInstances() { for (int i = 0; i < 10; i++) await ctx.ReportProgressAsync(10 * i); - if (RandomData.GetBool(1)) { + if (RandomData.GetBool(1)) + { Interlocked.Increment(ref errors); throw new Exception("Boom!"); } }); for (int i = 0; i < workItemCount; i++) - await queue.EnqueueAsync(new MyWorkItem { + await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test", Index = i }, true); var completedItems = new List(); object completedItemsLock = new(); - await messageBus.SubscribeAsync(status => { + await messageBus.SubscribeAsync(status => + { if (status.Progress == 100 && _logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Progress: {Progress}", status.Progress); @@ -123,9 +135,12 @@ await messageBus.SubscribeAsync(status => { }, cancellationTokenSource.Token) }; - try { + try + { await Task.WhenAll(tasks); - } catch (OperationCanceledException ex) { + } + catch (OperationCanceledException ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "One or more tasks were cancelled: {Message}", ex.Message); } @@ -139,7 +154,8 @@ await messageBus.SubscribeAsync(status => { } [Fact] - public async Task CanRunWorkItemWithClassHandler() { + public async Task CanRunWorkItemWithClassHandler() + { using var queue = new InMemoryQueue(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); @@ -147,12 +163,14 @@ public async Task CanRunWorkItemWithClassHandler() { handlerRegistry.Register(new MyWorkItemHandler(Log)); - string jobId = await queue.EnqueueAsync(new MyWorkItem { + string jobId = await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); var countdown = new AsyncCountdownEvent(11); - await messageBus.SubscribeAsync(status => { + await messageBus.SubscribeAsync(status => + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Progress: {Progress}", status.Progress); Assert.Equal(jobId, status.WorkItemId); countdown.Signal(); @@ -164,28 +182,33 @@ await messageBus.SubscribeAsync(status => { } [Fact] - public async Task CanRunWorkItemWithDelegateHandler() { + public async Task CanRunWorkItemWithDelegateHandler() + { using var queue = new InMemoryQueue(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); var job = new WorkItemJob(queue, messageBus, handlerRegistry, Log); - handlerRegistry.Register(async ctx => { + handlerRegistry.Register(async ctx => + { var jobData = ctx.GetData(); Assert.Equal("Test", jobData.SomeData); - for (int i = 1; i < 10; i++) { + for (int i = 1; i < 10; i++) + { await SystemClock.SleepAsync(100); await ctx.ReportProgressAsync(10 * i); } }, Log.CreateLogger("MyWorkItem")); - string jobId = await queue.EnqueueAsync(new MyWorkItem { + string jobId = await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); var countdown = new AsyncCountdownEvent(11); - await messageBus.SubscribeAsync(status => { + await messageBus.SubscribeAsync(status => + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Progress: {Progress}", status.Progress); Assert.Equal(jobId, status.WorkItemId); countdown.Signal(); @@ -197,7 +220,8 @@ await messageBus.SubscribeAsync(status => { } [Fact] - public async Task CanRunWorkItemJobUntilEmpty() { + public async Task CanRunWorkItemJobUntilEmpty() + { using var queue = new InMemoryQueue(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); @@ -205,11 +229,13 @@ public async Task CanRunWorkItemJobUntilEmpty() { handlerRegistry.Register(new MyWorkItemHandler(Log)); - await queue.EnqueueAsync(new MyWorkItem { + await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); - await queue.EnqueueAsync(new MyWorkItem { + await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); @@ -221,24 +247,28 @@ await queue.EnqueueAsync(new MyWorkItem { } [Fact] - public async Task CanRunBadWorkItem() { + public async Task CanRunBadWorkItem() + { using var queue = new InMemoryQueue(o => o.RetryDelay(TimeSpan.FromMilliseconds(500)).LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); var job = new WorkItemJob(queue, messageBus, handlerRegistry, Log); - handlerRegistry.Register(ctx => { + handlerRegistry.Register(ctx => + { var jobData = ctx.GetData(); Assert.Equal("Test", jobData.SomeData); throw new Exception(); }); - string jobId = await queue.EnqueueAsync(new MyWorkItem { + string jobId = await queue.EnqueueAsync(new MyWorkItem + { SomeData = "Test" }, true); var countdown = new AsyncCountdownEvent(2); - await messageBus.SubscribeAsync(status => { + await messageBus.SubscribeAsync(status => + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Progress: {Progress}", status.Progress); Assert.Equal(jobId, status.WorkItemId); countdown.Signal(); @@ -250,22 +280,26 @@ await messageBus.SubscribeAsync(status => { } } - public class MyWorkItem { + public class MyWorkItem + { public string SomeData { get; set; } public int Index { get; set; } } - public class MyWorkItemHandler : WorkItemHandlerBase { + public class MyWorkItemHandler : WorkItemHandlerBase + { public MyWorkItemHandler(ILoggerFactory loggerFactory = null) : base(loggerFactory) { } - public override async Task HandleItemAsync(WorkItemContext context) { + public override async Task HandleItemAsync(WorkItemContext context) + { var jobData = context.GetData(); Assert.Equal("Test", jobData.SomeData); - for (int i = 1; i < 10; i++) { + for (int i = 1; i < 10; i++) + { await SystemClock.SleepAsync(100); await context.ReportProgressAsync(10 * i); } } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs b/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs index 3fef587e8..d0ffe1e4d 100644 --- a/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs +++ b/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs @@ -3,64 +3,77 @@ using Foundatio.Caching; using Foundatio.Lock; using Foundatio.Messaging; -using Xunit; -using Xunit.Abstractions; using Foundatio.Utility; -using Microsoft.Extensions.Logging; using Foundatio.Xunit; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; -namespace Foundatio.Tests.Locks { - public class InMemoryLockTests : LockTestBase, IDisposable { +namespace Foundatio.Tests.Locks +{ + public class InMemoryLockTests : LockTestBase, IDisposable + { private readonly ICacheClient _cache; private readonly IMessageBus _messageBus; - public InMemoryLockTests(ITestOutputHelper output) : base(output) { + public InMemoryLockTests(ITestOutputHelper output) : base(output) + { _cache = new InMemoryCacheClient(o => o.LoggerFactory(Log)); _messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); } - protected override ILockProvider GetThrottlingLockProvider(int maxHits, TimeSpan period) { + protected override ILockProvider GetThrottlingLockProvider(int maxHits, TimeSpan period) + { return new ThrottlingLockProvider(_cache, maxHits, period, Log); } - protected override ILockProvider GetLockProvider() { + protected override ILockProvider GetLockProvider() + { return new CacheLockProvider(_cache, _messageBus, Log); } [Fact] - public override Task CanAcquireAndReleaseLockAsync() { - using (TestSystemClock.Install()) { + public override Task CanAcquireAndReleaseLockAsync() + { + using (TestSystemClock.Install()) + { return base.CanAcquireAndReleaseLockAsync(); } } [Fact] - public override Task LockWillTimeoutAsync() { + public override Task LockWillTimeoutAsync() + { return base.LockWillTimeoutAsync(); } [Fact] - public override Task LockOneAtATimeAsync() { + public override Task LockOneAtATimeAsync() + { return base.LockOneAtATimeAsync(); } [Fact] - public override Task CanAcquireMultipleResources() { + public override Task CanAcquireMultipleResources() + { return base.CanAcquireMultipleResources(); } [Fact] - public override Task CanAcquireLocksInParallel() { + public override Task CanAcquireLocksInParallel() + { return base.CanAcquireLocksInParallel(); } [Fact] - public override Task CanAcquireMultipleScopedResources() { + public override Task CanAcquireMultipleScopedResources() + { return base.CanAcquireMultipleScopedResources(); } [RetryFact] - public override Task WillThrottleCallsAsync() { + public override Task WillThrottleCallsAsync() + { Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); @@ -68,13 +81,15 @@ public override Task WillThrottleCallsAsync() { } [Fact] - public override Task CanReleaseLockMultipleTimes() { + public override Task CanReleaseLockMultipleTimes() + { return base.CanReleaseLockMultipleTimes(); } - public void Dispose() { + public void Dispose() + { _cache.Dispose(); _messageBus.Dispose(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Messaging/InMemoryMessageBusTests.cs b/tests/Foundatio.Tests/Messaging/InMemoryMessageBusTests.cs index fc2c65ebc..0cff44adf 100644 --- a/tests/Foundatio.Tests/Messaging/InMemoryMessageBusTests.cs +++ b/tests/Foundatio.Tests/Messaging/InMemoryMessageBusTests.cs @@ -4,17 +4,21 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Messaging { - public class InMemoryMessageBusTests : MessageBusTestBase, IDisposable { +namespace Foundatio.Tests.Messaging +{ + public class InMemoryMessageBusTests : MessageBusTestBase, IDisposable + { private IMessageBus _messageBus; - public InMemoryMessageBusTests(ITestOutputHelper output) : base(output) {} + public InMemoryMessageBusTests(ITestOutputHelper output) : base(output) { } - protected override IMessageBus GetMessageBus(Func config = null) { + protected override IMessageBus GetMessageBus(Func config = null) + { if (_messageBus != null) return _messageBus; - _messageBus = new InMemoryMessageBus(o => { + _messageBus = new InMemoryMessageBus(o => + { o.LoggerFactory(Log); if (config != null) config(o.Target); @@ -25,14 +29,17 @@ protected override IMessageBus GetMessageBus(Func o.LoggerFactory(Log)); - await messageBus.PublishAsync(new SimpleMessageA { + await messageBus.PublishAsync(new SimpleMessageA + { Data = "Hello" }); Assert.Equal(1, messageBus.MessagesSent); @@ -41,93 +48,111 @@ await messageBus.PublishAsync(new SimpleMessageA { } [Fact] - public override Task CanSendMessageAsync() { + public override Task CanSendMessageAsync() + { return base.CanSendMessageAsync(); } [Fact] - public override Task CanHandleNullMessageAsync() { + public override Task CanHandleNullMessageAsync() + { return base.CanHandleNullMessageAsync(); } [Fact] - public override Task CanSendDerivedMessageAsync() { + public override Task CanSendDerivedMessageAsync() + { return base.CanSendDerivedMessageAsync(); } [Fact] - public override Task CanSendMappedMessageAsync() { + public override Task CanSendMappedMessageAsync() + { return base.CanSendMappedMessageAsync(); } [Fact] - public override Task CanSendDelayedMessageAsync() { + public override Task CanSendDelayedMessageAsync() + { return base.CanSendDelayedMessageAsync(); } [Fact] - public override Task CanSubscribeConcurrentlyAsync() { + public override Task CanSubscribeConcurrentlyAsync() + { return base.CanSubscribeConcurrentlyAsync(); } [Fact] - public override Task CanReceiveMessagesConcurrentlyAsync() { + public override Task CanReceiveMessagesConcurrentlyAsync() + { return base.CanReceiveMessagesConcurrentlyAsync(); } [Fact] - public override Task CanSendMessageToMultipleSubscribersAsync() { + public override Task CanSendMessageToMultipleSubscribersAsync() + { return base.CanSendMessageToMultipleSubscribersAsync(); } [Fact] - public override Task CanTolerateSubscriberFailureAsync() { + public override Task CanTolerateSubscriberFailureAsync() + { return base.CanTolerateSubscriberFailureAsync(); } [Fact] - public override Task WillOnlyReceiveSubscribedMessageTypeAsync() { + public override Task WillOnlyReceiveSubscribedMessageTypeAsync() + { return base.WillOnlyReceiveSubscribedMessageTypeAsync(); } [Fact] - public override Task WillReceiveDerivedMessageTypesAsync() { + public override Task WillReceiveDerivedMessageTypesAsync() + { return base.WillReceiveDerivedMessageTypesAsync(); } [Fact] - public override Task CanSubscribeToAllMessageTypesAsync() { + public override Task CanSubscribeToAllMessageTypesAsync() + { return base.CanSubscribeToAllMessageTypesAsync(); } [Fact] - public override Task CanSubscribeToRawMessagesAsync() { + public override Task CanSubscribeToRawMessagesAsync() + { return base.CanSubscribeToRawMessagesAsync(); } [Fact] - public override Task CanCancelSubscriptionAsync() { + public override Task CanCancelSubscriptionAsync() + { return base.CanCancelSubscriptionAsync(); } [Fact] - public override Task WontKeepMessagesWithNoSubscribersAsync() { + public override Task WontKeepMessagesWithNoSubscribersAsync() + { return base.WontKeepMessagesWithNoSubscribersAsync(); } [Fact] - public override Task CanReceiveFromMultipleSubscribersAsync() { + public override Task CanReceiveFromMultipleSubscribersAsync() + { return base.CanReceiveFromMultipleSubscribersAsync(); } [Fact] - public override void CanDisposeWithNoSubscribersOrPublishers() { + public override void CanDisposeWithNoSubscribersOrPublishers() + { base.CanDisposeWithNoSubscribersOrPublishers(); } - public void Dispose() { + public void Dispose() + { _messageBus?.Dispose(); _messageBus = null; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs b/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs index c19f6f35c..5bd18b6dc 100644 --- a/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs +++ b/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs @@ -1,24 +1,28 @@ using System; -using Foundatio.Xunit; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Foundatio.Metrics; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -using System.Linq; -using System.Threading.Tasks; -using System.Threading; -namespace Foundatio.Tests.Metrics { - public class DiagnosticsMetricsTests : TestWithLoggingBase, IDisposable { +namespace Foundatio.Tests.Metrics +{ + public class DiagnosticsMetricsTests : TestWithLoggingBase, IDisposable + { private readonly DiagnosticsMetricsClient _client; - public DiagnosticsMetricsTests(ITestOutputHelper output) : base(output) { + public DiagnosticsMetricsTests(ITestOutputHelper output) : base(output) + { Log.MinimumLevel = LogLevel.Trace; _client = new DiagnosticsMetricsClient(o => o.MeterName("Test")); } [Fact] - public void Counter() { + public void Counter() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); _client.Counter("counter"); @@ -29,14 +33,16 @@ public void Counter() { } [Fact] - public void CounterWithValue() { + public void CounterWithValue() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); _client.Counter("counter", 5); _client.Counter("counter", 3); Assert.Equal(2, metricsCollector.GetMeasurements().Count); - Assert.All(metricsCollector.GetMeasurements(), m => { + Assert.All(metricsCollector.GetMeasurements(), m => + { Assert.Equal("counter", m.Name); }); Assert.Equal(8, metricsCollector.GetSum("counter")); @@ -44,20 +50,22 @@ public void CounterWithValue() { } [Fact] - public void Gauge() { + public void Gauge() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); _client.Gauge("gauge", 1.1); metricsCollector.RecordObservableInstruments(); - Assert.Single(metricsCollector.GetMeasurements());; + Assert.Single(metricsCollector.GetMeasurements()); ; Assert.Equal("gauge", metricsCollector.GetMeasurements().Single().Name); Assert.Equal(1.1, metricsCollector.GetMeasurements().Single().Value); } [Fact] - public void Timer() { + public void Timer() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); _client.Timer("timer", 450); @@ -68,10 +76,12 @@ public void Timer() { } [Fact] - public async Task CanWaitForCounter() { + public async Task CanWaitForCounter() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - var success = await metricsCollector.WaitForCounterAsync("timer", () => { + var success = await metricsCollector.WaitForCounterAsync("timer", () => + { _client.Counter("timer", 1); _client.Counter("timer", 2); return Task.CompletedTask; @@ -81,10 +91,12 @@ public async Task CanWaitForCounter() { } [Fact] - public async Task CanTimeoutWaitingForCounter() { + public async Task CanTimeoutWaitingForCounter() + { using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - var success = await metricsCollector.WaitForCounterAsync("timer", () => { + var success = await metricsCollector.WaitForCounterAsync("timer", () => + { _client.Counter("timer", 1); _client.Counter("timer", 2); return Task.CompletedTask; @@ -93,9 +105,10 @@ public async Task CanTimeoutWaitingForCounter() { Assert.False(success); } - public void Dispose() { + public void Dispose() + { _client.Dispose(); GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs b/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs index eb5a3971b..2dd9c239a 100644 --- a/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs +++ b/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs @@ -6,44 +6,54 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Metrics { - public class InMemoryMetricsTests : MetricsClientTestBase { +namespace Foundatio.Tests.Metrics +{ + public class InMemoryMetricsTests : MetricsClientTestBase + { public InMemoryMetricsTests(ITestOutputHelper output) : base(output) { } - public override IMetricsClient GetMetricsClient(bool buffered = false) { + public override IMetricsClient GetMetricsClient(bool buffered = false) + { return new InMemoryMetricsClient(o => o.LoggerFactory(Log).Buffered(buffered)); } [Fact] - public override Task CanSetGaugesAsync() { + public override Task CanSetGaugesAsync() + { return base.CanSetGaugesAsync(); } [Fact] - public override Task CanIncrementCounterAsync() { + public override Task CanIncrementCounterAsync() + { return base.CanIncrementCounterAsync(); } [RetryFact] - public override Task CanWaitForCounterAsync() { - using (TestSystemClock.Install()) { + public override Task CanWaitForCounterAsync() + { + using (TestSystemClock.Install()) + { return base.CanWaitForCounterAsync(); } } [Fact] - public override Task CanGetBufferedQueueMetricsAsync() { + public override Task CanGetBufferedQueueMetricsAsync() + { return base.CanGetBufferedQueueMetricsAsync(); } [Fact] - public override Task CanIncrementBufferedCounterAsync() { + public override Task CanIncrementBufferedCounterAsync() + { return base.CanIncrementBufferedCounterAsync(); } [Fact] - public override Task CanSendBufferedMetricsAsync() { + public override Task CanSendBufferedMetricsAsync() + { return base.CanSendBufferedMetricsAsync(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Properties/AssemblyInfo.cs b/tests/Foundatio.Tests/Properties/AssemblyInfo.cs index b946c3eca..314256921 100644 --- a/tests/Foundatio.Tests/Properties/AssemblyInfo.cs +++ b/tests/Foundatio.Tests/Properties/AssemblyInfo.cs @@ -1 +1 @@ -[assembly: Xunit.CollectionBehaviorAttribute(DisableTestParallelization = true, MaxParallelThreads = 1)] \ No newline at end of file +[assembly: Xunit.CollectionBehaviorAttribute(DisableTestParallelization = true, MaxParallelThreads = 1)] diff --git a/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs b/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs index d69ff0b10..6ff8e57fb 100644 --- a/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs +++ b/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs @@ -8,18 +8,21 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Queue { - public class InMemoryQueueTests : QueueTestBase { +namespace Foundatio.Tests.Queue +{ + public class InMemoryQueueTests : QueueTestBase + { private IQueue _queue; - public InMemoryQueueTests(ITestOutputHelper output) : base(output) {} + public InMemoryQueueTests(ITestOutputHelper output) : base(output) { } - protected override IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) { + protected override IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) + { if (_queue == null) _queue = new InMemoryQueue(o => o .RetryDelay(retryDelay.GetValueOrDefault(TimeSpan.FromMinutes(1))) .Retries(retries) - .RetryMultipliers(retryMultipliers ?? new [] { 1, 3, 5, 10 }) + .RetryMultipliers(retryMultipliers ?? new[] { 1, 3, 5, 10 }) .WorkItemTimeout(workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5))) .LoggerFactory(Log)); if (_logger.IsEnabled(LogLevel.Debug)) @@ -27,35 +30,45 @@ protected override IQueue GetQueue(int retries = 1, TimeSpan? wo return _queue; } - protected override async Task CleanupQueueAsync(IQueue queue) { + protected override async Task CleanupQueueAsync(IQueue queue) + { if (queue == null) return; - try { + try + { await queue.DeleteQueueAsync(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error cleaning up queue"); } } [Fact] - public async Task TestAsyncEvents() { + public async Task TestAsyncEvents() + { using var q = new InMemoryQueue(o => o.LoggerFactory(Log)); var disposables = new List(5); - try { - disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => { + try + { + disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => + { await SystemClock.SleepAsync(250); _logger.LogInformation("First Enqueuing"); })); - disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => { + disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => + { await SystemClock.SleepAsync(250); _logger.LogInformation("Second Enqueuing"); })); - disposables.Add(q.Enqueued.AddHandler(async (sender, args) => { + disposables.Add(q.Enqueued.AddHandler(async (sender, args) => + { await SystemClock.SleepAsync(250); _logger.LogInformation("First"); })); - disposables.Add(q.Enqueued.AddHandler(async (sender, args) => { + disposables.Add(q.Enqueued.AddHandler(async (sender, args) => + { await SystemClock.SleepAsync(250); _logger.LogInformation("Second"); })); @@ -69,14 +82,17 @@ public async Task TestAsyncEvents() { await q.EnqueueAsync(new SimpleWorkItem()); sw.Stop(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Time {Elapsed:g}", sw.Elapsed); - } finally { + } + finally + { foreach (var disposable in disposables) disposable.Dispose(); } } [Fact] - public async Task CanGetCompletedEntries() { + public async Task CanGetCompletedEntries() + { using var q = new InMemoryQueue(o => o.LoggerFactory(Log).CompletedEntryRetentionLimit(10)); await q.EnqueueAsync(new SimpleWorkItem()); @@ -94,7 +110,8 @@ public async Task CanGetCompletedEntries() { Assert.Empty(q.GetDequeuedEntries()); Assert.Single(q.GetCompletedEntries()); - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 100; i++) + { await q.EnqueueAsync(new SimpleWorkItem()); item = await q.DequeueAsync(); await item.CompleteAsync(); @@ -106,156 +123,187 @@ public async Task CanGetCompletedEntries() { } [Fact] - public override Task CanQueueAndDequeueWorkItemAsync() { + public override Task CanQueueAndDequeueWorkItemAsync() + { return base.CanQueueAndDequeueWorkItemAsync(); } [Fact] - public override Task CanQueueAndDequeueWorkItemWithDelayAsync() { + public override Task CanQueueAndDequeueWorkItemWithDelayAsync() + { return base.CanQueueAndDequeueWorkItemWithDelayAsync(); } [Fact] - public override Task CanUseQueueOptionsAsync() { + public override Task CanUseQueueOptionsAsync() + { return base.CanUseQueueOptionsAsync(); } [Fact] - public override Task CanDiscardDuplicateQueueEntriesAsync() { + public override Task CanDiscardDuplicateQueueEntriesAsync() + { return base.CanDiscardDuplicateQueueEntriesAsync(); } [Fact] - public override Task CanDequeueWithCancelledTokenAsync() { + public override Task CanDequeueWithCancelledTokenAsync() + { return base.CanDequeueWithCancelledTokenAsync(); } [Fact] - public override Task CanDequeueEfficientlyAsync() { + public override Task CanDequeueEfficientlyAsync() + { return base.CanDequeueEfficientlyAsync(); } [Fact] - public override Task CanResumeDequeueEfficientlyAsync() { + public override Task CanResumeDequeueEfficientlyAsync() + { return base.CanResumeDequeueEfficientlyAsync(); } [Fact] - public override Task CanQueueAndDequeueMultipleWorkItemsAsync() { + public override Task CanQueueAndDequeueMultipleWorkItemsAsync() + { return base.CanQueueAndDequeueMultipleWorkItemsAsync(); } [Fact] - public override Task WillNotWaitForItemAsync() { + public override Task WillNotWaitForItemAsync() + { return base.WillNotWaitForItemAsync(); } [Fact] - public override Task WillWaitForItemAsync() { + public override Task WillWaitForItemAsync() + { return base.WillWaitForItemAsync(); } [Fact] - public override Task DequeueWaitWillGetSignaledAsync() { + public override Task DequeueWaitWillGetSignaledAsync() + { return base.DequeueWaitWillGetSignaledAsync(); } [Fact] - public override Task CanUseQueueWorkerAsync() { + public override Task CanUseQueueWorkerAsync() + { return base.CanUseQueueWorkerAsync(); } [Fact] - public override Task CanHandleErrorInWorkerAsync() { + public override Task CanHandleErrorInWorkerAsync() + { return base.CanHandleErrorInWorkerAsync(); } [Fact] - public override Task WorkItemsWillTimeoutAsync() { - using (TestSystemClock.Install()) { + public override Task WorkItemsWillTimeoutAsync() + { + using (TestSystemClock.Install()) + { return base.WorkItemsWillTimeoutAsync(); } } [Fact] - public override Task WorkItemsWillGetMovedToDeadletterAsync() { + public override Task WorkItemsWillGetMovedToDeadletterAsync() + { return base.WorkItemsWillGetMovedToDeadletterAsync(); } [Fact] - public override Task CanAutoCompleteWorkerAsync() { + public override Task CanAutoCompleteWorkerAsync() + { return base.CanAutoCompleteWorkerAsync(); } [Fact] - public override Task CanHaveMultipleQueueInstancesAsync() { + public override Task CanHaveMultipleQueueInstancesAsync() + { return base.CanHaveMultipleQueueInstancesAsync(); } [Fact] - public override Task CanDelayRetryAsync() { + public override Task CanDelayRetryAsync() + { return base.CanDelayRetryAsync(); } [Fact] - public override Task CanRunWorkItemWithMetricsAsync() { + public override Task CanRunWorkItemWithMetricsAsync() + { return base.CanRunWorkItemWithMetricsAsync(); } [Fact] - public override Task CanRenewLockAsync() { + public override Task CanRenewLockAsync() + { return base.CanRenewLockAsync(); } [Fact] - public override Task CanAbandonQueueEntryOnceAsync() { + public override Task CanAbandonQueueEntryOnceAsync() + { return base.CanAbandonQueueEntryOnceAsync(); } [Fact] - public override Task CanCompleteQueueEntryOnceAsync() { + public override Task CanCompleteQueueEntryOnceAsync() + { return base.CanCompleteQueueEntryOnceAsync(); } [Fact] - public override Task CanDequeueWithLockingAsync() { + public override Task CanDequeueWithLockingAsync() + { return base.CanDequeueWithLockingAsync(); } [Fact] - public override Task CanHaveMultipleQueueInstancesWithLockingAsync() { + public override Task CanHaveMultipleQueueInstancesWithLockingAsync() + { return base.CanHaveMultipleQueueInstancesWithLockingAsync(); } [Fact] - public override Task MaintainJobNotAbandon_NotWorkTimeOutEntry() { + public override Task MaintainJobNotAbandon_NotWorkTimeOutEntry() + { return base.MaintainJobNotAbandon_NotWorkTimeOutEntry(); } [Fact] - public override Task VerifyRetryAttemptsAsync() { + public override Task VerifyRetryAttemptsAsync() + { return base.VerifyRetryAttemptsAsync(); } [Fact] - public override Task VerifyDelayedRetryAttemptsAsync() { + public override Task VerifyDelayedRetryAttemptsAsync() + { return base.VerifyDelayedRetryAttemptsAsync(); } [Fact] - public override Task CanHandleAutoAbandonInWorker() { + public override Task CanHandleAutoAbandonInWorker() + { Log.MinimumLevel = LogLevel.Trace; return base.CanHandleAutoAbandonInWorker(); } #region Issue239 - class QueueEntry_Issue239 : IQueueEntry where T : class { + class QueueEntry_Issue239 : IQueueEntry where T : class + { IQueueEntry _queueEntry; - public QueueEntry_Issue239(IQueueEntry queueEntry) { - _queueEntry = queueEntry; - } + public QueueEntry_Issue239(IQueueEntry queueEntry) + { + _queueEntry = queueEntry; + } public T Value => _queueEntry.Value; @@ -273,58 +321,69 @@ public QueueEntry_Issue239(IQueueEntry queueEntry) { public int Attempts => _queueEntry.Attempts; - public Task AbandonAsync() { + public Task AbandonAsync() + { return _queueEntry.AbandonAsync(); } - public Task CompleteAsync() { + public Task CompleteAsync() + { return _queueEntry.CompleteAsync(); } - public ValueTask DisposeAsync() { + public ValueTask DisposeAsync() + { return _queueEntry.DisposeAsync(); } - public object GetValue() { + public object GetValue() + { return _queueEntry.GetValue(); } - public void MarkAbandoned() { + public void MarkAbandoned() + { // we want to simulate timing of user complete call between the maintenance abandon call to _dequeued.TryRemove and entry.MarkAbandoned(); Task.Delay(1500).Wait(); _queueEntry.MarkAbandoned(); } - public void MarkCompleted() { + public void MarkCompleted() + { _queueEntry.MarkCompleted(); } - public Task RenewLockAsync() { + public Task RenewLockAsync() + { return _queueEntry.RenewLockAsync(); } } - class InMemoryQueue_Issue239 : InMemoryQueue where T : class { - public override Task AbandonAsync(IQueueEntry entry) { + class InMemoryQueue_Issue239 : InMemoryQueue where T : class + { + public override Task AbandonAsync(IQueueEntry entry) + { // delay first abandon from maintenance (simulate timing issues which may occur to demonstrate the problem) - return base.AbandonAsync(new QueueEntry_Issue239(entry)); + return base.AbandonAsync(new QueueEntry_Issue239(entry)); } - public InMemoryQueue_Issue239(ILoggerFactory loggerFactory) + public InMemoryQueue_Issue239(ILoggerFactory loggerFactory) : base(o => o .RetryDelay(TimeSpan.FromMinutes(1)) .Retries(1) .RetryMultipliers(new[] { 1, 3, 5, 10 }) .LoggerFactory(loggerFactory) - .WorkItemTimeout(TimeSpan.FromMilliseconds(100))) { + .WorkItemTimeout(TimeSpan.FromMilliseconds(100))) + { } } [Fact] // this test reproduce an issue which cause worker task loop to crash and stop processing items when auto abandoned item is ultimately processed and user call complete on // https://github.com/FoundatioFx/Foundatio/issues/239 - public virtual async Task CompleteOnAutoAbandonedHandledProperly_Issue239() { + public virtual async Task CompleteOnAutoAbandonedHandledProperly_Issue239() + { // create queue with short work item timeout so it will be auto abandoned var queue = new InMemoryQueue_Issue239(Log); @@ -332,16 +391,21 @@ public virtual async Task CompleteOnAutoAbandonedHandledProperly_Issue239() { var taskCompletionSource = new TaskCompletionSource(); // start handling items - await queue.StartWorkingAsync(async (item) => { + await queue.StartWorkingAsync(async (item) => + { // we want to wait for maintainance to be performed and auto abandon our item, we don't have any way for waiting in IQueue so we'll settle for a delay - if (item.Value.Data == "Delay") { + if (item.Value.Data == "Delay") + { await Task.Delay(TimeSpan.FromSeconds(1)); } - try { + try + { // call complete on the auto abandoned item await item.CompleteAsync(); - } finally { + } + finally + { // completeAsync will currently throw an exception becuase item can not be removed from dequeued list because it was already removed due to auto abandon // infrastructure handles user exception incorrectly taskCompletionSource.SetResult(true); @@ -368,4 +432,4 @@ await queue.StartWorkingAsync(async (item) => { #endregion } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Serializer/CompressedMessagePackSerializerTests.cs b/tests/Foundatio.Tests/Serializer/CompressedMessagePackSerializerTests.cs index 2d1d46c48..b221a21fb 100644 --- a/tests/Foundatio.Tests/Serializer/CompressedMessagePackSerializerTests.cs +++ b/tests/Foundatio.Tests/Serializer/CompressedMessagePackSerializerTests.cs @@ -5,40 +5,49 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public class CompressedMessagePackSerializerTests : SerializerTestsBase { +namespace Foundatio.Tests.Serializer +{ + public class CompressedMessagePackSerializerTests : SerializerTestsBase + { public CompressedMessagePackSerializerTests(ITestOutputHelper output) : base(output) { } - protected override ISerializer GetSerializer() { + protected override ISerializer GetSerializer() + { return new MessagePackSerializer(MessagePack.MessagePackSerializerOptions.Standard .WithCompression(MessagePack.MessagePackCompression.Lz4Block) .WithResolver(ContractlessStandardResolver.Instance)); } [Fact] - public override void CanRoundTripBytes() { + public override void CanRoundTripBytes() + { base.CanRoundTripBytes(); } [Fact] - public override void CanRoundTripString() { + public override void CanRoundTripString() + { base.CanRoundTripString(); } - + [Fact] - public override void CanHandlePrimitiveTypes() { + public override void CanHandlePrimitiveTypes() + { base.CanHandlePrimitiveTypes(); } [Fact(Skip = "Skip benchmarks for now")] - public virtual void Benchmark() { + public virtual void Benchmark() + { var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); _logger.LogInformation(summary.ToJson()); } } - public class CompressedMessagePackSerializerBenchmark : SerializerBenchmarkBase { - protected override ISerializer GetSerializer() { + public class CompressedMessagePackSerializerBenchmark : SerializerBenchmarkBase + { + protected override ISerializer GetSerializer() + { return new MessagePackSerializer(MessagePack.MessagePackSerializerOptions.Standard .WithCompression(MessagePack.MessagePackCompression.Lz4Block) .WithResolver(ContractlessStandardResolver.Instance)); diff --git a/tests/Foundatio.Tests/Serializer/JsonNetSerializerTests.cs b/tests/Foundatio.Tests/Serializer/JsonNetSerializerTests.cs index 1047ed899..e5a7a07b7 100644 --- a/tests/Foundatio.Tests/Serializer/JsonNetSerializerTests.cs +++ b/tests/Foundatio.Tests/Serializer/JsonNetSerializerTests.cs @@ -4,39 +4,48 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public class JsonNetSerializerTests : SerializerTestsBase { +namespace Foundatio.Tests.Serializer +{ + public class JsonNetSerializerTests : SerializerTestsBase + { public JsonNetSerializerTests(ITestOutputHelper output) : base(output) { } - protected override ISerializer GetSerializer() { + protected override ISerializer GetSerializer() + { return new JsonNetSerializer(); } - + [Fact] - public override void CanRoundTripBytes() { + public override void CanRoundTripBytes() + { base.CanRoundTripBytes(); } - + [Fact] - public override void CanRoundTripString() { + public override void CanRoundTripString() + { base.CanRoundTripString(); } - + [Fact] - public override void CanHandlePrimitiveTypes() { + public override void CanHandlePrimitiveTypes() + { base.CanHandlePrimitiveTypes(); } [Fact(Skip = "Skip benchmarks for now")] - public virtual void Benchmark() { + public virtual void Benchmark() + { var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); _logger.LogInformation(summary.ToJson()); } } - public class JsonNetSerializerBenchmark : SerializerBenchmarkBase { - protected override ISerializer GetSerializer() { + public class JsonNetSerializerBenchmark : SerializerBenchmarkBase + { + protected override ISerializer GetSerializer() + { return new JsonNetSerializer(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Serializer/MessagePackSerializerTests.cs b/tests/Foundatio.Tests/Serializer/MessagePackSerializerTests.cs index 554c085aa..bfd5b810c 100644 --- a/tests/Foundatio.Tests/Serializer/MessagePackSerializerTests.cs +++ b/tests/Foundatio.Tests/Serializer/MessagePackSerializerTests.cs @@ -4,39 +4,48 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public class MessagePackSerializerTests : SerializerTestsBase { +namespace Foundatio.Tests.Serializer +{ + public class MessagePackSerializerTests : SerializerTestsBase + { public MessagePackSerializerTests(ITestOutputHelper output) : base(output) { } - protected override ISerializer GetSerializer() { + protected override ISerializer GetSerializer() + { return new MessagePackSerializer(); } - + [Fact] - public override void CanRoundTripBytes() { + public override void CanRoundTripBytes() + { base.CanRoundTripBytes(); } - + [Fact] - public override void CanRoundTripString() { + public override void CanRoundTripString() + { base.CanRoundTripString(); } - + [Fact] - public override void CanHandlePrimitiveTypes() { + public override void CanHandlePrimitiveTypes() + { base.CanHandlePrimitiveTypes(); } [Fact(Skip = "Skip benchmarks for now")] - public virtual void Benchmark() { + public virtual void Benchmark() + { var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); _logger.LogInformation(summary.ToJson()); } } - public class MessagePackSerializerBenchmark : SerializerBenchmarkBase { - protected override ISerializer GetSerializer() { + public class MessagePackSerializerBenchmark : SerializerBenchmarkBase + { + protected override ISerializer GetSerializer() + { return new MessagePackSerializer(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Serializer/SystemTextJsonSerializerTests.cs b/tests/Foundatio.Tests/Serializer/SystemTextJsonSerializerTests.cs index 3f8e0eefb..91d1da200 100644 --- a/tests/Foundatio.Tests/Serializer/SystemTextJsonSerializerTests.cs +++ b/tests/Foundatio.Tests/Serializer/SystemTextJsonSerializerTests.cs @@ -4,39 +4,48 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public class SystemTextJsonSerializerTests : SerializerTestsBase { +namespace Foundatio.Tests.Serializer +{ + public class SystemTextJsonSerializerTests : SerializerTestsBase + { public SystemTextJsonSerializerTests(ITestOutputHelper output) : base(output) { } - protected override ISerializer GetSerializer() { + protected override ISerializer GetSerializer() + { return new SystemTextJsonSerializer(); } - + [Fact] - public override void CanRoundTripBytes() { + public override void CanRoundTripBytes() + { base.CanRoundTripBytes(); } - + [Fact] - public override void CanRoundTripString() { + public override void CanRoundTripString() + { base.CanRoundTripString(); } - + [Fact] - public override void CanHandlePrimitiveTypes() { + public override void CanHandlePrimitiveTypes() + { base.CanHandlePrimitiveTypes(); } [Fact(Skip = "Skip benchmarks for now")] - public virtual void Benchmark() { + public virtual void Benchmark() + { var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); _logger.LogInformation(summary.ToJson()); } } - public class SystemTextJsonSerializerBenchmark : SerializerBenchmarkBase { - protected override ISerializer GetSerializer() { + public class SystemTextJsonSerializerBenchmark : SerializerBenchmarkBase + { + protected override ISerializer GetSerializer() + { return new SystemTextJsonSerializer(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Serializer/Utf8JsonSerializerTests.cs b/tests/Foundatio.Tests/Serializer/Utf8JsonSerializerTests.cs index 96aa6992d..b4c3793ec 100644 --- a/tests/Foundatio.Tests/Serializer/Utf8JsonSerializerTests.cs +++ b/tests/Foundatio.Tests/Serializer/Utf8JsonSerializerTests.cs @@ -4,34 +4,42 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Serializer { - public class Utf8JsonSerializerTests : SerializerTestsBase { +namespace Foundatio.Tests.Serializer +{ + public class Utf8JsonSerializerTests : SerializerTestsBase + { public Utf8JsonSerializerTests(ITestOutputHelper output) : base(output) { } - protected override ISerializer GetSerializer() { + protected override ISerializer GetSerializer() + { return new Utf8JsonSerializer(); } - + [Fact] - public override void CanRoundTripBytes() { + public override void CanRoundTripBytes() + { base.CanRoundTripBytes(); } - + [Fact] - public override void CanRoundTripString() { + public override void CanRoundTripString() + { base.CanRoundTripString(); } [Fact(Skip = "Skip benchmarks for now")] - public virtual void Benchmark() { + public virtual void Benchmark() + { var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); _logger.LogInformation(summary.ToJson()); } } - public class Utf8JsonSerializerBenchmark : SerializerBenchmarkBase { - protected override ISerializer GetSerializer() { + public class Utf8JsonSerializerBenchmark : SerializerBenchmarkBase + { + protected override ISerializer GetSerializer() + { return new Utf8JsonSerializer(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs b/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs index 2608f998d..4d40a6d08 100644 --- a/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/FolderFileStorageTests.cs @@ -3,111 +3,134 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Storage { - public class FolderFileStorageTests : FileStorageTestsBase { - public FolderFileStorageTests(ITestOutputHelper output) : base(output) {} - - protected override IFileStorage GetStorage() { +namespace Foundatio.Tests.Storage +{ + public class FolderFileStorageTests : FileStorageTestsBase + { + public FolderFileStorageTests(ITestOutputHelper output) : base(output) { } + + protected override IFileStorage GetStorage() + { return new FolderFileStorage(o => o.Folder("|DataDirectory|\\temp")); } [Fact] - public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { + public override Task CanGetEmptyFileListOnMissingDirectoryAsync() + { return base.CanGetEmptyFileListOnMissingDirectoryAsync(); } [Fact] - public override Task CanGetFileListForSingleFolderAsync() { + public override Task CanGetFileListForSingleFolderAsync() + { return base.CanGetFileListForSingleFolderAsync(); } - + [Fact] - public override Task CanGetPagedFileListForSingleFolderAsync() { + public override Task CanGetPagedFileListForSingleFolderAsync() + { return base.CanGetPagedFileListForSingleFolderAsync(); } - + [Fact] - public override Task CanGetFileListForSingleFileAsync() { + public override Task CanGetFileListForSingleFileAsync() + { return base.CanGetFileListForSingleFileAsync(); } [Fact] - public override Task CanGetFileInfoAsync() { + public override Task CanGetFileInfoAsync() + { return base.CanGetFileInfoAsync(); } [Fact] - public override Task CanGetNonExistentFileInfoAsync() { + public override Task CanGetNonExistentFileInfoAsync() + { return base.CanGetNonExistentFileInfoAsync(); } [Fact] - public override Task CanSaveFilesAsync() { + public override Task CanSaveFilesAsync() + { return base.CanSaveFilesAsync(); } [Fact] - public override Task CanManageFilesAsync() { + public override Task CanManageFilesAsync() + { return base.CanManageFilesAsync(); } [Fact] - public override Task CanRenameFilesAsync() { + public override Task CanRenameFilesAsync() + { return base.CanRenameFilesAsync(); } [Fact] - public override Task CanConcurrentlyManageFilesAsync() { + public override Task CanConcurrentlyManageFilesAsync() + { return base.CanConcurrentlyManageFilesAsync(); } [Fact] - public override void CanUseDataDirectory() { + public override void CanUseDataDirectory() + { base.CanUseDataDirectory(); } [Fact] - public override Task CanDeleteEntireFolderAsync() { + public override Task CanDeleteEntireFolderAsync() + { return base.CanDeleteEntireFolderAsync(); } [Fact] - public override Task CanDeleteEntireFolderWithWildcardAsync() { + public override Task CanDeleteEntireFolderWithWildcardAsync() + { return base.CanDeleteEntireFolderWithWildcardAsync(); } [Fact(Skip = "Directory.EnumerateFiles does not support nested folder wildcards")] - public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() + { return base.CanDeleteFolderWithMultiFolderWildcardsAsync(); } [Fact] - public override Task CanDeleteSpecificFilesAsync() { + public override Task CanDeleteSpecificFilesAsync() + { return base.CanDeleteSpecificFilesAsync(); } [Fact] - public override Task CanDeleteNestedFolderAsync() { + public override Task CanDeleteNestedFolderAsync() + { return base.CanDeleteNestedFolderAsync(); } [Fact] - public override Task CanDeleteSpecificFilesInNestedFolderAsync() { + public override Task CanDeleteSpecificFilesInNestedFolderAsync() + { return base.CanDeleteSpecificFilesInNestedFolderAsync(); } [Fact] - public override Task CanRoundTripSeekableStreamAsync() { + public override Task CanRoundTripSeekableStreamAsync() + { return base.CanRoundTripSeekableStreamAsync(); } [Fact] - public override Task WillRespectStreamOffsetAsync() { + public override Task WillRespectStreamOffsetAsync() + { return base.WillRespectStreamOffsetAsync(); } [Fact] - public override Task WillWriteStreamContentAsync() { + public override Task WillWriteStreamContentAsync() + { return base.WillWriteStreamContentAsync(); } } diff --git a/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs b/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs index 8b67322ed..9a2e65105 100644 --- a/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/InMemoryFileStorageTests.cs @@ -3,106 +3,128 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Storage { - public class InMemoryFileStorageTests : FileStorageTestsBase { - public InMemoryFileStorageTests(ITestOutputHelper output) : base(output) {} - - protected override IFileStorage GetStorage() { +namespace Foundatio.Tests.Storage +{ + public class InMemoryFileStorageTests : FileStorageTestsBase + { + public InMemoryFileStorageTests(ITestOutputHelper output) : base(output) { } + + protected override IFileStorage GetStorage() + { return new InMemoryFileStorage { MaxFiles = 2000 }; } [Fact] - public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { + public override Task CanGetEmptyFileListOnMissingDirectoryAsync() + { return base.CanGetEmptyFileListOnMissingDirectoryAsync(); } [Fact] - public override Task CanGetFileListForSingleFolderAsync() { + public override Task CanGetFileListForSingleFolderAsync() + { return base.CanGetFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileListForSingleFileAsync() { + public override Task CanGetFileListForSingleFileAsync() + { return base.CanGetFileListForSingleFileAsync(); } - + [Fact] - public override Task CanGetPagedFileListForSingleFolderAsync() { + public override Task CanGetPagedFileListForSingleFolderAsync() + { return base.CanGetPagedFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileInfoAsync() { + public override Task CanGetFileInfoAsync() + { return base.CanGetFileInfoAsync(); } [Fact] - public override Task CanGetNonExistentFileInfoAsync() { + public override Task CanGetNonExistentFileInfoAsync() + { return base.CanGetNonExistentFileInfoAsync(); } [Fact] - public override Task CanSaveFilesAsync() { + public override Task CanSaveFilesAsync() + { return base.CanSaveFilesAsync(); } [Fact] - public override Task CanManageFilesAsync() { + public override Task CanManageFilesAsync() + { return base.CanManageFilesAsync(); } [Fact] - public override Task CanRenameFilesAsync() { + public override Task CanRenameFilesAsync() + { return base.CanRenameFilesAsync(); } [Fact] - public override Task CanConcurrentlyManageFilesAsync() { + public override Task CanConcurrentlyManageFilesAsync() + { return base.CanConcurrentlyManageFilesAsync(); } [Fact] - public override void CanUseDataDirectory() { + public override void CanUseDataDirectory() + { base.CanUseDataDirectory(); } [Fact] - public override Task CanDeleteEntireFolderAsync() { + public override Task CanDeleteEntireFolderAsync() + { return base.CanDeleteEntireFolderAsync(); } [Fact] - public override Task CanDeleteEntireFolderWithWildcardAsync() { + public override Task CanDeleteEntireFolderWithWildcardAsync() + { return base.CanDeleteEntireFolderWithWildcardAsync(); } [Fact] - public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() + { return base.CanDeleteFolderWithMultiFolderWildcardsAsync(); } [Fact] - public override Task CanDeleteSpecificFilesAsync() { + public override Task CanDeleteSpecificFilesAsync() + { return base.CanDeleteSpecificFilesAsync(); } [Fact] - public override Task CanDeleteNestedFolderAsync() { + public override Task CanDeleteNestedFolderAsync() + { return base.CanDeleteNestedFolderAsync(); } [Fact] - public override Task CanDeleteSpecificFilesInNestedFolderAsync() { + public override Task CanDeleteSpecificFilesInNestedFolderAsync() + { return base.CanDeleteSpecificFilesInNestedFolderAsync(); } [Fact] - public override Task CanRoundTripSeekableStreamAsync() { + public override Task CanRoundTripSeekableStreamAsync() + { return base.CanRoundTripSeekableStreamAsync(); } [Fact] - public override Task WillRespectStreamOffsetAsync() { + public override Task WillRespectStreamOffsetAsync() + { return base.WillRespectStreamOffsetAsync(); } } diff --git a/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs b/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs index 4fce2390b..1fbe12a43 100644 --- a/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/ScopedFolderFileStorageTests.cs @@ -3,106 +3,128 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Storage { - public class ScopedFolderFileStorageTests : FileStorageTestsBase { - public ScopedFolderFileStorageTests(ITestOutputHelper output) : base(output) {} - - protected override IFileStorage GetStorage() { +namespace Foundatio.Tests.Storage +{ + public class ScopedFolderFileStorageTests : FileStorageTestsBase + { + public ScopedFolderFileStorageTests(ITestOutputHelper output) : base(output) { } + + protected override IFileStorage GetStorage() + { return new ScopedFileStorage(new FolderFileStorage(o => o.Folder("|DataDirectory|\\temp")), "scoped"); } [Fact] - public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { + public override Task CanGetEmptyFileListOnMissingDirectoryAsync() + { return base.CanGetEmptyFileListOnMissingDirectoryAsync(); } [Fact] - public override Task CanGetFileListForSingleFolderAsync() { + public override Task CanGetFileListForSingleFolderAsync() + { return base.CanGetFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileListForSingleFileAsync() { + public override Task CanGetFileListForSingleFileAsync() + { return base.CanGetFileListForSingleFileAsync(); } - + [Fact] - public override Task CanGetPagedFileListForSingleFolderAsync() { + public override Task CanGetPagedFileListForSingleFolderAsync() + { return base.CanGetPagedFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileInfoAsync() { + public override Task CanGetFileInfoAsync() + { return base.CanGetFileInfoAsync(); } [Fact] - public override Task CanGetNonExistentFileInfoAsync() { + public override Task CanGetNonExistentFileInfoAsync() + { return base.CanGetNonExistentFileInfoAsync(); } [Fact] - public override Task CanSaveFilesAsync() { + public override Task CanSaveFilesAsync() + { return base.CanSaveFilesAsync(); } [Fact] - public override Task CanManageFilesAsync() { + public override Task CanManageFilesAsync() + { return base.CanManageFilesAsync(); } [Fact] - public override Task CanRenameFilesAsync() { + public override Task CanRenameFilesAsync() + { return base.CanRenameFilesAsync(); } [Fact] - public override Task CanConcurrentlyManageFilesAsync() { + public override Task CanConcurrentlyManageFilesAsync() + { return base.CanConcurrentlyManageFilesAsync(); } [Fact] - public override void CanUseDataDirectory() { + public override void CanUseDataDirectory() + { base.CanUseDataDirectory(); } [Fact] - public override Task CanDeleteEntireFolderAsync() { + public override Task CanDeleteEntireFolderAsync() + { return base.CanDeleteEntireFolderAsync(); } [Fact] - public override Task CanDeleteEntireFolderWithWildcardAsync() { + public override Task CanDeleteEntireFolderWithWildcardAsync() + { return base.CanDeleteEntireFolderWithWildcardAsync(); } [Fact(Skip = "Directory.EnumerateFiles does not support nested folder wildcards")] - public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() + { return base.CanDeleteFolderWithMultiFolderWildcardsAsync(); } [Fact] - public override Task CanDeleteSpecificFilesAsync() { + public override Task CanDeleteSpecificFilesAsync() + { return base.CanDeleteSpecificFilesAsync(); } [Fact] - public override Task CanDeleteNestedFolderAsync() { + public override Task CanDeleteNestedFolderAsync() + { return base.CanDeleteNestedFolderAsync(); } [Fact] - public override Task CanDeleteSpecificFilesInNestedFolderAsync() { + public override Task CanDeleteSpecificFilesInNestedFolderAsync() + { return base.CanDeleteSpecificFilesInNestedFolderAsync(); } [Fact] - public override Task CanRoundTripSeekableStreamAsync() { + public override Task CanRoundTripSeekableStreamAsync() + { return base.CanRoundTripSeekableStreamAsync(); } [Fact] - public override Task WillRespectStreamOffsetAsync() { + public override Task WillRespectStreamOffsetAsync() + { return base.WillRespectStreamOffsetAsync(); } } diff --git a/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs b/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs index 90d8c84b1..c8481a876 100644 --- a/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs +++ b/tests/Foundatio.Tests/Storage/ScopedInMemoryFileStorageTests.cs @@ -3,106 +3,128 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Storage { - public class ScopedInMemoryFileStorageTests : FileStorageTestsBase { - public ScopedInMemoryFileStorageTests(ITestOutputHelper output) : base(output) {} - - protected override IFileStorage GetStorage() { +namespace Foundatio.Tests.Storage +{ + public class ScopedInMemoryFileStorageTests : FileStorageTestsBase + { + public ScopedInMemoryFileStorageTests(ITestOutputHelper output) : base(output) { } + + protected override IFileStorage GetStorage() + { return new ScopedFileStorage(new InMemoryFileStorage { MaxFiles = 2000 }, "scoped"); } [Fact] - public override Task CanGetEmptyFileListOnMissingDirectoryAsync() { + public override Task CanGetEmptyFileListOnMissingDirectoryAsync() + { return base.CanGetEmptyFileListOnMissingDirectoryAsync(); } [Fact] - public override Task CanGetFileListForSingleFolderAsync() { + public override Task CanGetFileListForSingleFolderAsync() + { return base.CanGetFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileListForSingleFileAsync() { + public override Task CanGetFileListForSingleFileAsync() + { return base.CanGetFileListForSingleFileAsync(); } - + [Fact] - public override Task CanGetPagedFileListForSingleFolderAsync() { + public override Task CanGetPagedFileListForSingleFolderAsync() + { return base.CanGetPagedFileListForSingleFolderAsync(); } [Fact] - public override Task CanGetFileInfoAsync() { + public override Task CanGetFileInfoAsync() + { return base.CanGetFileInfoAsync(); } [Fact] - public override Task CanGetNonExistentFileInfoAsync() { + public override Task CanGetNonExistentFileInfoAsync() + { return base.CanGetNonExistentFileInfoAsync(); } [Fact] - public override Task CanSaveFilesAsync() { + public override Task CanSaveFilesAsync() + { return base.CanSaveFilesAsync(); } [Fact] - public override Task CanManageFilesAsync() { + public override Task CanManageFilesAsync() + { return base.CanManageFilesAsync(); } [Fact] - public override Task CanRenameFilesAsync() { + public override Task CanRenameFilesAsync() + { return base.CanRenameFilesAsync(); } [Fact] - public override Task CanConcurrentlyManageFilesAsync() { + public override Task CanConcurrentlyManageFilesAsync() + { return base.CanConcurrentlyManageFilesAsync(); } [Fact] - public override void CanUseDataDirectory() { + public override void CanUseDataDirectory() + { base.CanUseDataDirectory(); } [Fact] - public override Task CanDeleteEntireFolderAsync() { + public override Task CanDeleteEntireFolderAsync() + { return base.CanDeleteEntireFolderAsync(); } [Fact] - public override Task CanDeleteEntireFolderWithWildcardAsync() { + public override Task CanDeleteEntireFolderWithWildcardAsync() + { return base.CanDeleteEntireFolderWithWildcardAsync(); } [Fact] - public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() { + public override Task CanDeleteFolderWithMultiFolderWildcardsAsync() + { return base.CanDeleteFolderWithMultiFolderWildcardsAsync(); } [Fact] - public override Task CanDeleteSpecificFilesAsync() { + public override Task CanDeleteSpecificFilesAsync() + { return base.CanDeleteSpecificFilesAsync(); } [Fact] - public override Task CanDeleteNestedFolderAsync() { + public override Task CanDeleteNestedFolderAsync() + { return base.CanDeleteNestedFolderAsync(); } [Fact] - public override Task CanDeleteSpecificFilesInNestedFolderAsync() { + public override Task CanDeleteSpecificFilesInNestedFolderAsync() + { return base.CanDeleteSpecificFilesInNestedFolderAsync(); } [Fact] - public override Task CanRoundTripSeekableStreamAsync() { + public override Task CanRoundTripSeekableStreamAsync() + { return base.CanRoundTripSeekableStreamAsync(); } [Fact] - public override Task WillRespectStreamOffsetAsync() { + public override Task WillRespectStreamOffsetAsync() + { return base.WillRespectStreamOffsetAsync(); } } diff --git a/tests/Foundatio.Tests/Utility/CloneTests.cs b/tests/Foundatio.Tests/Utility/CloneTests.cs index 7dd05bc91..50bede077 100644 --- a/tests/Foundatio.Tests/Utility/CloneTests.cs +++ b/tests/Foundatio.Tests/Utility/CloneTests.cs @@ -1,26 +1,30 @@ using System; using System.Collections.Generic; -using Foundatio.Xunit; using Foundatio.Serializer; using Foundatio.Utility; +using Foundatio.Xunit; using Newtonsoft.Json.Linq; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Utility { - public class CloneTests : TestWithLoggingBase { +namespace Foundatio.Tests.Utility +{ + public class CloneTests : TestWithLoggingBase + { public CloneTests(ITestOutputHelper output) : base(output) { } [Fact] - public void CanCloneModel() { - var model = new CloneModel { + public void CanCloneModel() + { + var model = new CloneModel + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, HashSet = new HashSet(), - ObjectProperty = new CloneModel { IntProperty = 1 } + ObjectProperty = new CloneModel { IntProperty = 1 } }; - + var cloned = model.DeepClone(); Assert.Equal(model.IntProperty, cloned.IntProperty); Assert.Equal(model.StringProperty, cloned.StringProperty); @@ -32,8 +36,10 @@ public void CanCloneModel() { } [Fact] - public void CanCloneJsonSerializedModel() { - var model = new CloneModel { + public void CanCloneJsonSerializedModel() + { + var model = new CloneModel + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, @@ -58,8 +64,10 @@ public void CanCloneJsonSerializedModel() { } [Fact] - public void CanCloneMessagePackSerializedModel() { - var model = new CloneModel { + public void CanCloneMessagePackSerializedModel() + { + var model = new CloneModel + { IntProperty = 1, StringProperty = "test", ListProperty = new List { 1 }, @@ -84,7 +92,8 @@ public void CanCloneMessagePackSerializedModel() { } } - public class CloneModel { + public class CloneModel + { public int IntProperty { get; set; } public string StringProperty { get; set; } public List ListProperty { get; set; } @@ -93,4 +102,4 @@ public class CloneModel { public ISet HashSet { get; set; } public object ObjectProperty { get; set; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Utility/ConnectionStringParserTests.cs b/tests/Foundatio.Tests/Utility/ConnectionStringParserTests.cs index 3a8b2d6eb..4a3ec6a00 100644 --- a/tests/Foundatio.Tests/Utility/ConnectionStringParserTests.cs +++ b/tests/Foundatio.Tests/Utility/ConnectionStringParserTests.cs @@ -1,11 +1,14 @@ using System; -using Xunit; using Foundatio.Utility; +using Xunit; -namespace Foundatio.Tests.Utility { - public class ConfigurationTests { +namespace Foundatio.Tests.Utility +{ + public class ConfigurationTests + { [Fact] - public void CanParseConnectionString() { + public void CanParseConnectionString() + { const string connectionString = "provider=azurestorage;DefaultEndpointsProtocol=https;AccountName=test;AccountKey=nx4TKwaaaaaaaaaa8t51oPyOErc/4N0TOjrMy6aaaaaabDMbFiK+Gf5rLr6XnU1aaaaaqiX2Yik7tvLcwp4lw==;EndpointSuffix=core.windows.net"; var data = connectionString.ParseConnectionString(); Assert.Equal(5, data.Count); @@ -14,37 +17,40 @@ public void CanParseConnectionString() { Assert.Equal("test", data["AccountName"]); Assert.Equal("nx4TKwaaaaaaaaaa8t51oPyOErc/4N0TOjrMy6aaaaaabDMbFiK+Gf5rLr6XnU1aaaaaqiX2Yik7tvLcwp4lw==", data["AccountKey"]); Assert.Equal("core.windows.net", data["EndpointSuffix"]); - + Assert.Equal(connectionString, data.BuildConnectionString()); } - + [Fact] - public void WillThrowOnInvalidConnectionStrings() { + public void WillThrowOnInvalidConnectionStrings() + { string connectionString = "provider = azurestorage; = ; DefaultEndpointsProtocol = https ;"; Assert.Throws(() => connectionString.ParseConnectionString()); - + connectionString = "http://localhost:9200"; Assert.Throws(() => connectionString.ParseConnectionString()); } [Fact] - public void CanParseQuotedConnectionString() { + public void CanParseQuotedConnectionString() + { const string connectionString = "Blah=\"Hey \"\"now\"\" stuff\""; var data = connectionString.ParseConnectionString(); Assert.Single(data); Assert.Equal("Hey \"now\" stuff", data["blah"]); - + Assert.Equal(connectionString, data.BuildConnectionString()); } - + [Fact] - public void CanParseComplexQuotedConnectionString() { + public void CanParseComplexQuotedConnectionString() + { const string connectionString = "Blah=\"foo1=\"\"my value\"\";foo2 =\"\"my value\"\";\""; var data = connectionString.ParseConnectionString(); Assert.Single(data); Assert.Equal("foo1=\"my value\";foo2 =\"my value\";", data["Blah"]); - + Assert.Equal(connectionString, data.BuildConnectionString()); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Utility/DataDictionaryTests.cs b/tests/Foundatio.Tests/Utility/DataDictionaryTests.cs index 990105fb9..694a1ff03 100644 --- a/tests/Foundatio.Tests/Utility/DataDictionaryTests.cs +++ b/tests/Foundatio.Tests/Utility/DataDictionaryTests.cs @@ -1,19 +1,22 @@ -using Foundatio.Xunit; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using Foundatio.Serializer; using Foundatio.Utility; +using Foundatio.Xunit; using Xunit; using Xunit.Abstractions; -using System.Text.Json; -using System.Text; -using Foundatio.Serializer; -using System; -using System.Collections.Generic; -namespace Foundatio.Tests.Utility { - public class DataDictionaryTests : TestWithLoggingBase { +namespace Foundatio.Tests.Utility +{ + public class DataDictionaryTests : TestWithLoggingBase + { public DataDictionaryTests(ITestOutputHelper output) : base(output) { } [Fact] - public void CanGetData() { + public void CanGetData() + { var serializer = new SystemTextJsonSerializer(); var model = new MyModel(); @@ -66,21 +69,23 @@ public void CanGetData() { Assert.Equal(12, model.GetDataOrDefault("Int16")); Assert.Equal(12, model.GetDataOrDefault("Int32")); Assert.Equal(12, model.GetDataOrDefault("Int64")); - + Assert.Equal(1, model.GetDataOrDefault("bool")); } } - public class MyModel : IHaveData, IHaveSerializer { + public class MyModel : IHaveData, IHaveSerializer + { public int IntProperty { get; set; } public string StringProperty { get; set; } public IDictionary Data { get; } = new DataDictionary(); - public ISerializer Serializer { get; set; } + public ISerializer Serializer { get; set; } } - public class MyDataModel { + public class MyDataModel + { public int IntProperty { get; set; } public string StringProperty { get; set; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Utility/RunTests.cs b/tests/Foundatio.Tests/Utility/RunTests.cs index 21ba946c5..dc5f9412b 100644 --- a/tests/Foundatio.Tests/Utility/RunTests.cs +++ b/tests/Foundatio.Tests/Utility/RunTests.cs @@ -1,43 +1,52 @@ -using Foundatio.Xunit; +using System; +using System.Threading; +using System.Threading.Tasks; using Foundatio.Utility; +using Foundatio.Xunit; +using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -using System.Threading.Tasks; -using System; -using System.Threading; -using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Utility { - public class RunTests : TestWithLoggingBase { +namespace Foundatio.Tests.Utility +{ + public class RunTests : TestWithLoggingBase + { public RunTests(ITestOutputHelper output) : base(output) { } [Fact] - public async Task CanRunWithRetries() { - var task = Task.Run(() => { + public async Task CanRunWithRetries() + { + var task = Task.Run(() => + { _logger.LogInformation("Hi"); }); await task; await task; - await Run.WithRetriesAsync(() => { + await Run.WithRetriesAsync(() => + { return DoStuff(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); - await Run.WithRetriesAsync(async () => { + await Run.WithRetriesAsync(async () => + { await DoStuff(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); } [Fact] - public async Task CanRunWithRetriesAndResult() { - var result = await Run.WithRetriesAsync(() => { + public async Task CanRunWithRetriesAndResult() + { + var result = await Run.WithRetriesAsync(() => + { return ReturnStuff(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); Assert.Equal(1, result); - result = await Run.WithRetriesAsync(async () => { + result = await Run.WithRetriesAsync(async () => + { return await ReturnStuff(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -45,30 +54,37 @@ public async Task CanRunWithRetriesAndResult() { } [Fact] - public async Task CanBoomWithRetries() { - var exception = await Assert.ThrowsAsync(async () => { - await Run.WithRetriesAsync(() => { + public async Task CanBoomWithRetries() + { + var exception = await Assert.ThrowsAsync(async () => + { + await Run.WithRetriesAsync(() => + { return DoBoom(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); }); Assert.Equal("Hi", exception.Message); - exception = await Assert.ThrowsAsync(async () => { - await Run.WithRetriesAsync(async () => { + exception = await Assert.ThrowsAsync(async () => + { + await Run.WithRetriesAsync(async () => + { await DoBoom(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); }); Assert.Equal("Hi", exception.Message); int attempt = 0; - await Run.WithRetriesAsync(() => { + await Run.WithRetriesAsync(() => + { attempt++; return DoBoom(attempt < 5); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); Assert.Equal(5, attempt); attempt = 0; - await Run.WithRetriesAsync(async () => { + await Run.WithRetriesAsync(async () => + { attempt++; await DoBoom(attempt < 5); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -76,9 +92,12 @@ await Run.WithRetriesAsync(async () => { } [Fact] - public async Task CanBoomWithRetriesAndResult() { - var exception = await Assert.ThrowsAsync(async () => { - var result = await Run.WithRetriesAsync(() => { + public async Task CanBoomWithRetriesAndResult() + { + var exception = await Assert.ThrowsAsync(async () => + { + var result = await Run.WithRetriesAsync(() => + { return ReturnBoom(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -86,8 +105,10 @@ public async Task CanBoomWithRetriesAndResult() { }); Assert.Equal("Hi", exception.Message); - exception = await Assert.ThrowsAsync(async () => { - var result = await Run.WithRetriesAsync(async () => { + exception = await Assert.ThrowsAsync(async () => + { + var result = await Run.WithRetriesAsync(async () => + { return await ReturnBoom(); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -96,7 +117,8 @@ public async Task CanBoomWithRetriesAndResult() { Assert.Equal("Hi", exception.Message); int attempt = 0; - var result = await Run.WithRetriesAsync(() => { + var result = await Run.WithRetriesAsync(() => + { attempt++; return ReturnBoom(attempt < 5); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -104,7 +126,8 @@ public async Task CanBoomWithRetriesAndResult() { Assert.Equal(1, result); attempt = 0; - result = await Run.WithRetriesAsync(async () => { + result = await Run.WithRetriesAsync(async () => + { attempt++; return await ReturnBoom(attempt < 5); }, maxAttempts: 5, retryInterval: TimeSpan.FromMilliseconds(10), cancellationToken: CancellationToken.None, logger: _logger); @@ -112,16 +135,19 @@ public async Task CanBoomWithRetriesAndResult() { Assert.Equal(1, result); } - private async Task ReturnStuff() { + private async Task ReturnStuff() + { await Task.Delay(10); return 1; } - private Task DoStuff() { + private Task DoStuff() + { return Task.Delay(10); } - private async Task ReturnBoom(bool shouldThrow = true) { + private async Task ReturnBoom(bool shouldThrow = true) + { await Task.Delay(10); if (shouldThrow) @@ -130,11 +156,12 @@ private async Task ReturnBoom(bool shouldThrow = true) { return 1; } - private async Task DoBoom(bool shouldThrow = true) { + private async Task DoBoom(bool shouldThrow = true) + { await Task.Delay(10); if (shouldThrow) throw new ApplicationException("Hi"); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs b/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs index cd4f676c9..1844c8ec1 100644 --- a/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs +++ b/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs @@ -1,24 +1,29 @@ using System; using System.Threading; using System.Threading.Tasks; -using Foundatio.Xunit; +using Foundatio.AsyncEx; using Foundatio.Tests.Extensions; using Foundatio.Utility; -using Foundatio.AsyncEx; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Utility { - public class ScheduledTimerTests : TestWithLoggingBase { - public ScheduledTimerTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Tests.Utility +{ + public class ScheduledTimerTests : TestWithLoggingBase + { + public ScheduledTimerTests(ITestOutputHelper output) : base(output) + { Log.SetLogLevel(LogLevel.Trace); } [Fact] - public Task CanRun() { + public Task CanRun() + { var resetEvent = new AsyncAutoResetEvent(); - Task Callback() { + Task Callback() + { resetEvent.Set(); return null; } @@ -29,21 +34,25 @@ public Task CanRun() { } [RetryFact] - public Task CanRunAndScheduleConcurrently() { + public Task CanRunAndScheduleConcurrently() + { return CanRunConcurrentlyAsync(); } [Fact] - public Task CanRunWithMinimumInterval() { + public Task CanRunWithMinimumInterval() + { return CanRunConcurrentlyAsync(TimeSpan.FromMilliseconds(100)); } - private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) { + private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) + { Log.MinimumLevel = LogLevel.Trace; const int iterations = 2; var countdown = new AsyncCountdownEvent(iterations); - async Task Callback() { + async Task Callback() + { _logger.LogInformation("Starting work"); await SystemClock.SleepAsync(250); countdown.Signal(); @@ -53,8 +62,10 @@ private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) using var timer = new ScheduledTimer(Callback, minimumIntervalTime: minimumIntervalTime, loggerFactory: Log); timer.ScheduleNext(); - _ = Task.Run(async () => { - for (int i = 0; i < iterations; i++) { + _ = Task.Run(async () => + { + for (int i = 0; i < iterations; i++) + { await SystemClock.SleepAsync(10); timer.ScheduleNext(); } @@ -72,11 +83,13 @@ private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) } [Fact] - public async Task CanRecoverFromError() { + public async Task CanRecoverFromError() + { int hits = 0; var resetEvent = new AsyncAutoResetEvent(false); - Task Callback() { + Task Callback() + { Interlocked.Increment(ref hits); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Callback called for the #{Hits} time", hits); @@ -93,4 +106,4 @@ public async Task CanRecoverFromError() { Assert.Equal(2, hits); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Tests/Utility/SystemClockTests.cs b/tests/Foundatio.Tests/Utility/SystemClockTests.cs index 12513ba01..7e551f0fa 100644 --- a/tests/Foundatio.Tests/Utility/SystemClockTests.cs +++ b/tests/Foundatio.Tests/Utility/SystemClockTests.cs @@ -1,18 +1,22 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Foundatio.Xunit; using Foundatio.Utility; +using Foundatio.Xunit; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Tests.Utility { - public class SystemClockTests : TestWithLoggingBase { - public SystemClockTests(ITestOutputHelper output) : base(output) {} +namespace Foundatio.Tests.Utility +{ + public class SystemClockTests : TestWithLoggingBase + { + public SystemClockTests(ITestOutputHelper output) : base(output) { } [Fact] - public void CanGetTime() { - using (TestSystemClock.Install()) { + public void CanGetTime() + { + using (TestSystemClock.Install()) + { var now = DateTime.UtcNow; TestSystemClock.SetFrozenTime(now); Assert.Equal(now, SystemClock.UtcNow); @@ -24,8 +28,10 @@ public void CanGetTime() { } [Fact] - public void CanSleep() { - using (TestSystemClock.Install()) { + public void CanSleep() + { + using (TestSystemClock.Install()) + { var sw = Stopwatch.StartNew(); SystemClock.Sleep(250); sw.Stop(); @@ -46,8 +52,10 @@ public void CanSleep() { } [Fact] - public async Task CanSleepAsync() { - using (TestSystemClock.Install()) { + public async Task CanSleepAsync() + { + using (TestSystemClock.Install()) + { var sw = Stopwatch.StartNew(); await SystemClock.SleepAsync(250); sw.Stop(); @@ -69,8 +77,10 @@ public async Task CanSleepAsync() { } [Fact] - public void CanSetTimeZone() { - using (TestSystemClock.Install()) { + public void CanSetTimeZone() + { + using (TestSystemClock.Install()) + { var utcNow = DateTime.UtcNow; var now = new DateTime(utcNow.AddHours(1).Ticks, DateTimeKind.Local); TestSystemClock.SetFrozenTime(utcNow); @@ -85,8 +95,10 @@ public void CanSetTimeZone() { } [Fact] - public void CanSetLocalFixedTime() { - using (TestSystemClock.Install()) { + public void CanSetLocalFixedTime() + { + using (TestSystemClock.Install()) + { var now = DateTime.Now; var utcNow = now.ToUniversalTime(); TestSystemClock.SetFrozenTime(now); @@ -100,8 +112,10 @@ public void CanSetLocalFixedTime() { } [Fact] - public void CanSetUtcFixedTime() { - using (TestSystemClock.Install()) { + public void CanSetUtcFixedTime() + { + using (TestSystemClock.Install()) + { var utcNow = DateTime.UtcNow; var now = utcNow.ToLocalTime(); TestSystemClock.SetFrozenTime(utcNow); diff --git a/tests/Foundatio.Tests/Utility/TestUdpListener.cs b/tests/Foundatio.Tests/Utility/TestUdpListener.cs index e92617dd8..2a03e6d4e 100644 --- a/tests/Foundatio.Tests/Utility/TestUdpListener.cs +++ b/tests/Foundatio.Tests/Utility/TestUdpListener.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Foundatio.Tests.Utility { - public class TestUdpListener : IDisposable { +namespace Foundatio.Tests.Utility +{ + public class TestUdpListener : IDisposable + { private readonly List _messages = new(); private UdpClient _listener; private readonly IPEndPoint _localIpEndPoint; @@ -18,51 +20,63 @@ public class TestUdpListener : IDisposable { private CancellationTokenSource _cancellationTokenSource; private readonly object _lock = new(); - public TestUdpListener(string server, int port, ILoggerFactory loggerFactory) { + public TestUdpListener(string server, int port, ILoggerFactory loggerFactory) + { _logger = loggerFactory.CreateLogger(); _localIpEndPoint = new IPEndPoint(IPAddress.Parse(server), port); _senderIpEndPoint = new IPEndPoint(IPAddress.Any, 0); } - public string[] GetMessages() { + public string[] GetMessages() + { return _messages.ToArray(); } - public void ResetMessages() { + public void ResetMessages() + { _logger.LogInformation("ResetMessages"); _messages.Clear(); } - public void StartListening() { - lock (_lock) { - if (_listener != null) { + public void StartListening() + { + lock (_lock) + { + if (_listener != null) + { _logger.LogInformation("StartListening: Already listening"); return; } _logger.LogInformation("StartListening"); _cancellationTokenSource = new CancellationTokenSource(); - _listener = new UdpClient(_localIpEndPoint) { + _listener = new UdpClient(_localIpEndPoint) + { Client = { ReceiveTimeout = 1000 } }; _receiveTask = Task.Factory.StartNew(() => ProcessMessages(_cancellationTokenSource.Token), _cancellationTokenSource.Token); } } - private void ProcessMessages(CancellationToken cancellationToken) { - while (true) { - if (cancellationToken.IsCancellationRequested) { + private void ProcessMessages(CancellationToken cancellationToken) + { + while (true) + { + if (cancellationToken.IsCancellationRequested) + { _logger.LogInformation("Stopped ProcessMessages due to CancellationToken.IsCancellationRequested"); _cancellationTokenSource = null; StopListening(); return; } - try { - lock (_lock) { + try + { + lock (_lock) + { if (_listener == null) break; - + var result = _listener.Receive(ref _senderIpEndPoint); if (result.Length == 0) continue; @@ -71,15 +85,20 @@ private void ProcessMessages(CancellationToken cancellationToken) { _logger.LogInformation("Received message: {Message}", message); _messages.Add(message); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error during ProcessMessages: {Message}", ex.Message); } } } - public void StopListening() { - try { - lock (_lock) { + public void StopListening() + { + try + { + lock (_lock) + { _logger.LogInformation("Closing listener socket"); _listener?.Close(); _listener?.Dispose(); @@ -88,37 +107,45 @@ public void StopListening() { _receiveTask?.Wait(1000); _receiveTask = null; } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error during StopListening: {Message}", ex.Message); } } - - public void StopListening(int expectedMessageCount) { + + public void StopListening(int expectedMessageCount) + { StopListening(expectedMessageCount, new CancellationTokenSource(10000).Token); } - public void StopListening(int expectedMessageCount, CancellationToken cancellationToken) { + public void StopListening(int expectedMessageCount, CancellationToken cancellationToken) + { _logger.LogInformation($"StopListening called, waiting for {expectedMessageCount} expected message(s)"); - while (_messages.Count < expectedMessageCount) { - if (cancellationToken.IsCancellationRequested) { + while (_messages.Count < expectedMessageCount) + { + if (cancellationToken.IsCancellationRequested) + { _logger.LogInformation("Stopped listening due to CancellationToken.IsCancellationRequested"); break; } - if (_cancellationTokenSource.Token.IsCancellationRequested) { + if (_cancellationTokenSource.Token.IsCancellationRequested) + { _logger.LogInformation("Stopped listening due to CancellationTokenSource.IsCancellationRequested"); break; } - + Thread.Sleep(100); } - + _logger.LogInformation("StopListening Count={Count}", _messages.Count); StopListening(); } - public void Dispose() { + public void Dispose() + { _logger.LogInformation("Dispose"); StopListening(); } } -} \ No newline at end of file +}