From d60a8630e4c75d20828519d5c406d9fa9e0d14f4 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 5 Jan 2024 08:19:08 -0600 Subject: [PATCH] dotnet format --- .editorconfig | 255 +++++++------- .../Extensions/NumberExtensions.cs | 15 +- .../Extensions/TaskExtensions.cs | 12 +- samples/Foundatio.SampleJob/PingQueueJob.cs | 20 +- samples/Foundatio.SampleJob/Program.cs | 17 +- .../SampleServiceProvider.cs | 16 +- samples/Foundatio.SampleJobClient/Program.cs | 46 ++- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 265 ++++++++++----- .../Cache/RedisCacheClientOptions.cs | 17 +- .../Cache/RedisHybridCacheClient.cs | 12 +- .../Cache/RedisHybridCacheClientOptions.cs | 18 +- .../Extensions/RedisExtensions.cs | 27 +- .../Extensions/TaskExtensions.cs | 15 +- .../Extensions/TimespanExtensions.cs | 11 +- .../Extensions/TypeExtensions.cs | 21 +- .../Messaging/RedisMessageBus.cs | 62 ++-- .../Messaging/RedisMessageBusOptions.cs | 14 +- .../Metrics/RedisMetricsClient.cs | 9 +- .../Metrics/RedisMetricsClientOptions.cs | 14 +- src/Foundatio.Redis/Queues/RedisQueue.cs | 319 ++++++++++++------ .../Queues/RedisQueueOptions.cs | 29 +- .../Storage/RedisFileStorage.cs | 109 ++++-- .../Storage/RedisFileStorageOptions.cs | 15 +- .../Utility/EmbeddedResourceLoader.cs | 15 +- .../Caching/CacheBenchmarks.cs | 56 ++- .../Foundatio.Benchmarks.csproj | 4 +- tests/Foundatio.Benchmarks/Program.cs | 17 +- .../Queues/JobQueueBenchmarks.cs | 40 ++- .../Queues/QueueBenchmarks.cs | 44 ++- .../Foundatio.Benchmarks/Queues/QueueItem.cs | 8 +- .../Caching/RedisCacheClientTests.cs | 104 ++++-- .../Caching/RedisHybridCacheClientTests.cs | 58 ++-- .../ConnectionMuliplexerExtensions.cs | 20 +- .../Jobs/RedisJobQueueTests.cs | 23 +- .../Locks/RedisLockTests.cs | 46 ++- .../Messaging/RedisMessageBusTests.cs | 93 +++-- .../Metrics/RedisMetricsTests.cs | 58 ++-- .../Properties/AssemblyInfo.cs | 2 +- .../Queues/RedisQueueTests.cs | 260 +++++++++----- .../Foundatio.Redis.Tests/SharedConnection.cs | 9 +- .../Storage/RedisFileStorageTests.cs | 66 ++-- 41 files changed, 1458 insertions(+), 803 deletions(-) diff --git a/.editorconfig b/.editorconfig index dd82c0c..44c634e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,162 +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 = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion - -############################### -# 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.SampleJob/Extensions/NumberExtensions.cs b/samples/Foundatio.SampleJob/Extensions/NumberExtensions.cs index a0b8ce8..861042e 100644 --- a/samples/Foundatio.SampleJob/Extensions/NumberExtensions.cs +++ b/samples/Foundatio.SampleJob/Extensions/NumberExtensions.cs @@ -1,16 +1,21 @@ using System; -namespace Foundatio.Extensions { - internal static class NumericExtensions { - public static string ToOrdinal(this int num) { - switch (num % 100) { +namespace Foundatio.Extensions +{ + internal static class NumericExtensions + { + 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/samples/Foundatio.SampleJob/Extensions/TaskExtensions.cs b/samples/Foundatio.SampleJob/Extensions/TaskExtensions.cs index 892c04e..25722ae 100644 --- a/samples/Foundatio.SampleJob/Extensions/TaskExtensions.cs +++ b/samples/Foundatio.SampleJob/Extensions/TaskExtensions.cs @@ -3,15 +3,19 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; -namespace Foundatio.Extensions { - internal static class TaskExtensions { +namespace Foundatio.Extensions +{ + 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 ConfiguredTaskAwaitable AnyContext(this Task task) { + public static ConfiguredTaskAwaitable AnyContext(this Task task) + { return task.ConfigureAwait(continueOnCapturedContext: false); } } diff --git a/samples/Foundatio.SampleJob/PingQueueJob.cs b/samples/Foundatio.SampleJob/PingQueueJob.cs index f29d0e7..b100748 100644 --- a/samples/Foundatio.SampleJob/PingQueueJob.cs +++ b/samples/Foundatio.SampleJob/PingQueueJob.cs @@ -6,30 +6,35 @@ using Foundatio.Extensions; using Foundatio.Jobs; using Foundatio.Lock; -using Foundatio.Queues; using Foundatio.Messaging; +using Foundatio.Queues; using Foundatio.Utility; using Microsoft.Extensions.Logging; -namespace Foundatio.SampleJob { - public class PingQueueJob : QueueJobBase { +namespace Foundatio.SampleJob +{ + public class PingQueueJob : QueueJobBase + { private readonly ILockProvider _locker; private int _runCount; - public PingQueueJob(IQueue queue, ILoggerFactory loggerFactory, ICacheClient cacheClient, IMessageBus messageBus) : base(queue, loggerFactory) { + public PingQueueJob(IQueue queue, ILoggerFactory loggerFactory, ICacheClient cacheClient, IMessageBus messageBus) : base(queue, loggerFactory) + { AutoComplete = true; _locker = new CacheLockProvider(cacheClient, messageBus, loggerFactory); } public int RunCount => _runCount; - protected override Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = new CancellationToken()) { + protected override Task GetQueueEntryLockAsync(IQueueEntry queueEntry, CancellationToken cancellationToken = new CancellationToken()) + { return _locker.AcquireAsync(String.Concat("pull:", queueEntry.Value.Id), TimeSpan.FromMinutes(30), TimeSpan.FromSeconds(1)); } - protected override async Task ProcessQueueEntryAsync(QueueEntryContext context) { + protected override async Task ProcessQueueEntryAsync(QueueEntryContext context) + { Interlocked.Increment(ref _runCount); if (_logger.IsEnabled(LogLevel.Information)) @@ -43,7 +48,8 @@ protected override async Task ProcessQueueEntryAsync(QueueEntryContex } } - public class PingRequest { + public class PingRequest + { public string Data { get; set; } public string Id { get; set; } public int PercentChanceOfException { get; set; } = 0; diff --git a/samples/Foundatio.SampleJob/Program.cs b/samples/Foundatio.SampleJob/Program.cs index 6bb7a4a..78f41db 100644 --- a/samples/Foundatio.SampleJob/Program.cs +++ b/samples/Foundatio.SampleJob/Program.cs @@ -5,11 +5,14 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Foundatio.SampleJob { - public class Program { +namespace Foundatio.SampleJob +{ + public class Program + { private static ILogger _logger; - public static int Main() { + public static int Main() + { var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); _logger = loggerFactory.CreateLogger("MessageBus"); @@ -20,12 +23,14 @@ public static int Main() { return new JobRunner(jobOptions).RunInConsoleAsync().GetAwaiter().GetResult(); } - private static void HandleEchoMessage(EchoMessage m) { + private static void HandleEchoMessage(EchoMessage m) + { _logger.LogInformation($"Got message: {m.Message}"); } } - public class EchoMessage { + public class EchoMessage + { public string Message { get; set; } } -} \ No newline at end of file +} diff --git a/samples/Foundatio.SampleJob/SampleServiceProvider.cs b/samples/Foundatio.SampleJob/SampleServiceProvider.cs index 8d8ca04..d460b21 100644 --- a/samples/Foundatio.SampleJob/SampleServiceProvider.cs +++ b/samples/Foundatio.SampleJob/SampleServiceProvider.cs @@ -4,16 +4,20 @@ using Foundatio.Messaging; using Foundatio.Metrics; using Foundatio.Queues; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using StackExchange.Redis; -namespace Foundatio.SampleJob { - public class SampleServiceProvider { - public static IServiceProvider Create(ILoggerFactory loggerFactory) { +namespace Foundatio.SampleJob +{ + public class SampleServiceProvider + { + public static IServiceProvider Create(ILoggerFactory loggerFactory) + { var container = new ServiceCollection(); - if (loggerFactory != null) { + if (loggerFactory != null) + { container.AddSingleton(loggerFactory); container.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); } @@ -29,4 +33,4 @@ public static IServiceProvider Create(ILoggerFactory loggerFactory) { return container.BuildServiceProvider(); } } -} \ No newline at end of file +} diff --git a/samples/Foundatio.SampleJobClient/Program.cs b/samples/Foundatio.SampleJobClient/Program.cs index aa69932..6ad86e2 100644 --- a/samples/Foundatio.SampleJobClient/Program.cs +++ b/samples/Foundatio.SampleJobClient/Program.cs @@ -1,13 +1,13 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Foundatio.Queues; using Foundatio.Messaging; +using Foundatio.Queues; using Foundatio.Utility; -using Microsoft.Extensions.Logging; using Foundatio.Xunit; +using Microsoft.Extensions.Logging; using StackExchange.Redis; -using System.Linq; namespace Foundatio.SampleJobClient { @@ -56,7 +56,8 @@ private static void EnqueueContinuousPings(int count, CancellationToken token) } while (!token.IsCancellationRequested); } - private static void HandleKey(ConsoleKey key) { + private static void HandleKey(ConsoleKey key) + { if (key == ConsoleKey.D1) { EnqueuePing(1); @@ -88,11 +89,14 @@ private static void HandleKey(ConsoleKey key) { } } - private static void MonitorKeyPress() { - Task.Run(() => { + private static void MonitorKeyPress() + { + Task.Run(() => + { while (_isRunning) { - while (!Console.KeyAvailable) { + while (!Console.KeyAvailable) + { SystemClock.Sleep(250); } var key = Console.ReadKey(true).Key; @@ -102,10 +106,12 @@ private static void MonitorKeyPress() { }); } - private static void DrawLoop() { + private static void DrawLoop() + { Console.CursorVisible = false; - while (_isRunning) { + while (_isRunning) + { ClearConsoleLines(0, OPTIONS_MENU_LINE_COUNT + SEPERATOR_LINE_COUNT + _loggerFactory.MaxLogEntriesToStore); DrawOptionsMenu(); @@ -138,14 +144,14 @@ private static void DrawOptionsMenu() Console.Write("3: "); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Enqueue continuous"); - + Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("M: "); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Send echo message"); - + Console.WriteLine(); - + Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("S: "); Console.ForegroundColor = ConsoleColor.White; @@ -156,7 +162,8 @@ private static void DrawOptionsMenu() Console.WriteLine("Quit"); } - private static void DrawLogMessages() { + private static void DrawLogMessages() + { Console.SetCursorPosition(0, OPTIONS_MENU_LINE_COUNT + SEPERATOR_LINE_COUNT); foreach (var logEntry in _loggerFactory.LogEntries.ToArray()) { @@ -166,22 +173,24 @@ private static void DrawLogMessages() { Console.ForegroundColor = originalColor; } } - - private static void ClearConsoleLines(int startLine = 0, int endLine = -1) { + + private static void ClearConsoleLines(int startLine = 0, int endLine = -1) + { if (endLine < 0) endLine = Console.WindowHeight - 2; int currentLine = Console.CursorTop; int currentPosition = Console.CursorLeft; - for (int i = startLine; i <= endLine; i++) { + for (int i = startLine; i <= endLine; i++) + { Console.SetCursorPosition(0, i); Console.Write(new string(' ', Console.WindowWidth)); } Console.SetCursorPosition(currentPosition, currentLine); } - + private static ConsoleColor GetColor(LogEntry logEntry) { switch (logEntry.LogLevel) @@ -204,7 +213,8 @@ private static ConsoleColor GetColor(LogEntry logEntry) } } - public class EchoMessage { + public class EchoMessage + { public string Message { get; set; } } diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 4a43e6f..5ff6aee 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -2,17 +2,19 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Foundatio.AsyncEx; using Foundatio.Extensions; using Foundatio.Redis; +using Foundatio.Redis.Utility; using Foundatio.Serializer; -using Foundatio.AsyncEx; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using StackExchange.Redis; -using Foundatio.Redis.Utility; -namespace Foundatio.Caching { - public sealed class RedisCacheClient : ICacheClient, IHaveSerializer { +namespace Foundatio.Caching +{ + public sealed class RedisCacheClient : ICacheClient, IHaveSerializer + { private readonly RedisCacheClientOptions _options; private readonly ILogger _logger; @@ -23,9 +25,10 @@ public sealed class RedisCacheClient : ICacheClient, IHaveSerializer { private LoadedLuaScript _removeIfEqual; private LoadedLuaScript _replaceIfEqual; private LoadedLuaScript _setIfHigher; - private LoadedLuaScript _setIfLower; + private LoadedLuaScript _setIfLower; - public RedisCacheClient(RedisCacheClientOptions options) { + public RedisCacheClient(RedisCacheClientOptions options) + { _options = options; options.Serializer = options.Serializer ?? DefaultSerializer.Instance; _logger = options.LoggerFactory?.CreateLogger(typeof(RedisCacheClient)) ?? NullLogger.Instance; @@ -36,12 +39,14 @@ public RedisCacheClient(Builder _options.ConnectionMultiplexer.GetDatabase(); - - public Task RemoveAsync(string key) { + + public Task RemoveAsync(string key) + { return Database.KeyDeleteAsync(key); } - 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."); @@ -54,28 +59,37 @@ public async Task RemoveIfEqualAsync(string key, T expected) { return result > 0; } - public async Task RemoveAllAsync(IEnumerable keys = null) { - if (keys == null) { + public async Task RemoveAllAsync(IEnumerable keys = null) + { + if (keys == null) + { var endpoints = _options.ConnectionMultiplexer.GetEndPoints(); if (endpoints.Length == 0) return 0; - foreach (var endpoint in endpoints) { + foreach (var endpoint in endpoints) + { var server = _options.ConnectionMultiplexer.GetServer(endpoint); if (server.IsReplica) continue; - try { + try + { await server.FlushDatabaseAsync().AnyContext(); continue; - } catch (Exception) {} + } + catch (Exception) { } - try { + try + { await foreach (var key in server.KeysAsync().ConfigureAwait(false)) await Database.KeyDeleteAsync(key).AnyContext(); - } catch (Exception) {} + } + catch (Exception) { } } - } else { + } + else + { var redisKeys = keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).ToArray(); if (redisKeys.Length > 0) return (int)await Database.KeyDeleteAsync(redisKeys).AnyContext(); @@ -84,7 +98,8 @@ public async Task RemoveAllAsync(IEnumerable keys = null) { return 0; } - public async Task RemoveByPrefixAsync(string prefix) { + public async Task RemoveByPrefixAsync(string prefix) + { const int chunkSize = 2500; string regex = $"{prefix}*"; @@ -93,7 +108,8 @@ public async Task RemoveByPrefixAsync(string prefix) { (int cursor, string[] keys) = await ScanKeysAsync(regex, index, chunkSize).AnyContext(); - while (keys.Length != 0 || index < chunkSize) { + while (keys.Length != 0 || index < chunkSize) + { total += await RemoveAllAsync(keys).AnyContext(); index += chunkSize; (cursor, keys) = await ScanKeysAsync(regex, cursor, chunkSize).AnyContext(); @@ -117,7 +133,8 @@ public async Task RemoveByPrefixAsync(string prefix) { private static readonly RedisValue _nullValue = "@@NULL"; - public async Task> GetAsync(string key) { + public async Task> GetAsync(string key) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); @@ -125,21 +142,26 @@ public async Task> GetAsync(string key) { return RedisValueToCacheValue(redisValue); } - private CacheValue> RedisValuesToCacheValue(RedisValue[] redisValues) { + private CacheValue> RedisValuesToCacheValue(RedisValue[] redisValues) + { var result = new List(); - foreach (var redisValue in redisValues) { + foreach (var redisValue in redisValues) + { if (!redisValue.HasValue) continue; if (redisValue == _nullValue) continue; - try { + try + { var value = redisValue.ToValueOfType(_options.Serializer); result.Add(value); - } catch (Exception ex) { + } + catch (Exception ex) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Unable to deserialize value {Value} to type {Type}", redisValue, typeof(T).FullName); - + if (_options.ShouldThrowOnSerializationError) throw; } @@ -148,25 +170,30 @@ private CacheValue> RedisValuesToCacheValue(RedisValue[] redi return new CacheValue>(result, true); } - private CacheValue RedisValueToCacheValue(RedisValue redisValue) { + private CacheValue RedisValueToCacheValue(RedisValue redisValue) + { if (!redisValue.HasValue) return CacheValue.NoValue; if (redisValue == _nullValue) return CacheValue.Null; - try { + try + { var value = redisValue.ToValueOfType(_options.Serializer); return 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 {Type}", redisValue, typeof(T).FullName); - + if (_options.ShouldThrowOnSerializationError) throw; - + return CacheValue.NoValue; } } - public async Task>> GetAllAsync(IEnumerable keys) { + public async Task>> GetAllAsync(IEnumerable keys) + { string[] keyArray = keys.ToArray(); var values = await Database.StringGetAsync(keyArray.Select(k => (RedisKey)k).ToArray(), _options.ReadMode).AnyContext(); @@ -177,17 +204,21 @@ public async Task>> GetAllAsync(IEnumerable return result; } - 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."); - + if (page.HasValue && page.Value < 1) throw new ArgumentNullException(nameof(page), "Page cannot be less than 1."); - if (!page.HasValue) { + if (!page.HasValue) + { var set = await Database.SortedSetRangeByScoreAsync(key, flags: _options.ReadMode).AnyContext(); return RedisValuesToCacheValue(set); - } else { + } + else + { long start = ((page.Value - 1) * pageSize); long end = start + pageSize - 1; var set = await Database.SortedSetRangeByRankAsync(key, start, end, flags: _options.ReadMode).AnyContext(); @@ -195,11 +226,13 @@ public async Task>> GetListAsync(string key, int? p } } - public async Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { + public async Task AddAsync(string key, T 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) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Removing expired key: {Key}", key); await this.RemoveAsync(key).AnyContext(); @@ -209,14 +242,16 @@ public async Task AddAsync(string key, T value, TimeSpan? expiresIn = n return await InternalSetAsync(key, value, expiresIn, When.NotExists).AnyContext(); } - 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."); if (values == null) throw new ArgumentNullException(nameof(values)); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Removing expired key: {Key}", key); await this.RemoveAsync(key).AnyContext(); @@ -224,21 +259,27 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS } long highestScore = 0; - try { + try + { var items = await Database.SortedSetRangeByRankWithScoresAsync(key, 0, 0, order: Order.Descending); highestScore = items.Length > 0 ? (long)items.First().Score : 0; - } catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) { + } + catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) + { // convert legacy set to sortedset var oldItems = await Database.SetMembersAsync(key).AnyContext(); await Database.KeyDeleteAsync(key).AnyContext(); - if (values is string) { + if (values is string) + { var oldItemValues = new List(); foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) oldItemValues.Add(oldItem); highestScore = await ListAddAsync(key, oldItemValues).AnyContext(); - } else { + } + else + { var oldItemValues = new List(); foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) oldItemValues.Add(oldItem); @@ -248,9 +289,12 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS } var redisValues = new List(); - if (values is string stringValue) { + if (values is string stringValue) + { redisValues.Add(new SortedSetEntry(stringValue.ToRedisValue(_options.Serializer), highestScore + 1)); - } else { + } + else + { var valuesArray = values.ToArray(); for (int i = 0; i < valuesArray.Length; i++) redisValues.Add(new SortedSetEntry(valuesArray[i].ToRedisValue(_options.Serializer), highestScore + i + 1)); @@ -263,14 +307,16 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS return result; } - public async Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { + public async Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); if (values == null) throw new ArgumentNullException(nameof(values)); - if (expiresIn?.Ticks < 0) { + if (expiresIn?.Ticks < 0) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Removing expired key: {Key}", key); await this.RemoveAsync(key).AnyContext(); @@ -284,24 +330,30 @@ public async Task ListRemoveAsync(string key, IEnumerable values, Ti foreach (var value in values) redisValues.Add(value.ToRedisValue(_options.Serializer)); - try { + try + { long result = await Database.SortedSetRemoveAsync(key, redisValues.ToArray()).AnyContext(); if (result > 0 && expiresIn.HasValue) await SetExpirationAsync(key, expiresIn.Value).AnyContext(); return result; - } catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) { + } + catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) + { // convert legacy set to sortedset var oldItems = await Database.SetMembersAsync(key).AnyContext(); await Database.KeyDeleteAsync(key).AnyContext(); - if (values is string) { + if (values is string) + { var oldItemValues = new List(); foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) oldItemValues.Add(oldItem); await ListAddAsync(key, oldItemValues).AnyContext(); - } else { + } + else + { var oldItemValues = new List(); foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) oldItemValues.Add(oldItem); @@ -314,79 +366,98 @@ public async Task ListRemoveAsync(string key, IEnumerable values, Ti } } - public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { + public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); return InternalSetAsync(key, value, expiresIn); } - 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."); await LoadScriptsAsync().AnyContext(); - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { var result = await Database.ScriptEvaluateAsync(_setIfHigher, new { key = (RedisKey)key, value, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (double)result; - } else { + } + else + { var result = await Database.ScriptEvaluateAsync(_setIfHigher, new { key = (RedisKey)key, value, expires = RedisValue.EmptyString }).AnyContext(); return (double)result; } } - 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."); await LoadScriptsAsync().AnyContext(); - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { var result = await Database.ScriptEvaluateAsync(_setIfHigher, new { key = (RedisKey)key, value, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (long)result; - } else { + } + else + { var result = await Database.ScriptEvaluateAsync(_setIfHigher, new { key = (RedisKey)key, value, expires = RedisValue.EmptyString }).AnyContext(); return (long)result; } } - 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."); await LoadScriptsAsync().AnyContext(); - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { var result = await Database.ScriptEvaluateAsync(_setIfLower, new { key = (RedisKey)key, value, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (double)result; - } else { + } + else + { var result = await Database.ScriptEvaluateAsync(_setIfLower, new { key = (RedisKey)key, value, expires = RedisValue.EmptyString }).AnyContext(); return (double)result; } } - 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."); await LoadScriptsAsync().AnyContext(); - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { var result = await Database.ScriptEvaluateAsync(_setIfLower, new { key = (RedisKey)key, value, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (long)result; - } else { + } + else + { var result = await Database.ScriptEvaluateAsync(_setIfLower, new { key = (RedisKey)key, value, expires = RedisValue.EmptyString }).AnyContext(); return (long)result; } } - private Task InternalSetAsync(string key, T value, TimeSpan? expiresIn = null, When when = When.Always, CommandFlags flags = CommandFlags.None) { + private Task InternalSetAsync(string key, T value, TimeSpan? expiresIn = null, When when = When.Always, CommandFlags flags = CommandFlags.None) + { var redisValue = value.ToRedisValue(_options.Serializer); return Database.StringSetAsync(key, redisValue, expiresIn, when, flags); } - 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; @@ -398,14 +469,16 @@ 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)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); return InternalSetAsync(key, value, expiresIn, When.Exists); } - 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."); @@ -418,22 +491,25 @@ public async Task ReplaceIfEqualAsync(string key, T value, T expected, redisResult = await Database.ScriptEvaluateAsync(_replaceIfEqual, new { key = (RedisKey)key, value = redisValue, expected = expectedValue, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); else redisResult = await Database.ScriptEvaluateAsync(_replaceIfEqual, new { key = (RedisKey)key, value = redisValue, expected = expectedValue, expires = "" }).AnyContext(); - + var result = (int)redisResult; return result > 0; } - public async Task IncrementAsync(string key, double amount = 1, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, double amount = 1, 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) + { await this.RemoveAsync(key).AnyContext(); return -1; } - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { await LoadScriptsAsync().AnyContext(); var result = await Database.ScriptEvaluateAsync(_incrementWithExpire, new { key = (RedisKey)key, value = amount, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (double)result; @@ -442,16 +518,19 @@ public async Task IncrementAsync(string key, double amount = 1, TimeSpan return await Database.StringIncrementAsync(key, amount).AnyContext(); } - public async Task IncrementAsync(string key, long amount = 1, TimeSpan? expiresIn = null) { + public async Task IncrementAsync(string key, long amount = 1, 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) + { await this.RemoveAsync(key).AnyContext(); return -1; } - if (expiresIn.HasValue) { + if (expiresIn.HasValue) + { await LoadScriptsAsync().AnyContext(); var result = await Database.ScriptEvaluateAsync(_incrementWithExpire, new { key = (RedisKey)key, value = amount, expires = (int)expiresIn.Value.TotalMilliseconds }).AnyContext(); return (long)result; @@ -460,21 +539,24 @@ public async Task IncrementAsync(string key, long amount = 1, TimeSpan? ex return await Database.StringIncrementAsync(key, amount).AnyContext(); } - public Task ExistsAsync(string key) { + public Task ExistsAsync(string key) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); - + return Database.KeyExistsAsync(key); } - 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."); return Database.KeyTimeToLiveAsync(key); } - public Task SetExpirationAsync(string key, TimeSpan expiresIn) { + public Task SetExpirationAsync(string key, TimeSpan expiresIn) + { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); @@ -484,11 +566,13 @@ public Task SetExpirationAsync(string key, TimeSpan expiresIn) { return Database.KeyExpireAsync(key, expiresIn); } - private async Task LoadScriptsAsync() { + private async Task LoadScriptsAsync() + { if (_scriptsLoaded) return; - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { if (_scriptsLoaded) return; @@ -498,7 +582,8 @@ private async Task LoadScriptsAsync() { var setIfHigher = LuaScript.Prepare(SetIfHigherScript); var setIfLower = LuaScript.Prepare(SetIfLowerScript); - foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) { + foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) + { var server = _options.ConnectionMultiplexer.GetServer(endpoint); if (server.IsReplica) continue; @@ -514,21 +599,23 @@ private async Task LoadScriptsAsync() { } } - private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) { + private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Redis connection restored."); _scriptsLoaded = false; } - public void Dispose() { + public void Dispose() + { _options.ConnectionMultiplexer.ConnectionRestored -= ConnectionMultiplexerOnConnectionRestored; } ISerializer IHaveSerializer.Serializer => _options.Serializer; - private static readonly string IncrementWithScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.IncrementWithExpire.lua"); - private static readonly string RemoveIfEqualScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.RemoveIfEqual.lua"); - private static readonly string ReplaceIfEqualScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.ReplaceIfEqual.lua"); - private static readonly string SetIfHigherScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfHigher.lua"); - private static readonly string SetIfLowerScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfLower.lua"); + private static readonly string IncrementWithScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.IncrementWithExpire.lua"); + private static readonly string RemoveIfEqualScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.RemoveIfEqual.lua"); + private static readonly string ReplaceIfEqualScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.ReplaceIfEqual.lua"); + private static readonly string SetIfHigherScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfHigher.lua"); + private static readonly string SetIfLowerScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfLower.lua"); } } diff --git a/src/Foundatio.Redis/Cache/RedisCacheClientOptions.cs b/src/Foundatio.Redis/Cache/RedisCacheClientOptions.cs index acddc63..98c35fe 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClientOptions.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClientOptions.cs @@ -1,7 +1,9 @@ using StackExchange.Redis; -namespace Foundatio.Caching { - public class RedisCacheClientOptions : SharedOptions { +namespace Foundatio.Caching +{ + public class RedisCacheClientOptions : SharedOptions + { public IConnectionMultiplexer ConnectionMultiplexer { get; set; } /// @@ -16,15 +18,18 @@ public class RedisCacheClientOptions : SharedOptions { } - public class RedisCacheClientOptionsBuilder : SharedOptionsBuilder { - public RedisCacheClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { + public class RedisCacheClientOptionsBuilder : SharedOptionsBuilder + { + public RedisCacheClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) + { Target.ConnectionMultiplexer = connectionMultiplexer; return this; } - public RedisCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) { + public RedisCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) + { Target.ShouldThrowOnSerializationError = shouldThrow; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Cache/RedisHybridCacheClient.cs b/src/Foundatio.Redis/Cache/RedisHybridCacheClient.cs index bb6cb6e..7a2d0f7 100644 --- a/src/Foundatio.Redis/Cache/RedisHybridCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisHybridCacheClient.cs @@ -1,8 +1,10 @@ using Foundatio.Messaging; -namespace Foundatio.Caching { +namespace Foundatio.Caching +{ - public class RedisHybridCacheClient : HybridCacheClient { + public class RedisHybridCacheClient : HybridCacheClient + { public RedisHybridCacheClient(RedisHybridCacheClientOptions options, InMemoryCacheClientOptions localOptions = null) : base(new RedisCacheClient(o => o .ConnectionMultiplexer(options.ConnectionMultiplexer) @@ -13,12 +15,14 @@ public RedisHybridCacheClient(RedisHybridCacheClientOptions options, InMemoryCac .Subscriber(options.ConnectionMultiplexer.GetSubscriber()) .Topic(options.RedisChannelName) .Serializer(options.Serializer) - .LoggerFactory(options.LoggerFactory)), localOptions, options.LoggerFactory) { } + .LoggerFactory(options.LoggerFactory)), localOptions, options.LoggerFactory) + { } public RedisHybridCacheClient(Builder config, Builder localConfig = null) : this(config(new RedisHybridCacheClientOptionsBuilder()).Build(), localConfig(new InMemoryCacheClientOptionsBuilder()).Build()) { } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _distributedCache.Dispose(); _messageBus.Dispose(); diff --git a/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs b/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs index 477c391..1c24dd2 100644 --- a/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs +++ b/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs @@ -1,24 +1,30 @@ using StackExchange.Redis; -namespace Foundatio.Caching { - public class RedisHybridCacheClientOptions : RedisCacheClientOptions { +namespace Foundatio.Caching +{ + public class RedisHybridCacheClientOptions : RedisCacheClientOptions + { public string RedisChannelName { get; set; } = "cache-messages"; } public class RedisHybridCacheClientOptionsBuilder : - SharedOptionsBuilder { + SharedOptionsBuilder + { - public RedisHybridCacheClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { + public RedisHybridCacheClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) + { Target.ConnectionMultiplexer = connectionMultiplexer; return this; } - public RedisHybridCacheClientOptionsBuilder RedisChannelName(string redisChannelName) { + public RedisHybridCacheClientOptionsBuilder RedisChannelName(string redisChannelName) + { Target.RedisChannelName = redisChannelName; return this; } - public RedisHybridCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) { + public RedisHybridCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) + { Target.ShouldThrowOnSerializationError = shouldThrow; return this; } diff --git a/src/Foundatio.Redis/Extensions/RedisExtensions.cs b/src/Foundatio.Redis/Extensions/RedisExtensions.cs index affbfdf..fda32fa 100644 --- a/src/Foundatio.Redis/Extensions/RedisExtensions.cs +++ b/src/Foundatio.Redis/Extensions/RedisExtensions.cs @@ -4,11 +4,14 @@ using Foundatio.Utility; using StackExchange.Redis; -namespace Foundatio.Redis { - internal static class RedisValueExtensions { +namespace Foundatio.Redis +{ + internal static class RedisValueExtensions + { private static readonly RedisValue _nullValue = "@@NULL"; - public static T ToValueOfType(this RedisValue redisValue, ISerializer serializer) { + public static T ToValueOfType(this RedisValue redisValue, ISerializer serializer) + { T value; var type = typeof(T); @@ -22,7 +25,8 @@ public static T ToValueOfType(this RedisValue redisValue, ISerializer seriali return value; } - public static RedisValue ToRedisValue(this T value, ISerializer serializer) { + public static RedisValue ToRedisValue(this T value, ISerializer serializer) + { var redisValue = _nullValue; if (value == null) return redisValue; @@ -69,18 +73,23 @@ public static RedisValue ToRedisValue(this T value, ISerializer serializer) { } } - public static class RedisExtensions { - public static bool IsCluster(this IConnectionMultiplexer muxer) { + public static class RedisExtensions + { + public static bool IsCluster(this IConnectionMultiplexer muxer) + { var configuration = ConfigurationOptions.Parse(muxer.Configuration); if (configuration.Proxy == Proxy.Twemproxy) return true; int standaloneCount = 0, clusterCount = 0, sentinelCount = 0; - foreach (var endPoint in muxer.GetEndPoints()) { + foreach (var endPoint in muxer.GetEndPoints()) + { var server = muxer.GetServer(endPoint); - if (server.IsConnected) { + if (server.IsConnected) + { // count the server types - switch (server.ServerType) { + switch (server.ServerType) + { case ServerType.Twemproxy: case ServerType.Standalone: standaloneCount++; diff --git a/src/Foundatio.Redis/Extensions/TaskExtensions.cs b/src/Foundatio.Redis/Extensions/TaskExtensions.cs index 42ec656..f8fa69d 100644 --- a/src/Foundatio.Redis/Extensions/TaskExtensions.cs +++ b/src/Foundatio.Redis/Extensions/TaskExtensions.cs @@ -4,20 +4,25 @@ using System.Threading.Tasks; using Foundatio.AsyncEx; -namespace Foundatio.Extensions { - internal static class TaskExtensions { +namespace Foundatio.Extensions +{ + 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 ConfiguredTaskAwaitable AnyContext(this Task task) { + public static ConfiguredTaskAwaitable AnyContext(this Task 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.Redis/Extensions/TimespanExtensions.cs b/src/Foundatio.Redis/Extensions/TimespanExtensions.cs index d093a2a..ad90fa5 100644 --- a/src/Foundatio.Redis/Extensions/TimespanExtensions.cs +++ b/src/Foundatio.Redis/Extensions/TimespanExtensions.cs @@ -1,9 +1,12 @@ using System; -namespace Foundatio.Extensions { - internal static class TimespanExtensions { - public static TimeSpan Min(this TimeSpan source, TimeSpan other) { +namespace Foundatio.Extensions +{ + internal static class TimespanExtensions + { + public static TimeSpan Min(this TimeSpan source, TimeSpan other) + { return source.Ticks > other.Ticks ? other : source; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Extensions/TypeExtensions.cs b/src/Foundatio.Redis/Extensions/TypeExtensions.cs index 6d479da..e1c03ad 100644 --- a/src/Foundatio.Redis/Extensions/TypeExtensions.cs +++ b/src/Foundatio.Redis/Extensions/TypeExtensions.cs @@ -1,12 +1,15 @@ using System; using Foundatio.Utility; -namespace Foundatio.Extensions { - internal static class TypeExtensions { - public static bool IsNumeric(this Type type) { +namespace Foundatio.Extensions +{ + internal static class TypeExtensions + { + public static bool IsNumeric(this Type type) + { if (type.IsArray) return false; - + if (type == TypeHelper.ByteType || type == TypeHelper.DecimalType || type == TypeHelper.DoubleType || @@ -19,8 +22,9 @@ public static bool IsNumeric(this Type type) { type == TypeHelper.UInt32Type || type == TypeHelper.UInt64Type) return true; - - switch (Type.GetTypeCode(type)) { + + switch (Type.GetTypeCode(type)) + { case TypeCode.Byte: case TypeCode.Decimal: case TypeCode.Double: @@ -39,7 +43,8 @@ public static bool IsNumeric(this Type type) { } - public static bool IsNullableNumeric(this Type type) { + public static bool IsNullableNumeric(this Type type) + { if (type.IsArray) return false; @@ -47,4 +52,4 @@ public static bool IsNullableNumeric(this Type type) { return t != null && t.IsNumeric(); } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Messaging/RedisMessageBus.cs b/src/Foundatio.Redis/Messaging/RedisMessageBus.cs index 31d027c..d65bd95 100644 --- a/src/Foundatio.Redis/Messaging/RedisMessageBus.cs +++ b/src/Foundatio.Redis/Messaging/RedisMessageBus.cs @@ -1,30 +1,35 @@ using System; -using System.Threading;using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Foundatio.AsyncEx; using Foundatio.Extensions; using Foundatio.Serializer; using Foundatio.Utility; -using Foundatio.AsyncEx; using Microsoft.Extensions.Logging; using StackExchange.Redis; -using System.Collections.Generic; -using System.Linq; -namespace Foundatio.Messaging { - public class RedisMessageBus : MessageBusBase { +namespace Foundatio.Messaging +{ + public class RedisMessageBus : MessageBusBase + { private readonly AsyncLock _lock = new(); private bool _isSubscribed; private ChannelMessageQueue _channelMessageQueue = null; - public RedisMessageBus(RedisMessageBusOptions options) : base(options) {} + public RedisMessageBus(RedisMessageBusOptions options) : base(options) { } public RedisMessageBus(Builder config) : this(config(new RedisMessageBusOptionsBuilder()).Build()) { } - protected override async Task EnsureTopicSubscriptionAsync(CancellationToken cancellationToken) { + protected override async Task EnsureTopicSubscriptionAsync(CancellationToken cancellationToken) + { if (_isSubscribed) return; - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { if (_isSubscribed) return; @@ -37,20 +42,24 @@ protected override async Task EnsureTopicSubscriptionAsync(CancellationToken can } } - private async Task OnMessage(ChannelMessage channelMessage) { + private async Task OnMessage(ChannelMessage channelMessage) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("OnMessage({Channel})", channelMessage.Channel); - if (_subscribers.IsEmpty || !channelMessage.Message.HasValue) { + if (_subscribers.IsEmpty || !channelMessage.Message.HasValue) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("No subscribers ({Channel})", channelMessage.Channel); return; } - + IMessage message; - try { + try + { var envelope = _serializer.Deserialize((byte[])channelMessage.Message); - message = new Message(envelope.Data, DeserializeMessageBody) { + message = new Message(envelope.Data, DeserializeMessageBody) + { Type = envelope.Type, ClrType = GetMappedMessageType(envelope.Type), CorrelationId = envelope.CorrelationId, @@ -59,7 +68,9 @@ private async Task OnMessage(ChannelMessage channelMessage) { foreach (var property in envelope.Properties) message.Properties.Add(property.Key, property.Value); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogWarning(ex, "OnMessage({Channel}) Error deserializing message: {Message}", channelMessage.Channel, ex.Message); return; } @@ -67,9 +78,11 @@ private async Task OnMessage(ChannelMessage channelMessage) { await SendMessageToSubscribersAsync(message).AnyContext(); } - 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) + { var mappedType = GetMappedMessageType(messageType); - if (options.DeliveryDelay.HasValue && options.DeliveryDelay.Value > TimeSpan.Zero) { + if (options.DeliveryDelay.HasValue && options.DeliveryDelay.Value > TimeSpan.Zero) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Schedule delayed message: {MessageType} ({Delay}ms)", messageType, options.DeliveryDelay.Value.TotalMilliseconds); await AddDelayedMessageAsync(mappedType, message, options.DeliveryDelay.Value).AnyContext(); return; @@ -77,7 +90,8 @@ protected override async Task PublishImplAsync(string messageType, object messag if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Message Publish: {MessageType}", messageType); byte[] bodyData = SerializeMessageBody(messageType, message); - byte[] data = _serializer.SerializeToBytes(new RedisMessageEnvelope { + byte[] data = _serializer.SerializeToBytes(new RedisMessageEnvelope + { Type = messageType, Data = bodyData, CorrelationId = options.CorrelationId, @@ -90,11 +104,14 @@ protected override async Task PublishImplAsync(string messageType, object messag await Run.WithRetriesAsync(() => _options.Subscriber.PublishAsync(_options.Topic, data, CommandFlags.FireAndForget), logger: _logger, cancellationToken: cancellationToken).AnyContext(); } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); - if (_isSubscribed) { - using (_lock.Lock()) { + if (_isSubscribed) + { + using (_lock.Lock()) + { if (!_isSubscribed) return; @@ -109,7 +126,8 @@ public override void Dispose() { } } - public class RedisMessageEnvelope { + public class RedisMessageEnvelope + { public string UniqueId { get; set; } public string CorrelationId { get; set; } public string Type { get; set; } diff --git a/src/Foundatio.Redis/Messaging/RedisMessageBusOptions.cs b/src/Foundatio.Redis/Messaging/RedisMessageBusOptions.cs index fe144ef..16dd635 100644 --- a/src/Foundatio.Redis/Messaging/RedisMessageBusOptions.cs +++ b/src/Foundatio.Redis/Messaging/RedisMessageBusOptions.cs @@ -1,15 +1,19 @@ using System; using StackExchange.Redis; -namespace Foundatio.Messaging { - public class RedisMessageBusOptions : SharedMessageBusOptions { +namespace Foundatio.Messaging +{ + public class RedisMessageBusOptions : SharedMessageBusOptions + { public ISubscriber Subscriber { get; set; } } - public class RedisMessageBusOptionsBuilder : SharedMessageBusOptionsBuilder { - public RedisMessageBusOptionsBuilder Subscriber(ISubscriber subscriber) { + public class RedisMessageBusOptionsBuilder : SharedMessageBusOptionsBuilder + { + public RedisMessageBusOptionsBuilder Subscriber(ISubscriber subscriber) + { Target.Subscriber = subscriber; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Metrics/RedisMetricsClient.cs b/src/Foundatio.Redis/Metrics/RedisMetricsClient.cs index 0d6956f..cc328b2 100644 --- a/src/Foundatio.Redis/Metrics/RedisMetricsClient.cs +++ b/src/Foundatio.Redis/Metrics/RedisMetricsClient.cs @@ -1,14 +1,17 @@ using System; using Foundatio.Caching; -namespace Foundatio.Metrics { - public class RedisMetricsClient : CacheBucketMetricsClientBase { +namespace Foundatio.Metrics +{ + public class RedisMetricsClient : CacheBucketMetricsClientBase + { public RedisMetricsClient(RedisMetricsClientOptions options) : base(new RedisCacheClient(o => o.ConnectionMultiplexer(options.ConnectionMultiplexer).LoggerFactory(options.LoggerFactory)), options) { } public RedisMetricsClient(Builder config) : this(config(new RedisMetricsClientOptionsBuilder()).Build()) { } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _cache.Dispose(); } diff --git a/src/Foundatio.Redis/Metrics/RedisMetricsClientOptions.cs b/src/Foundatio.Redis/Metrics/RedisMetricsClientOptions.cs index 387c1be..63ad1f9 100644 --- a/src/Foundatio.Redis/Metrics/RedisMetricsClientOptions.cs +++ b/src/Foundatio.Redis/Metrics/RedisMetricsClientOptions.cs @@ -1,15 +1,19 @@ using System; using StackExchange.Redis; -namespace Foundatio.Metrics { - public class RedisMetricsClientOptions : SharedMetricsClientOptions { +namespace Foundatio.Metrics +{ + public class RedisMetricsClientOptions : SharedMetricsClientOptions + { public IConnectionMultiplexer ConnectionMultiplexer { get; set; } } - public class RedisMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder { - public RedisMetricsClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { + public class RedisMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder + { + public RedisMetricsClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) + { Target.ConnectionMultiplexer = connectionMultiplexer; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Queues/RedisQueue.cs b/src/Foundatio.Redis/Queues/RedisQueue.cs index fc25372..8dbc084 100644 --- a/src/Foundatio.Redis/Queues/RedisQueue.cs +++ b/src/Foundatio.Redis/Queues/RedisQueue.cs @@ -4,19 +4,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Foundatio.AsyncEx; using Foundatio.Caching; using Foundatio.Extensions; using Foundatio.Lock; -using Foundatio.AsyncEx; using Foundatio.Redis; +using Foundatio.Redis.Utility; using Foundatio.Utility; using Microsoft.Extensions.Logging; using StackExchange.Redis; -using Foundatio.Redis.Utility; #pragma warning disable 4014 -namespace Foundatio.Queues { - public class RedisQueue : QueueBase> where T : class { +namespace Foundatio.Queues +{ + public class RedisQueue : QueueBase> where T : class + { private readonly AsyncLock _lock = new(); private readonly AsyncAutoResetEvent _autoResetEvent = new(); private readonly ISubscriber _subscriber; @@ -36,7 +38,8 @@ public class RedisQueue : QueueBase> where T : class private LoadedLuaScript _dequeueId; - public RedisQueue(RedisQueueOptions options) : base(options) { + public RedisQueue(RedisQueueOptions options) : base(options) + { if (options.ConnectionMultiplexer == null) throw new ArgumentException("ConnectionMultiplexer is required."); @@ -63,15 +66,17 @@ public RedisQueue(Builder, RedisQueueOptions> con : this(config(new RedisQueueOptionsBuilder()).Build()) { } public IDatabase Database => _options.ConnectionMultiplexer.GetDatabase(); - + protected override Task EnsureQueueCreatedAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; private bool IsMaintenanceRunning => !_options.RunMaintenanceTasks || _maintenanceTask != null && !_maintenanceTask.IsCanceled && !_maintenanceTask.IsFaulted && !_maintenanceTask.IsCompleted; - private async Task EnsureMaintenanceRunningAsync() { + private async Task EnsureMaintenanceRunningAsync() + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested || IsMaintenanceRunning) return; - using (await _lock.LockAsync(_queueDisposedCancellationTokenSource.Token).AnyContext()) { + using (await _lock.LockAsync(_queueDisposedCancellationTokenSource.Token).AnyContext()) + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested || _maintenanceTask != null) return; @@ -80,11 +85,13 @@ private async Task EnsureMaintenanceRunningAsync() { } } - private async Task EnsureTopicSubscriptionAsync() { + private async Task EnsureTopicSubscriptionAsync() + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested || _isSubscribed) return; - using (await _lock.LockAsync(_queueDisposedCancellationTokenSource.Token).AnyContext()) { + using (await _lock.LockAsync(_queueDisposedCancellationTokenSource.Token).AnyContext()) + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested || _isSubscribed) return; @@ -95,14 +102,16 @@ private async Task EnsureTopicSubscriptionAsync() { } } - protected override Task GetQueueStatsImplAsync() { + protected override Task GetQueueStatsImplAsync() + { var queued = Database.ListLengthAsync(_queueListName); var wait = Database.ListLengthAsync(_waitListName); var working = Database.ListLengthAsync(_workListName); var deadLetter = Database.ListLengthAsync(_deadListName); return Task.WhenAll(queued, wait, working, deadLetter) - .ContinueWith(t => new QueueStats { + .ContinueWith(t => new QueueStats + { Queued = queued.Result + wait.Result, Working = working.Result, Deadletter = deadLetter.Result, @@ -115,13 +124,15 @@ protected override Task GetQueueStatsImplAsync() { }, TaskContinuationOptions.OnlyOnRanToCompletion); } - protected override QueueStats GetMetricsQueueStats() { + protected override QueueStats GetMetricsQueueStats() + { long queued = Database.ListLength(_queueListName); long wait = Database.ListLength(_waitListName); long working = Database.ListLength(_workListName); long deadLetter = Database.ListLength(_deadListName); - return new QueueStats { + return new QueueStats + { Queued = queued + wait, Working = working, Deadletter = deadLetter, @@ -139,11 +150,13 @@ protected override QueueStats GetMetricsQueueStats() { private readonly string _waitListName; private readonly string _deadListName; - private string GetPayloadKey(string id) { + private string GetPayloadKey(string id) + { return String.Concat(_listPrefix, ":", id); } - private TimeSpan GetPayloadTtl() { + private TimeSpan GetPayloadTtl() + { var ttl = TimeSpan.Zero; for (int attempt = 1; attempt <= _options.Retries + 1; attempt++) ttl = ttl.Add(GetRetryDelay(attempt)); @@ -152,43 +165,53 @@ private TimeSpan GetPayloadTtl() { return TimeSpan.FromMilliseconds(Math.Max(ttl.TotalMilliseconds * 1.5, TimeSpan.FromDays(7).TotalMilliseconds)); } - private string GetAttemptsKey(string id) { + private string GetAttemptsKey(string id) + { return String.Concat(_listPrefix, ":", id, ":attempts"); } - private TimeSpan GetAttemptsTtl() { + private TimeSpan GetAttemptsTtl() + { return _payloadTimeToLive; } - private string GetEnqueuedTimeKey(string id) { + private string GetEnqueuedTimeKey(string id) + { return String.Concat(_listPrefix, ":", id, ":enqueued"); } - private string GetDequeuedTimeKey(string id) { + private string GetDequeuedTimeKey(string id) + { return String.Concat(_listPrefix, ":", id, ":dequeued"); } - private string GetRenewedTimeKey(string id) { + private string GetRenewedTimeKey(string id) + { return String.Concat(_listPrefix, ":", id, ":renewed"); } - private TimeSpan GetWorkItemTimeoutTimeTtl() { + private TimeSpan GetWorkItemTimeoutTimeTtl() + { return TimeSpan.FromMilliseconds(Math.Max(_options.WorkItemTimeout.TotalMilliseconds * 1.5, TimeSpan.FromHours(1).TotalMilliseconds)); } - private string GetWaitTimeKey(string id) { + private string GetWaitTimeKey(string id) + { return String.Concat(_listPrefix, ":", id, ":wait"); } - private TimeSpan GetWaitTimeTtl() { + private TimeSpan GetWaitTimeTtl() + { return _payloadTimeToLive; } - private string GetTopicName() { + private string GetTopicName() + { return String.Concat(_listPrefix, ":in"); } - protected override async Task EnqueueImplAsync(T data, QueueEntryOptions options) { + protected override async Task EnqueueImplAsync(T data, QueueEntryOptions options) + { string id = Guid.NewGuid().ToString("N"); if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Queue {Name} enqueue item: {EntryId}", _options.Name, id); @@ -196,13 +219,15 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions throw new NotSupportedException("DeliveryDelay is not supported in the Redis queue implementation."); bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (!await OnEnqueuingAsync(data, options).AnyContext()) { + if (!await OnEnqueuingAsync(data, options).AnyContext()) + { if (isTraceLogLevelEnabled) _logger.LogTrace("Aborting enqueue item: {EntryId}", id); return null; } var now = SystemClock.UtcNow; - var envelope = new RedisPayloadEnvelope { + var envelope = new RedisPayloadEnvelope + { Properties = options.Properties, CorrelationId = options.CorrelationId, Value = data @@ -216,10 +241,13 @@ await Run.WithRetriesAsync(() => Task.WhenAll( Database.ListLeftPushAsync(_queueListName, id) ), logger: _logger).AnyContext(); - try { + try + { _autoResetEvent.Set(); await Run.WithRetriesAsync(() => _subscriber.PublishAsync(GetTopicName(), id), logger: _logger).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { if (isTraceLogLevelEnabled) _logger.LogTrace(ex, "Error publishing topic message"); } @@ -233,48 +261,65 @@ await Run.WithRetriesAsync(() => Task.WhenAll( private readonly List _workers = []; - 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) continue; - try { + try + { await handler(queueEntry, linkedCancellationToken.Token).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { Interlocked.Increment(ref _workerErrorCount); _logger.LogError(ex, "Worker error: {Message}", ex.Message); - if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) { - try { + if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) + { + try + { await queueEntry.AbandonAsync().AnyContext(); - } catch (Exception abandonEx) { + } + catch (Exception abandonEx) + { _logger.LogError(abandonEx, "Worker error abandoning queue entry: {Message}", abandonEx.Message); } } } - 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); } } @@ -284,7 +329,8 @@ 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...", _options.Name); if (!IsMaintenanceRunning) @@ -295,15 +341,18 @@ protected override async Task> DequeueImplAsync(CancellationToken var value = await DequeueIdAsync(linkedCancellationToken).AnyContext(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Initial list value: {Value}", value.IsNullOrEmpty ? "" : value.ToString()); - while (value.IsNullOrEmpty && !linkedCancellationToken.IsCancellationRequested) { + while (value.IsNullOrEmpty && !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(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Waited for dequeue: {Elapsed}", sw.Elapsed.ToString()); @@ -314,8 +363,9 @@ protected override async Task> DequeueImplAsync(CancellationToken if (value.IsNullOrEmpty) return null; - - try { + + try + { var entry = await GetQueueEntryAsync(value).AnyContext(); if (entry == null) return null; @@ -325,22 +375,27 @@ protected override async Task> DequeueImplAsync(CancellationToken _logger.LogDebug("Dequeued item: {Value}", value); return entry; - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error getting dequeued item payload: {Value}", value); throw; } } - public override async Task RenewLockAsync(IQueueEntry entry) { + public override async Task RenewLockAsync(IQueueEntry entry) + { if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Queue {Name} renew lock item: {EntryId}", _options.Name, entry.Id); await Run.WithRetriesAsync(() => _cache.SetAsync(GetRenewedTimeKey(entry.Id), SystemClock.UtcNow.Ticks, GetWorkItemTimeoutTimeTtl()), logger: _logger).AnyContext(); await OnLockRenewedAsync(entry).AnyContext(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Renew lock done: {EntryId}", entry.Id); } - private async Task> GetQueueEntryAsync(string workId) { + private async Task> GetQueueEntryAsync(string workId) + { var payload = await Run.WithRetriesAsync(() => _cache.GetAsync>(GetPayloadKey(workId)), logger: _logger).AnyContext(); - if (payload.IsNull) { + if (payload.IsNull) + { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError("Error getting queue payload: {WorkId}", workId); await Database.ListRemoveAsync(_workListName, workId).AnyContext(); return null; @@ -349,10 +404,11 @@ private async Task> GetQueueEntryAsync(string workId) { var enqueuedTimeTicks = Run.WithRetriesAsync(() => _cache.GetAsync(GetEnqueuedTimeKey(workId), 0), logger: _logger); var attemptsValue = Run.WithRetriesAsync(() => _cache.GetAsync(GetAttemptsKey(workId), 0), logger: _logger); await Task.WhenAll(enqueuedTimeTicks, attemptsValue).AnyContext(); - + var queueEntry = new QueueEntry(workId, payload.Value.CorrelationId, payload.Value.Value, this, new DateTime(enqueuedTimeTicks.Result, DateTimeKind.Utc), attemptsValue.Result + 1); - if (payload.Value.Properties != null) { + if (payload.Value.Properties != null) + { foreach (var property in payload.Value.Properties) queueEntry.Properties.Add(property.Key, property.Value); } @@ -360,14 +416,18 @@ private async Task> GetQueueEntryAsync(string workId) { return queueEntry; } - private async Task DequeueIdAsync(CancellationToken linkedCancellationToken) { - try { - return await Run.WithRetriesAsync(async () => { + private async Task DequeueIdAsync(CancellationToken linkedCancellationToken) + { + try + { + return await Run.WithRetriesAsync(async () => + { var timeout = GetWorkItemTimeoutTimeTtl(); long now = SystemClock.UtcNow.Ticks; await LoadScriptsAsync().AnyContext(); - var result = await Database.ScriptEvaluateAsync(_dequeueId, new { + var result = await Database.ScriptEvaluateAsync(_dequeueId, new + { queueListName = (RedisKey)_queueListName, workListName = (RedisKey)_workListName, listPrefix = _listPrefix, @@ -375,22 +435,27 @@ private async Task DequeueIdAsync(CancellationToken linkedCancellati timeout = timeout.TotalMilliseconds }).AnyContext(); return result.ToString(); - }, 3, TimeSpan.FromMilliseconds(100), linkedCancellationToken, _logger).AnyContext(); - } catch (Exception ex) { + }, 3, TimeSpan.FromMilliseconds(100), linkedCancellationToken, _logger).AnyContext(); + } + catch (Exception ex) + { _logger.LogError(ex, "Queue {Name} dequeue id async error: {Error}", _options.Name, ex.Message); return RedisValue.Null; } } - public override async Task CompleteAsync(IQueueEntry entry) { + public override async Task CompleteAsync(IQueueEntry entry) + { if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Queue {Name} complete item: {EntryId}", _options.Name, entry.Id); - if (entry.IsAbandoned || entry.IsCompleted) { + if (entry.IsAbandoned || entry.IsCompleted) + { //_logger.LogDebug("Queue {Name} item already abandoned or completed: {EntryId}", _options.Name, entry.Id); throw new InvalidOperationException("Queue entry has already been completed or abandoned."); } long result = await Run.WithRetriesAsync(() => Database.ListRemoveAsync(_workListName, entry.Id), logger: _logger).AnyContext(); - if (result == 0) { + if (result == 0) + { _logger.LogDebug("Queue {Name} item not in work list: {EntryId}", _options.Name, entry.Id); throw new InvalidOperationException("Queue entry not in work list, it may have been auto abandoned."); } @@ -410,9 +475,11 @@ await Run.WithRetriesAsync(() => Task.WhenAll( if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Complete done: {EntryId}", entry.Id); } - public override async Task AbandonAsync(IQueueEntry entry) { + public override async Task AbandonAsync(IQueueEntry entry) + { _logger.LogDebug("Queue {Name}:{QueueId} abandon item: {EntryId}", _options.Name, QueueId, entry.Id); - if (entry.IsAbandoned || entry.IsCompleted) { + if (entry.IsAbandoned || entry.IsCompleted) + { _logger.LogError("Queue {Name}:{QueueId} unable to abandon item because already abandoned or completed: {EntryId}", _options.Name, QueueId, entry.Id); throw new InvalidOperationException("Queue entry has already been completed or abandoned."); } @@ -426,7 +493,8 @@ public override async Task AbandonAsync(IQueueEntry entry) { var retryDelay = GetRetryDelay(attempts); _logger.LogInformation("Item: {EntryId}, Retry attempts: {RetryAttempts}, Retries Allowed: {Retries}, Retry Delay: {RetryDelay:g}", entry.Id, attempts - 1, _options.Retries, retryDelay); - if (attempts > _options.Retries) { + if (attempts > _options.Retries) + { _logger.LogInformation("Exceeded retry limit moving to deadletter: {EntryId}", entry.Id); var tx = Database.CreateTransaction(); @@ -444,7 +512,9 @@ await Run.WithRetriesAsync(() => Task.WhenAll( Database.KeyDeleteAsync(GetDequeuedTimeKey(entry.Id)), Database.KeyDeleteAsync(GetWaitTimeKey(entry.Id)) ), logger: _logger).AnyContext(); - } else if (retryDelay > TimeSpan.Zero) { + } + else if (retryDelay > TimeSpan.Zero) + { _logger.LogInformation("Adding item to wait list for future retry: {EntryId}", entry.Id); await Run.WithRetriesAsync(() => Task.WhenAll( @@ -462,9 +532,11 @@ await Run.WithRetriesAsync(() => Task.WhenAll( throw new InvalidOperationException("Queue entry not in work list, it may have been auto abandoned."); await Run.WithRetriesAsync(() => Database.KeyDeleteAsync(GetDequeuedTimeKey(entry.Id)), logger: _logger).AnyContext(); - } else { + } + else + { _logger.LogInformation("Adding item back to queue for retry: {EntryId}", entry.Id); - + await Run.WithRetriesAsync(() => _cache.IncrementAsync(attemptsCacheKey, 1, GetAttemptsTtl()), logger: _logger).AnyContext(); var tx = Database.CreateTransaction(); @@ -489,8 +561,10 @@ await Run.WithRetriesAsync(() => Task.WhenAll( _logger.LogInformation("Abandon complete: {EntryId}", entry.Id); } - private TimeSpan GetRetryDelay(int attempts) { - if (_options.RetryDelay <= TimeSpan.Zero) { + private TimeSpan GetRetryDelay(int attempts) + { + if (_options.RetryDelay <= TimeSpan.Zero) + { return TimeSpan.Zero; } @@ -499,11 +573,13 @@ private TimeSpan GetRetryDelay(int attempts) { return TimeSpan.FromMilliseconds(_options.RetryDelay.TotalMilliseconds * multiplier); } - protected override Task> GetDeadletterItemsImplAsync(CancellationToken cancellationToken) { + protected override Task> GetDeadletterItemsImplAsync(CancellationToken cancellationToken) + { throw new NotImplementedException(); } - public override async Task DeleteQueueAsync() { + public override async Task DeleteQueueAsync() + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Deleting queue: {Name}", _options.Name); await Task.WhenAll( DeleteListAsync(_queueListName), @@ -519,10 +595,12 @@ await Task.WhenAll( _workerErrorCount = 0; } - private async Task DeleteListAsync(string name) { + private async Task DeleteListAsync(string name) + { var itemIds = await Database.ListRangeAsync(name).AnyContext(); var tasks = new List(); - foreach (var id in itemIds) { + foreach (var id in itemIds) + { tasks.AddRange(new Task[] { Database.KeyDeleteAsync(GetPayloadKey(id)), Database.KeyDeleteAsync(GetAttemptsKey(id)), @@ -537,10 +615,12 @@ private async Task DeleteListAsync(string name) { await Task.WhenAll(tasks).AnyContext(); } - private async Task TrimDeadletterItemsAsync(int maxItems) { + private async Task TrimDeadletterItemsAsync(int maxItems) + { var itemIds = (await Database.ListRangeAsync(_deadListName).AnyContext()).Skip(maxItems); var tasks = new List(); - foreach (var id in itemIds) { + foreach (var id in itemIds) + { tasks.AddRange(new Task[] { Database.KeyDeleteAsync(GetPayloadKey(id)), Database.KeyDeleteAsync(GetAttemptsKey(id)), @@ -558,32 +638,38 @@ private async Task TrimDeadletterItemsAsync(int maxItems) { await Task.WhenAll(tasks).AnyContext(); } - private void OnTopicMessage(RedisChannel redisChannel, RedisValue redisValue) { + private void OnTopicMessage(RedisChannel redisChannel, RedisValue redisValue) + { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Queue OnMessage {Name}: {Value}", _options.Name, redisValue); _autoResetEvent.Set(); } - private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) { + private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) + { if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Redis connection restored."); _scriptsLoaded = false; _autoResetEvent.Set(); } - public async Task DoMaintenanceWorkAsync() { + public async Task DoMaintenanceWorkAsync() + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested) return; _logger.LogTrace("Starting DoMaintenance: Name: {Name} Id: {Id}", _options.Name, QueueId); var utcNow = SystemClock.UtcNow; - try { + try + { var workIds = await Database.ListRangeAsync(_workListName).AnyContext(); - foreach (var workId in workIds) { + foreach (var workId in workIds) + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested) return; var renewedTimeTicks = await _cache.GetAsync(GetRenewedTimeKey(workId)).AnyContext(); - if (!renewedTimeTicks.HasValue) { + if (!renewedTimeTicks.HasValue) + { _logger.LogTrace("Skipping {WorkId}: no renewed time", workId); continue; } @@ -596,7 +682,8 @@ public async Task DoMaintenanceWorkAsync() { _logger.LogInformation("{WorkId} Auto abandon item. Renewed: {RenewedTime:o} Current: {UtcNow:o} Timeout: {WorkItemTimeout:g} QueueId: {QueueId}", workId, renewedTime, utcNow, _options.WorkItemTimeout, QueueId); var entry = await GetQueueEntryAsync(workId).AnyContext(); - if (entry == null) { + if (entry == null) + { _logger.LogError("{WorkId} Error getting queue entry for work item timeout", workId); continue; } @@ -605,16 +692,20 @@ public async Task DoMaintenanceWorkAsync() { await AbandonAsync(entry).AnyContext(); Interlocked.Increment(ref _workItemTimeoutCount); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error checking for work item timeouts: {Message}", ex.Message); } if (_queueDisposedCancellationTokenSource.IsCancellationRequested) return; - try { + try + { var waitIds = await Database.ListRangeAsync(_waitListName).AnyContext(); - foreach (var waitId in waitIds) { + foreach (var waitId in waitIds) + { if (_queueDisposedCancellationTokenSource.IsCancellationRequested) return; @@ -637,45 +728,55 @@ public async Task DoMaintenanceWorkAsync() { await Run.WithRetriesAsync(() => _subscriber.PublishAsync(GetTopicName(), waitId), cancellationToken: _queueDisposedCancellationTokenSource.Token, logger: _logger).AnyContext(); } - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error adding items back to the queue after the retry delay: {Message}", ex.Message); } if (_queueDisposedCancellationTokenSource.IsCancellationRequested) return; - try { + try + { await TrimDeadletterItemsAsync(_options.DeadLetterMaxItems).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error trimming deadletter items: {0}", ex.Message); } - + _logger.LogTrace("Finished DoMaintenance: Name: {Name} Id: {Id} Duration: {Duration:g}", _options.Name, QueueId, SystemClock.UtcNow.Subtract(utcNow)); } - private async Task DoMaintenanceWorkLoopAsync() { - while (!_queueDisposedCancellationTokenSource.IsCancellationRequested) { + private async Task DoMaintenanceWorkLoopAsync() + { + while (!_queueDisposedCancellationTokenSource.IsCancellationRequested) + { _logger.LogTrace("Requesting Maintenance Lock. Name: {Name} Id: {Id}", _options.Name, QueueId); - + var utcNow = SystemClock.UtcNow; using var linkedCancellationToken = GetLinkedDisposableCancellationTokenSource(new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token); bool gotLock = await _maintenanceLockProvider.TryUsingAsync($"{_options.Name}-maintenance", DoMaintenanceWorkAsync, cancellationToken: linkedCancellationToken.Token).AnyContext(); - + _logger.LogTrace("{Status} Maintenance Lock. Name: {Name} Id: {Id} Time To Acquire: {AcquireDuration:g}", gotLock ? "Acquired" : "Failed to acquire", _options.Name, QueueId, SystemClock.UtcNow.Subtract(utcNow)); } } - private async Task LoadScriptsAsync() { + private async Task LoadScriptsAsync() + { if (_scriptsLoaded) return; - using (await _lock.LockAsync().AnyContext()) { + using (await _lock.LockAsync().AnyContext()) + { if (_scriptsLoaded) return; var dequeueId = LuaScript.Prepare(DequeueIdScript); - foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) { + foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) + { var server = _options.ConnectionMultiplexer.GetServer(endpoint); if (server.IsReplica) continue; @@ -687,13 +788,17 @@ private async Task LoadScriptsAsync() { } } - public override void Dispose() { + public override void Dispose() + { base.Dispose(); _options.ConnectionMultiplexer.ConnectionRestored -= ConnectionMultiplexerOnConnectionRestored; - if (_isSubscribed) { - lock (_lock.Lock()) { - if (_isSubscribed) { + if (_isSubscribed) + { + lock (_lock.Lock()) + { + if (_isSubscribed) + { _logger.LogTrace("Unsubscribing from topic {Topic}", GetTopicName()); _subscriber.Unsubscribe(GetTopicName(), OnTopicMessage, CommandFlags.FireAndForget); _isSubscribed = false; @@ -703,7 +808,8 @@ public override void Dispose() { } _logger.LogTrace("Got {WorkerCount} workers to cleanup", _workers.Count); - foreach (var worker in _workers) { + foreach (var worker in _workers) + { if (worker.IsCompleted) continue; @@ -718,9 +824,10 @@ public override void Dispose() { private static readonly string DequeueIdScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.DequeueId.lua"); } - public class RedisPayloadEnvelope { + public class RedisPayloadEnvelope + { public string CorrelationId { get; set; } public IDictionary Properties { get; set; } public T Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Queues/RedisQueueOptions.cs b/src/Foundatio.Redis/Queues/RedisQueueOptions.cs index 928f7b3..6a200f8 100644 --- a/src/Foundatio.Redis/Queues/RedisQueueOptions.cs +++ b/src/Foundatio.Redis/Queues/RedisQueueOptions.cs @@ -1,9 +1,11 @@ using System; using StackExchange.Redis; -namespace Foundatio.Queues { +namespace Foundatio.Queues +{ // TODO: Make queue settings immutable and stored in redis so that multiple clients can't have different settings. - public class RedisQueueOptions : SharedQueueOptions where T : class { + public class RedisQueueOptions : SharedQueueOptions where T : class + { public IConnectionMultiplexer ConnectionMultiplexer { get; set; } public TimeSpan RetryDelay { get; set; } = TimeSpan.FromMinutes(1); public int[] RetryMultipliers { get; set; } = { 1, 3, 5, 10 }; @@ -12,35 +14,42 @@ public class RedisQueueOptions : SharedQueueOptions where T : class { public bool RunMaintenanceTasks { get; set; } = true; } - public class RedisQueueOptionsBuilder : SharedQueueOptionsBuilder, RedisQueueOptionsBuilder> where T: class { - public RedisQueueOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { + public class RedisQueueOptionsBuilder : SharedQueueOptionsBuilder, RedisQueueOptionsBuilder> where T : class + { + public RedisQueueOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) + { Target.ConnectionMultiplexer = connectionMultiplexer; return this; } - public RedisQueueOptionsBuilder RetryDelay(TimeSpan retryDelay) { + public RedisQueueOptionsBuilder RetryDelay(TimeSpan retryDelay) + { Target.RetryDelay = retryDelay; return this; } - public RedisQueueOptionsBuilder RetryMultipliers(int[] retryMultipliers) { + public RedisQueueOptionsBuilder RetryMultipliers(int[] retryMultipliers) + { Target.RetryMultipliers = retryMultipliers; return this; } - public RedisQueueOptionsBuilder DeadLetterTimeToLive(TimeSpan deadLetterTimeToLive) { + public RedisQueueOptionsBuilder DeadLetterTimeToLive(TimeSpan deadLetterTimeToLive) + { Target.DeadLetterTimeToLive = deadLetterTimeToLive; return this; } - public RedisQueueOptionsBuilder DeadLetterMaxItems(int deadLetterMaxItems) { + public RedisQueueOptionsBuilder DeadLetterMaxItems(int deadLetterMaxItems) + { Target.DeadLetterMaxItems = deadLetterMaxItems; return this; } - public RedisQueueOptionsBuilder RunMaintenanceTasks(bool runMaintenanceTasks) { + public RedisQueueOptionsBuilder RunMaintenanceTasks(bool runMaintenanceTasks) + { Target.RunMaintenanceTasks = runMaintenanceTasks; return this; } } -} \ No newline at end of file +} diff --git a/src/Foundatio.Redis/Storage/RedisFileStorage.cs b/src/Foundatio.Redis/Storage/RedisFileStorage.cs index 0093b82..daa4113 100644 --- a/src/Foundatio.Redis/Storage/RedisFileStorage.cs +++ b/src/Foundatio.Redis/Storage/RedisFileStorage.cs @@ -12,14 +12,17 @@ using Microsoft.Extensions.Logging.Abstractions; using StackExchange.Redis; -namespace Foundatio.Storage { - public class RedisFileStorage : IFileStorage { +namespace Foundatio.Storage +{ + public class RedisFileStorage : IFileStorage + { private readonly RedisFileStorageOptions _options; private readonly ISerializer _serializer; private readonly ILogger _logger; private readonly string _fileSpecContainer; - public RedisFileStorage(RedisFileStorageOptions options) { + public RedisFileStorage(RedisFileStorageOptions options) + { if (options.ConnectionMultiplexer == null) throw new ArgumentException("ConnectionMultiplexer is required."); @@ -37,7 +40,8 @@ public RedisFileStorage(Builder _serializer; public IDatabase Database => _options.ConnectionMultiplexer.GetDatabase(); - public void Dispose() { + public void Dispose() + { _options.ConnectionMultiplexer.ConnectionRestored -= ConnectionMultiplexerOnConnectionRestored; } @@ -59,7 +63,8 @@ public async Task GetFileStreamAsync(string path, StreamMode streamMode, var fileContent = await Run.WithRetriesAsync(() => Database.HashGetAsync(_options.ContainerName, normalizedPath), cancellationToken: cancellationToken, logger: _logger).AnyContext(); - if (fileContent.IsNull) { + if (fileContent.IsNull) + { _logger.LogError("Unable to get file stream for {Path}: File Not Found", normalizedPath); return null; } @@ -67,7 +72,8 @@ public async Task GetFileStreamAsync(string path, StreamMode streamMode, return new MemoryStream(fileContent); } - public async Task GetFileInfoAsync(string path) { + public async Task GetFileInfoAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -75,7 +81,8 @@ public async Task GetFileInfoAsync(string path) { _logger.LogTrace("Getting file info for {Path}", normalizedPath); var fileSpec = await Run.WithRetriesAsync(() => Database.HashGetAsync(_fileSpecContainer, normalizedPath), logger: _logger).AnyContext(); - if (!fileSpec.HasValue) { + if (!fileSpec.HasValue) + { _logger.LogError("Unable to get file info for {Path}: File Not Found", normalizedPath); return null; } @@ -83,7 +90,8 @@ public async Task GetFileInfoAsync(string path) { return _serializer.Deserialize((byte[])fileSpec); } - public Task ExistsAsync(string path) { + public Task ExistsAsync(string path) + { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -93,7 +101,8 @@ public Task ExistsAsync(string path) { return Run.WithRetriesAsync(() => Database.HashExistsAsync(_fileSpecContainer, normalizedPath), logger: _logger); } - 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) @@ -101,10 +110,11 @@ public async Task SaveFileAsync(string path, Stream stream, CancellationTo string normalizedPath = NormalizePath(path); _logger.LogTrace("Saving {Path}", normalizedPath); - - try { + + try + { var database = Database; - + using var memory = new MemoryStream(); await stream.CopyToAsync(memory, 0x14000, cancellationToken).AnyContext(); var saveFileTask = database.HashSetAsync(_options.ContainerName, normalizedPath, memory.ToArray()); @@ -112,7 +122,8 @@ public async Task SaveFileAsync(string path, Stream stream, CancellationTo memory.Seek(0, SeekOrigin.Begin); memory.SetLength(0); - _serializer.Serialize(new FileSpec { + _serializer.Serialize(new FileSpec + { Path = normalizedPath, Created = DateTime.UtcNow, Modified = DateTime.UtcNow, @@ -123,13 +134,15 @@ await Run.WithRetriesAsync(() => Task.WhenAll(saveFileTask, saveSpecTask), cancellationToken: cancellationToken, logger: _logger).AnyContext(); return true; } - catch (Exception ex) { + catch (Exception ex) + { _logger.LogError(ex, "Error saving {Path}: {Message}", normalizedPath, ex.Message); return false; } } - 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)) @@ -138,18 +151,22 @@ public async Task RenameFileAsync(string path, string newPath, Cancellatio string normalizedPath = NormalizePath(path); string normalizedNewPath = NormalizePath(newPath); _logger.LogInformation("Renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); - - try { + + try + { var stream = await GetFileStreamAsync(normalizedPath, cancellationToken).AnyContext(); return await DeleteFileAsync(normalizedPath, cancellationToken).AnyContext() && await SaveFileAsync(normalizedNewPath, stream, cancellationToken).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error renaming {Path} to {NewPath}: {Message}", normalizedPath, newPath, ex.Message); return false; } } - 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)) @@ -158,20 +175,24 @@ public async Task CopyFileAsync(string path, string targetPath, Cancellati string normalizedPath = NormalizePath(path); string normalizedTargetPath = NormalizePath(targetPath); _logger.LogInformation("Copying {Path} to {TargetPath}", normalizedPath, normalizedTargetPath); - - try { + + try + { using var stream = await GetFileStreamAsync(normalizedPath, cancellationToken).AnyContext(); if (stream == null) return false; return await SaveFileAsync(normalizedTargetPath, stream, cancellationToken).AnyContext(); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError(ex, "Error copying {Path} to {TargetPath}: {Message}", normalizedPath, normalizedTargetPath, ex.Message); return false; } } - 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)); @@ -185,12 +206,14 @@ public async Task DeleteFileAsync(string path, CancellationToken cancellat return true; } - public async Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellationToken = default) { + public async Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellationToken = default) + { var files = await GetFileListAsync(searchPattern, cancellationToken: cancellationToken).AnyContext(); int count = 0; _logger.LogInformation("Deleting {FileCount} files matching {SearchPattern}", files, searchPattern); - foreach (var file in files) { + foreach (var file in files) + { await DeleteFileAsync(file.Path, cancellationToken).AnyContext(); count++; } @@ -199,7 +222,8 @@ public async Task DeleteFilesAsync(string searchPattern = null, Cancellatio return count; } - private Task> GetFileListAsync(string searchPattern = null, int? limit = null, int? skip = null, CancellationToken cancellationToken = default) { + private Task> GetFileListAsync(string searchPattern = null, int? limit = null, int? skip = null, CancellationToken cancellationToken = default) + { if (limit is <= 0) return Task.FromResult(new List()); @@ -207,7 +231,8 @@ private Task> GetFileListAsync(string searchPattern = null, int? string prefix = searchPattern; Regex patternRegex = null; int wildcardPos = searchPattern?.IndexOf('*') ?? -1; - if (searchPattern != null && wildcardPos >= 0) { + if (searchPattern != null && wildcardPos >= 0) + { patternRegex = new Regex($"^{Regex.Escape(searchPattern).Replace("\\*", ".*?")}$"); int slashPos = searchPattern.LastIndexOf('/'); prefix = slashPos >= 0 ? searchPattern.Substring(0, slashPos) : String.Empty; @@ -229,7 +254,8 @@ private Task> GetFileListAsync(string searchPattern = null, int? ); } - 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; @@ -239,7 +265,8 @@ public async Task GetPagedFileListAsync(int pageSize = 100, return result; } - private NextPageResult GetFiles(SearchCriteria criteria, int page, int pageSize) { + private NextPageResult GetFiles(SearchCriteria criteria, int page, int pageSize) + { int pagingLimit = pageSize; int skip = (page - 1) * pagingLimit; if (pagingLimit < Int32.MaxValue) @@ -258,12 +285,14 @@ private NextPageResult GetFiles(SearchCriteria criteria, int page, int pageSize) .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, @@ -271,16 +300,19 @@ private NextPageResult GetFiles(SearchCriteria criteria, int page, int pageSize) }; } - private string NormalizePath(string path) { + private string NormalizePath(string path) + { return path?.Replace('\\', '/'); } - private class SearchCriteria { + private class SearchCriteria + { public string Prefix { get; set; } public Regex Pattern { get; set; } } - private SearchCriteria GetRequestCriteria(string searchPattern) { + private SearchCriteria GetRequestCriteria(string searchPattern) + { if (String.IsNullOrEmpty(searchPattern)) return new SearchCriteria { Prefix = String.Empty }; @@ -291,19 +323,22 @@ private SearchCriteria GetRequestCriteria(string searchPattern) { string prefix = normalizedSearchPattern; Regex patternRegex = null; - if (hasWildcard) { + if (hasWildcard) + { patternRegex = new Regex($"^{Regex.Escape(normalizedSearchPattern).Replace("\\*", ".*?")}$"); int slashPos = normalizedSearchPattern.LastIndexOf('/'); prefix = slashPos >= 0 ? normalizedSearchPattern.Substring(0, slashPos) : String.Empty; } - return new SearchCriteria { + return new SearchCriteria + { Prefix = prefix, Pattern = patternRegex }; } - private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) { + private void ConnectionMultiplexerOnConnectionRestored(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) + { _logger.LogInformation("Redis connection restored"); } } diff --git a/src/Foundatio.Redis/Storage/RedisFileStorageOptions.cs b/src/Foundatio.Redis/Storage/RedisFileStorageOptions.cs index 363a4c3..091ef98 100644 --- a/src/Foundatio.Redis/Storage/RedisFileStorageOptions.cs +++ b/src/Foundatio.Redis/Storage/RedisFileStorageOptions.cs @@ -1,19 +1,24 @@ using System; using StackExchange.Redis; -namespace Foundatio.Storage { - public class RedisFileStorageOptions : SharedOptions { +namespace Foundatio.Storage +{ + public class RedisFileStorageOptions : SharedOptions + { public IConnectionMultiplexer ConnectionMultiplexer { get; set; } public string ContainerName { get; set; } = "storage"; } - public class RedisFileStorageOptionsBuilder : SharedOptionsBuilder { - public RedisFileStorageOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { + public class RedisFileStorageOptionsBuilder : SharedOptionsBuilder + { + public RedisFileStorageOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) + { Target.ConnectionMultiplexer = connectionMultiplexer; return this; } - public RedisFileStorageOptionsBuilder ContainerName(string containerName) { + public RedisFileStorageOptionsBuilder ContainerName(string containerName) + { Target.ContainerName = containerName ?? throw new ArgumentNullException(nameof(containerName)); return this; } diff --git a/src/Foundatio.Redis/Utility/EmbeddedResourceLoader.cs b/src/Foundatio.Redis/Utility/EmbeddedResourceLoader.cs index d1af7bc..e893a51 100644 --- a/src/Foundatio.Redis/Utility/EmbeddedResourceLoader.cs +++ b/src/Foundatio.Redis/Utility/EmbeddedResourceLoader.cs @@ -1,14 +1,17 @@ using System.IO; using System.Reflection; -namespace Foundatio.Redis.Utility { - internal static class EmbeddedResourceLoader { - internal static string GetEmbeddedResource(string name) { - var assembly = typeof(EmbeddedResourceLoader).GetTypeInfo().Assembly; +namespace Foundatio.Redis.Utility +{ + internal static class EmbeddedResourceLoader + { + internal static string GetEmbeddedResource(string name) + { + var assembly = typeof(EmbeddedResourceLoader).GetTypeInfo().Assembly; using var stream = assembly.GetManifestResourceStream(name); using var streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } - } -} \ No newline at end of file + } +} diff --git a/tests/Foundatio.Benchmarks/Caching/CacheBenchmarks.cs b/tests/Foundatio.Benchmarks/Caching/CacheBenchmarks.cs index aa22324..5c95ce1 100644 --- a/tests/Foundatio.Benchmarks/Caching/CacheBenchmarks.cs +++ b/tests/Foundatio.Benchmarks/Caching/CacheBenchmarks.cs @@ -4,14 +4,17 @@ using Foundatio.Messaging; using StackExchange.Redis; -namespace Foundatio.Benchmarks.Caching { - public class CacheBenchmarks { +namespace Foundatio.Benchmarks.Caching +{ + public class CacheBenchmarks + { private const int ITEM_COUNT = 1000; private readonly ICacheClient _inMemoryCache = new InMemoryCacheClient(new InMemoryCacheClientOptions()); private readonly ICacheClient _redisCache; private readonly ICacheClient _hybridCacheClient; - public CacheBenchmarks() { + public CacheBenchmarks() + { var muxer = ConnectionMultiplexer.Connect("localhost"); _redisCache = new RedisCacheClient(new RedisCacheClientOptions { ConnectionMultiplexer = muxer }); _redisCache.RemoveAllAsync().GetAwaiter().GetResult(); @@ -19,62 +22,79 @@ public CacheBenchmarks() { } [Benchmark] - public void ProcessInMemoryCache() { + public void ProcessInMemoryCache() + { Process(_inMemoryCache); } [Benchmark] - public void ProcessRedisCache() { + public void ProcessRedisCache() + { Process(_redisCache); } [Benchmark] - public void ProcessHybridRedisCache() { + public void ProcessHybridRedisCache() + { Process(_hybridCacheClient); } [Benchmark] - public void ProcessInMemoryCacheWithConstantInvalidation() { + public void ProcessInMemoryCacheWithConstantInvalidation() + { Process(_inMemoryCache, true); } [Benchmark] - public void ProcessRedisCacheWithConstantInvalidation() { + public void ProcessRedisCacheWithConstantInvalidation() + { Process(_redisCache, true); } [Benchmark] - public void ProcessHybridRedisCacheWithConstantInvalidation() { + public void ProcessHybridRedisCacheWithConstantInvalidation() + { Process(_hybridCacheClient, true); } - private void Process(ICacheClient cache, bool useSingleKey = false) { - try { - for (int i = 0; i < ITEM_COUNT; i++) { + private void Process(ICacheClient cache, bool useSingleKey = false) + { + try + { + for (int i = 0; i < ITEM_COUNT; i++) + { string key = useSingleKey ? "test" : String.Concat("test", i); cache.SetAsync(key, new CacheItem { Id = i }, TimeSpan.FromHours(1)).GetAwaiter().GetResult(); } - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine(ex); } - try { - for (int i = 0; i < ITEM_COUNT; i++) { + try + { + for (int i = 0; i < ITEM_COUNT; i++) + { string key = useSingleKey ? "test" : String.Concat("test", i); var entry = cache.GetAsync(key).GetAwaiter().GetResult(); } - for (int i = 0; i < ITEM_COUNT; i++) { + for (int i = 0; i < ITEM_COUNT; i++) + { string key = useSingleKey ? "test" : "test0"; var entry = cache.GetAsync(key).GetAwaiter().GetResult(); } - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine(ex); } } } - public class CacheItem { + public class CacheItem + { public int Id { get; set; } } } diff --git a/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj b/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj index 767b996..fea3286 100644 --- a/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj +++ b/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj @@ -6,6 +6,6 @@ - + - \ No newline at end of file + diff --git a/tests/Foundatio.Benchmarks/Program.cs b/tests/Foundatio.Benchmarks/Program.cs index 1d42794..193af10 100644 --- a/tests/Foundatio.Benchmarks/Program.cs +++ b/tests/Foundatio.Benchmarks/Program.cs @@ -5,9 +5,12 @@ using Foundatio.Benchmarks.Caching; using Foundatio.Benchmarks.Queues; -namespace Foundatio.Benchmarks { - public class Program { - public static void Main(string[] args) { +namespace Foundatio.Benchmarks +{ + public class Program + { + public static void Main(string[] args) + { var summary = BenchmarkRunner.Run(); Console.WriteLine(summary.ToString()); @@ -20,9 +23,11 @@ public static void Main(string[] args) { } } - public class BenchmarkConfig : ManualConfig { - public BenchmarkConfig() { + public class BenchmarkConfig : ManualConfig + { + public BenchmarkConfig() + { AddJob(Job.Default.WithWarmupCount(1).WithIterationCount(1)); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Benchmarks/Queues/JobQueueBenchmarks.cs b/tests/Foundatio.Benchmarks/Queues/JobQueueBenchmarks.cs index 7431da9..c360196 100644 --- a/tests/Foundatio.Benchmarks/Queues/JobQueueBenchmarks.cs +++ b/tests/Foundatio.Benchmarks/Queues/JobQueueBenchmarks.cs @@ -6,59 +6,73 @@ using Microsoft.Extensions.Logging; using StackExchange.Redis; -namespace Foundatio.Benchmarks.Queues { - public class JobQueueBenchmarks { +namespace Foundatio.Benchmarks.Queues +{ + public class JobQueueBenchmarks + { private const int ITEM_COUNT = 1000; private readonly IQueue _inMemoryQueue = new InMemoryQueue(); private readonly IQueue _redisQueue = new RedisQueue(o => o.ConnectionMultiplexer(ConnectionMultiplexer.Connect("localhost"))); [IterationSetup] - public void Setup() { + public void Setup() + { _inMemoryQueue.DeleteQueueAsync().GetAwaiter().GetResult(); _redisQueue.DeleteQueueAsync().GetAwaiter().GetResult(); } [IterationSetup(Target = nameof(RunInMemoryJobUntilEmptyAsync))] - public Task EnqueueInMemoryQueueAsync() { + public Task EnqueueInMemoryQueueAsync() + { return EnqueueQueueAsync(_inMemoryQueue); } [Benchmark] - public Task RunInMemoryJobUntilEmptyAsync() { + public Task RunInMemoryJobUntilEmptyAsync() + { return RunJobUntilEmptyAsync(_inMemoryQueue); } [IterationSetup(Target = nameof(RunRedisQueueJobUntilEmptyAsync))] - public Task EnqueueRedisQueueAsync() { + public Task EnqueueRedisQueueAsync() + { return EnqueueQueueAsync(_redisQueue); } [Benchmark] - public Task RunRedisQueueJobUntilEmptyAsync() { + public Task RunRedisQueueJobUntilEmptyAsync() + { return RunJobUntilEmptyAsync(_redisQueue); } - private async Task EnqueueQueueAsync(IQueue queue) { - try { + private async Task EnqueueQueueAsync(IQueue queue) + { + try + { for (int i = 0; i < ITEM_COUNT; i++) await queue.EnqueueAsync(new QueueItem { Id = i }); - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine(ex); } } - private Task RunJobUntilEmptyAsync(IQueue queue) { + private Task RunJobUntilEmptyAsync(IQueue queue) + { var job = new BenchmarkJobQueue(queue); return job.RunUntilEmptyAsync(); } } - public class BenchmarkJobQueue : QueueJobBase { + public class BenchmarkJobQueue : QueueJobBase + { public BenchmarkJobQueue(Lazy> queue, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) { } public BenchmarkJobQueue(IQueue queue, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) { } - protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { + protected override Task ProcessQueueEntryAsync(QueueEntryContext context) + { return Task.FromResult(JobResult.Success); } } diff --git a/tests/Foundatio.Benchmarks/Queues/QueueBenchmarks.cs b/tests/Foundatio.Benchmarks/Queues/QueueBenchmarks.cs index e9df74e..f7114e5 100644 --- a/tests/Foundatio.Benchmarks/Queues/QueueBenchmarks.cs +++ b/tests/Foundatio.Benchmarks/Queues/QueueBenchmarks.cs @@ -3,56 +3,72 @@ using Foundatio.Queues; using StackExchange.Redis; -namespace Foundatio.Benchmarks.Queues { - public class QueueBenchmarks { +namespace Foundatio.Benchmarks.Queues +{ + public class QueueBenchmarks + { private const int ITEM_COUNT = 1000; private readonly IQueue _inMemoryQueue = new InMemoryQueue(); private readonly IQueue _redisQueue = new RedisQueue(o => o.ConnectionMultiplexer(ConnectionMultiplexer.Connect("localhost"))); [IterationSetup] - public void Setup() { + public void Setup() + { _inMemoryQueue.DeleteQueueAsync().GetAwaiter().GetResult(); _redisQueue.DeleteQueueAsync().GetAwaiter().GetResult(); } [IterationSetup(Target = nameof(DequeueInMemoryQueue))] [Benchmark] - public void EnqueueInMemoryQueue() { + public void EnqueueInMemoryQueue() + { EnqueueQueue(_inMemoryQueue); } [Benchmark] - public void DequeueInMemoryQueue() { + public void DequeueInMemoryQueue() + { DequeueQueue(_inMemoryQueue); } [IterationSetup(Target = nameof(DequeueRedisQueue))] [Benchmark] - public void EnqueueRedisQueue() { + public void EnqueueRedisQueue() + { EnqueueQueue(_redisQueue); } [Benchmark] - public void DequeueRedisQueue() { + public void DequeueRedisQueue() + { DequeueQueue(_redisQueue); } - private void EnqueueQueue(IQueue queue) { - try { + private void EnqueueQueue(IQueue queue) + { + try + { for (int i = 0; i < ITEM_COUNT; i++) queue.EnqueueAsync(new QueueItem { Id = i }).GetAwaiter().GetResult(); - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine(ex); } } - private void DequeueQueue(IQueue queue) { - try { - for (int i = 0; i < ITEM_COUNT; i++) { + private void DequeueQueue(IQueue queue) + { + try + { + for (int i = 0; i < ITEM_COUNT; i++) + { var entry = queue.DequeueAsync(TimeSpan.Zero).GetAwaiter().GetResult(); entry.CompleteAsync().GetAwaiter().GetResult(); } - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine(ex); } } diff --git a/tests/Foundatio.Benchmarks/Queues/QueueItem.cs b/tests/Foundatio.Benchmarks/Queues/QueueItem.cs index fb46a76..0b2b9f8 100644 --- a/tests/Foundatio.Benchmarks/Queues/QueueItem.cs +++ b/tests/Foundatio.Benchmarks/Queues/QueueItem.cs @@ -1,7 +1,9 @@ using System; -namespace Foundatio.Benchmarks.Queues { - public class QueueItem { +namespace Foundatio.Benchmarks.Queues +{ + public class QueueItem + { public int Id { get; set; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 36bdc5a..82c827c 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -9,69 +9,84 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Caching { - public class RedisCacheClientTests : CacheClientTestsBase { - public RedisCacheClientTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Caching +{ + public class RedisCacheClientTests : CacheClientTestsBase + { + public RedisCacheClientTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } - protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { + protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return new RedisCacheClient(o => o.ConnectionMultiplexer(SharedConnection.GetMuxer(Log)).LoggerFactory(Log).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(); } @@ -80,63 +95,75 @@ public override Task CanRemoveByPrefixAsync() { [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - 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 CanRemoveIfEqual() { + public override Task CanRemoveIfEqual() + { return base.CanRemoveIfEqual(); } [Fact] - public override Task CanReplaceIfEqual() { + public override Task CanReplaceIfEqual() + { return base.CanReplaceIfEqual(); } [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 CanUpgradeListType() { + public async Task CanUpgradeListType() + { var db = SharedConnection.GetMuxer(Log).GetDatabase(); var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { var items = new List(); for (int i = 1; i < 20001; i++) items.Add(Guid.NewGuid().ToString()); @@ -161,12 +188,14 @@ public async Task CanUpgradeListType() { } [Fact] - public async Task CanManageLargeListsAsync() { + public async Task CanManageLargeListsAsync() + { var cache = GetCacheClient(); if (cache == null) return; - using (cache) { + using (cache) + { await cache.RemoveAllAsync(); var items = new List(); @@ -176,12 +205,12 @@ public async Task CanManageLargeListsAsync() { foreach (var batch in Batch(items, 1000)) await cache.ListAddAsync("largelist", batch); - + var pagedResult = await cache.GetListAsync("largelist", 1, 5); Assert.NotNull(pagedResult); Assert.Equal(5, pagedResult.Value.Count); Assert.Equal(pagedResult.Value.ToArray(), new[] { items[0], items[1], items[2], items[3], items[4] }); - + pagedResult = await cache.GetListAsync("largelist", 2, 5); Assert.NotNull(pagedResult); Assert.Equal(5, pagedResult.Value.Count); @@ -202,7 +231,7 @@ public async Task CanManageLargeListsAsync() { result = await cache.ListRemoveAsync("largelist", items[1]); Assert.Equal(1, result); - + pagedResult = await cache.GetListAsync("largelist", 1, 5); Assert.NotNull(pagedResult); Assert.Equal(5, pagedResult.Value.Count); @@ -211,20 +240,23 @@ public async Task CanManageLargeListsAsync() { } [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(); } - + private IEnumerable> Batch(IList source, int size) { if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index 37ff826..65871a2 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -7,14 +7,18 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Caching { - public class RedisHybridCacheClientTests : HybridCacheClientTests { - public RedisHybridCacheClientTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Caching +{ + public class RedisHybridCacheClientTests : HybridCacheClientTests + { + public RedisHybridCacheClientTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } - protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) { + protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationError = true) + { return new RedisHybridCacheClient(o => o .ConnectionMultiplexer(SharedConnection.GetMuxer(Log)) .LoggerFactory(Log).ShouldThrowOnSerializationError(shouldThrowOnSerializationError), @@ -24,22 +28,26 @@ protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationEr } [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(); } @@ -48,53 +56,63 @@ public override Task CanRemoveByPrefixAsync() { [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - 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] - public override Task WillUseLocalCache() { + public override Task WillUseLocalCache() + { return base.WillUseLocalCache(); } [Fact(Skip = "Skipping for now until we figure out a timing issue")] - public override Task WillExpireRemoteItems() { + public override Task WillExpireRemoteItems() + { Log.MinimumLevel = LogLevel.Trace; return base.WillExpireRemoteItems(); } [Fact] - public override Task WillWorkWithSets() { + public override Task WillWorkWithSets() + { 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(); } } diff --git a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMuliplexerExtensions.cs b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMuliplexerExtensions.cs index 8eac228..702abdd 100644 --- a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMuliplexerExtensions.cs +++ b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMuliplexerExtensions.cs @@ -2,27 +2,33 @@ using System.Threading.Tasks; using StackExchange.Redis; -namespace Foundatio.Redis.Tests.Extensions { - public static class ConnectionMultiplexerExtensions { - public static async Task FlushAllAsync(this ConnectionMultiplexer muxer) { +namespace Foundatio.Redis.Tests.Extensions +{ + public static class ConnectionMultiplexerExtensions + { + public static async Task FlushAllAsync(this ConnectionMultiplexer muxer) + { var endpoints = muxer.GetEndPoints(); if (endpoints.Length == 0) return; - foreach (var endpoint in endpoints) { + foreach (var endpoint in endpoints) + { var server = muxer.GetServer(endpoint); if (!server.IsReplica) await server.FlushAllDatabasesAsync(); } } - public static async Task CountAllKeysAsync(this ConnectionMultiplexer muxer) { + public static async Task CountAllKeysAsync(this ConnectionMultiplexer muxer) + { var endpoints = muxer.GetEndPoints(); if (endpoints.Length == 0) return 0; long count = 0; - foreach (var endpoint in endpoints) { + foreach (var endpoint in endpoints) + { var server = muxer.GetServer(endpoint); if (!server.IsReplica) count += await server.DatabaseSizeAsync(); @@ -31,4 +37,4 @@ public static async Task CountAllKeysAsync(this ConnectionMultiplexer muxe return count; } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/Jobs/RedisJobQueueTests.cs b/tests/Foundatio.Redis.Tests/Jobs/RedisJobQueueTests.cs index e5ae549..58ed966 100644 --- a/tests/Foundatio.Redis.Tests/Jobs/RedisJobQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Jobs/RedisJobQueueTests.cs @@ -6,14 +6,18 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Jobs { - public class RedisJobQueueTests : JobQueueTestsBase { - public RedisJobQueueTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Jobs +{ + public class RedisJobQueueTests : JobQueueTestsBase + { + public RedisJobQueueTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } - protected override IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay) { + protected override IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay) + { return new RedisQueue(o => o .ConnectionMultiplexer(SharedConnection.GetMuxer(Log)) .Retries(retries) @@ -23,18 +27,21 @@ protected override IQueue GetSampleWorkItemQueue(int retrie } [Fact] - public override Task CanRunMultipleQueueJobsAsync() { + public override Task CanRunMultipleQueueJobsAsync() + { return base.CanRunMultipleQueueJobsAsync(); } [Fact] - public override Task CanRunQueueJobWithLockFailAsync() { + public override Task CanRunQueueJobWithLockFailAsync() + { return base.CanRunQueueJobWithLockFailAsync(); } [Fact] - public override Task CanRunQueueJobAsync() { + public override Task CanRunQueueJobAsync() + { return base.CanRunQueueJobAsync(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/Locks/RedisLockTests.cs b/tests/Foundatio.Redis.Tests/Locks/RedisLockTests.cs index d3ccc48..4fe7301 100644 --- a/tests/Foundatio.Redis.Tests/Locks/RedisLockTests.cs +++ b/tests/Foundatio.Redis.Tests/Locks/RedisLockTests.cs @@ -1,54 +1,63 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; -using Foundatio.Lock; using Foundatio.Caching; -using Xunit; -using Xunit.Abstractions; +using Foundatio.Lock; using Foundatio.Messaging; using Foundatio.Redis.Tests.Extensions; using Foundatio.Tests.Locks; +using Foundatio.Utility; using Foundatio.Xunit; using Microsoft.Extensions.Logging; -using Foundatio.Utility; -using System.Diagnostics; +using Xunit; +using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Locks { - public class RedisLockTests : LockTestBase, IDisposable { +namespace Foundatio.Redis.Tests.Locks +{ + public class RedisLockTests : LockTestBase, IDisposable + { private readonly ICacheClient _cache; private readonly IMessageBus _messageBus; - public RedisLockTests(ITestOutputHelper output) : base(output) { + public RedisLockTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); _cache = new RedisCacheClient(o => o.ConnectionMultiplexer(muxer).LoggerFactory(Log)); _messageBus = new RedisMessageBus(o => o.Subscriber(muxer.GetSubscriber()).Topic("test-lock").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 CanAcquireLocksInParallel() { + public override Task CanAcquireLocksInParallel() + { return base.CanAcquireLocksInParallel(); } [Fact] - public override Task CanAcquireAndReleaseLockAsync() { + public override Task CanAcquireAndReleaseLockAsync() + { return base.CanAcquireAndReleaseLockAsync(); } [Fact] - public override Task LockWillTimeoutAsync() { + public override Task LockWillTimeoutAsync() + { return base.LockWillTimeoutAsync(); } [Fact] - public async Task LockWontTimeoutEarly() { + public async Task LockWontTimeoutEarly() + { Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); Log.SetLogLevel(LogLevel.Trace); @@ -83,16 +92,19 @@ public async Task LockWontTimeoutEarly() { } [RetryFact] - public override Task WillThrottleCallsAsync() { + public override Task WillThrottleCallsAsync() + { return base.WillThrottleCallsAsync(); } [Fact] - public override Task LockOneAtATimeAsync() { + public override Task LockOneAtATimeAsync() + { return base.LockOneAtATimeAsync(); } - public void Dispose() { + public void Dispose() + { _cache.Dispose(); _messageBus.Dispose(); var muxer = SharedConnection.GetMuxer(Log); diff --git a/tests/Foundatio.Redis.Tests/Messaging/RedisMessageBusTests.cs b/tests/Foundatio.Redis.Tests/Messaging/RedisMessageBusTests.cs index 9e3083c..e946645 100644 --- a/tests/Foundatio.Redis.Tests/Messaging/RedisMessageBusTests.cs +++ b/tests/Foundatio.Redis.Tests/Messaging/RedisMessageBusTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Foundatio.AsyncEx; using Foundatio.Caching; using Foundatio.Messaging; using Foundatio.Queues; @@ -8,141 +9,169 @@ using Foundatio.Tests.Extensions; using Foundatio.Tests.Messaging; using Foundatio.Tests.Queue; -using Foundatio.AsyncEx; using Xunit; using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Messaging { - public class RedisMessageBusTests : MessageBusTestBase { - public RedisMessageBusTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Messaging +{ + public class RedisMessageBusTests : MessageBusTestBase + { + public RedisMessageBusTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } - protected override IMessageBus GetMessageBus(Func config = null) { - return new RedisMessageBus(o => { + protected override IMessageBus GetMessageBus(Func config = null) + { + return new RedisMessageBus(o => + { o.Subscriber(SharedConnection.GetMuxer(Log).GetSubscriber()); o.Topic("test-messages"); o.LoggerFactory(Log); if (config != null) config(o.Target); - + return o; }); } [Fact] - public override Task CanUseMessageOptionsAsync() { + public override Task CanUseMessageOptionsAsync() + { return base.CanUseMessageOptionsAsync(); } [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(); } [Fact] - public async Task CanDisposeCacheAndQueueAndReceiveSubscribedMessages() { + public async Task CanDisposeCacheAndQueueAndReceiveSubscribedMessages() + { var muxer = SharedConnection.GetMuxer(Log); var messageBus1 = new RedisMessageBus(new RedisMessageBusOptions { Subscriber = muxer.GetSubscriber(), Topic = "test-messages", LoggerFactory = Log }); var cache = new RedisCacheClient(new RedisCacheClientOptions { ConnectionMultiplexer = muxer }); Assert.NotNull(cache); - var queue = new RedisQueue(new RedisQueueOptions { + var queue = new RedisQueue(new RedisQueueOptions + { ConnectionMultiplexer = muxer, LoggerFactory = Log }); Assert.NotNull(queue); - using (messageBus1) { - using (cache) { - using (queue) { + using (messageBus1) + { + using (cache) + { + using (queue) + { await cache.SetAsync("test", "test", TimeSpan.FromSeconds(10)); await queue.DequeueAsync(new CancellationToken(true)); var countdown = new AsyncCountdownEvent(2); - await messageBus1.SubscribeAsync(msg => { + await messageBus1.SubscribeAsync(msg => + { Assert.Equal("Hello", msg.Data); countdown.Signal(); }); @@ -162,4 +191,4 @@ await messageBus1.SubscribeAsync(msg => { } } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/Metrics/RedisMetricsTests.cs b/tests/Foundatio.Redis.Tests/Metrics/RedisMetricsTests.cs index 423d316..ca2a82a 100644 --- a/tests/Foundatio.Redis.Tests/Metrics/RedisMetricsTests.cs +++ b/tests/Foundatio.Redis.Tests/Metrics/RedisMetricsTests.cs @@ -3,62 +3,73 @@ using Foundatio.Metrics; using Foundatio.Redis.Tests.Extensions; using Foundatio.Tests.Metrics; - +using Foundatio.Xunit; using Xunit; using Xunit.Abstractions; -using Foundatio.Xunit; -namespace Foundatio.Redis.Tests.Metrics { - public class RedisMetricsTests : MetricsClientTestBase, IDisposable { - public RedisMetricsTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Metrics +{ + public class RedisMetricsTests : MetricsClientTestBase, IDisposable + { + public RedisMetricsTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } #pragma warning disable CS0618 // Type or member is obsolete - public override IMetricsClient GetMetricsClient(bool buffered = false) { + public override IMetricsClient GetMetricsClient(bool buffered = false) + { return new RedisMetricsClient(o => o.ConnectionMultiplexer(SharedConnection.GetMuxer(Log)).Buffered(buffered).LoggerFactory(Log)); } #pragma warning restore CS0618 // Type or member is obsolete [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() { + public override Task CanWaitForCounterAsync() + { 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(); } - + [Fact] - public async Task SendGaugesAsync() { + public async Task SendGaugesAsync() + { using var metrics = GetMetricsClient(); if (!(metrics is IMetricsClientStats stats)) return; int max = 1000; - for (int index = 0; index <= max; index++) { + for (int index = 0; index <= max; index++) + { metrics.Gauge("mygauge", index); metrics.Timer("mygauge", index); } @@ -67,13 +78,15 @@ public async Task SendGaugesAsync() { } [Fact] - public async Task SendGaugesBufferedAsync() { + public async Task SendGaugesBufferedAsync() + { using var metrics = GetMetricsClient(true); if (!(metrics is IMetricsClientStats stats)) return; int max = 1000; - for (int index = 0; index <= max; index++) { + for (int index = 0; index <= max; index++) + { metrics.Gauge("mygauge", index); metrics.Timer("mygauge", index); } @@ -85,18 +98,21 @@ public async Task SendGaugesBufferedAsync() { } [Fact] - public async Task SendRedisAsync() { + public async Task SendRedisAsync() + { var db = SharedConnection.GetMuxer(Log).GetDatabase(); int max = 1000; - for (int index = 0; index <= max; index++) { + for (int index = 0; index <= max; index++) + { await db.SetAddAsync("test", index); } } - public void Dispose() { + public void Dispose() + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/Properties/AssemblyInfo.cs b/tests/Foundatio.Redis.Tests/Properties/AssemblyInfo.cs index b946c3e..3142569 100644 --- a/tests/Foundatio.Redis.Tests/Properties/AssemblyInfo.cs +++ b/tests/Foundatio.Redis.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.Redis.Tests/Queues/RedisQueueTests.cs b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs index 63863d8..e06b3b1 100644 --- a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs @@ -1,38 +1,42 @@ 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.Lock; -using Foundatio.Tests.Extensions; using Foundatio.Messaging; using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Redis.Tests.Extensions; +using Foundatio.Tests.Extensions; using Foundatio.Tests.Queue; +using Foundatio.Tests.Utility; using Foundatio.Utility; -using Foundatio.AsyncEx; +using Foundatio.Xunit; using Microsoft.Extensions.Logging; +using StackExchange.Redis; using Xunit; using Xunit.Abstractions; -using Foundatio.Xunit; -using Foundatio.Tests.Utility; -using StackExchange.Redis; -using System.Diagnostics; #pragma warning disable 4014 -namespace Foundatio.Redis.Tests.Queues { - public class RedisQueueTests : QueueTestBase { - public RedisQueueTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Queues +{ + public class RedisQueueTests : QueueTestBase + { + public RedisQueueTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); while (muxer.CountAllKeysAsync().GetAwaiter().GetResult() != 0) muxer.FlushAllAsync().GetAwaiter().GetResult(); } - 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) + { var queue = new RedisQueue(o => o .ConnectionMultiplexer(SharedConnection.GetMuxer(Log)) .Retries(retries) @@ -43,118 +47,140 @@ protected override IQueue GetQueue(int retries = 1, TimeSpan? wo .RunMaintenanceTasks(runQueueMaintenance) .LoggerFactory(Log) ); - + _logger.LogDebug("Queue Id: {queueId}", queue.QueueId); return queue; } [Fact] - public override Task CanQueueAndDequeueWorkItemAsync() { + public override Task CanQueueAndDequeueWorkItemAsync() + { return base.CanQueueAndDequeueWorkItemAsync(); } [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 CanRenewLockAsync() { + public override Task CanRenewLockAsync() + { return base.CanRenewLockAsync(); } [Fact] - public override Task CanHandleErrorInWorkerAsync() { + public override Task CanHandleErrorInWorkerAsync() + { return base.CanHandleErrorInWorkerAsync(); } [Fact] - public override Task WorkItemsWillTimeoutAsync() { + public override Task WorkItemsWillTimeoutAsync() + { 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(); } [RetryFact] - 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 CanAbandonQueueEntryOnceAsync() { + public override Task CanAbandonQueueEntryOnceAsync() + { return base.CanAbandonQueueEntryOnceAsync(); } [Fact] - public override Task CanCompleteQueueEntryOnceAsync() { + public override Task CanCompleteQueueEntryOnceAsync() + { return base.CanCompleteQueueEntryOnceAsync(); } [Fact] - public override Task CanHandleAutoAbandonInWorker() { + public override Task CanHandleAutoAbandonInWorker() + { return base.CanHandleAutoAbandonInWorker(); } [Fact(Skip = "Need to fix some sort of left over metrics from other tests issue")] - public override Task CanUseQueueOptionsAsync() { + public override Task CanUseQueueOptionsAsync() + { return base.CanUseQueueOptionsAsync(); } [RetryFact] - public override async Task CanDequeueWithLockingAsync() { + public override async Task CanDequeueWithLockingAsync() + { var muxer = SharedConnection.GetMuxer(Log); using var cache = new RedisCacheClient(new RedisCacheClientOptions { ConnectionMultiplexer = muxer, LoggerFactory = Log }); using var messageBus = new RedisMessageBus(new RedisMessageBusOptions { Subscriber = muxer.GetSubscriber(), Topic = "test-queue", LoggerFactory = Log }); @@ -163,7 +189,8 @@ public override async Task CanDequeueWithLockingAsync() { } [Fact] - public override async Task CanHaveMultipleQueueInstancesWithLockingAsync() { + public override async Task CanHaveMultipleQueueInstancesWithLockingAsync() + { var muxer = SharedConnection.GetMuxer(Log); using var cache = new RedisCacheClient(new RedisCacheClientOptions { ConnectionMultiplexer = muxer, LoggerFactory = Log }); using var messageBus = new RedisMessageBus(new RedisMessageBusOptions { Subscriber = muxer.GetSubscriber(), Topic = "test-queue", LoggerFactory = Log }); @@ -172,12 +199,14 @@ public override async Task CanHaveMultipleQueueInstancesWithLockingAsync() { } [Fact] - public async Task VerifyCacheKeysAreCorrect() { + public async Task VerifyCacheKeysAreCorrect() + { var queue = GetQueue(retries: 3, workItemTimeout: TimeSpan.FromSeconds(2), retryDelay: TimeSpan.Zero, runQueueMaintenance: false); if (queue == null) return; - using (queue) { + using (queue) + { var muxer = SharedConnection.GetMuxer(Log); var db = muxer.GetDatabase(); string listPrefix = muxer.IsCluster() ? "{q:SimpleWorkItem}" : "q:SimpleWorkItem"; @@ -224,18 +253,22 @@ public async Task VerifyCacheKeysAreCorrect() { } [Fact] - public async Task VerifyCacheKeysAreCorrectAfterAbandon() { + public async Task VerifyCacheKeysAreCorrectAfterAbandon() + { var queue = GetQueue(retries: 2, workItemTimeout: TimeSpan.FromMilliseconds(100), retryDelay: TimeSpan.Zero, runQueueMaintenance: false) as RedisQueue; if (queue == null) return; - using (TestSystemClock.Install()) { - using (queue) { + using (TestSystemClock.Install()) + { + using (queue) + { var muxer = SharedConnection.GetMuxer(Log); var db = muxer.GetDatabase(); string listPrefix = muxer.IsCluster() ? "{q:SimpleWorkItem}" : "q:SimpleWorkItem"; - string id = await queue.EnqueueAsync(new SimpleWorkItem { + string id = await queue.EnqueueAsync(new SimpleWorkItem + { Data = "blah", Id = 1 }); @@ -292,18 +325,22 @@ public async Task VerifyCacheKeysAreCorrectAfterAbandon() { } [Fact] - public async Task VerifyCacheKeysAreCorrectAfterAbandonWithRetryDelay() { + public async Task VerifyCacheKeysAreCorrectAfterAbandonWithRetryDelay() + { var queue = GetQueue(retries: 2, workItemTimeout: TimeSpan.FromMilliseconds(100), retryDelay: TimeSpan.FromMilliseconds(250), runQueueMaintenance: false) as RedisQueue; if (queue == null) return; - using (TestSystemClock.Install()) { - using (queue) { + using (TestSystemClock.Install()) + { + using (queue) + { var muxer = SharedConnection.GetMuxer(Log); var db = muxer.GetDatabase(); string listPrefix = muxer.IsCluster() ? "{q:SimpleWorkItem}" : "q:SimpleWorkItem"; - string id = await queue.EnqueueAsync(new SimpleWorkItem { + string id = await queue.EnqueueAsync(new SimpleWorkItem + { Data = "blah", Id = 1 }); @@ -355,24 +392,28 @@ public async Task VerifyCacheKeysAreCorrectAfterAbandonWithRetryDelay() { } [Fact] - public async Task CanTrimDeadletterItems() { + public async Task CanTrimDeadletterItems() + { var queue = GetQueue(retries: 0, workItemTimeout: TimeSpan.FromMilliseconds(50), deadLetterMaxItems: 3, runQueueMaintenance: false) as RedisQueue; if (queue == null) return; - using (queue) { + using (queue) + { var muxer = SharedConnection.GetMuxer(Log); var db = muxer.GetDatabase(); string listPrefix = muxer.IsCluster() ? "{q:SimpleWorkItem}" : "q:SimpleWorkItem"; var workItemIds = new List(); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) + { string id = await queue.EnqueueAsync(new SimpleWorkItem { Data = "blah", Id = i }); _logger.LogTrace(id); workItemIds.Add(id); } - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) + { var workItem = await queue.DequeueAsync(); await workItem.AbandonAsync(); _logger.LogTrace("Abandoning: " + workItem.Id); @@ -381,7 +422,8 @@ public async Task CanTrimDeadletterItems() { workItemIds.Reverse(); await queue.DoMaintenanceWorkAsync(); - foreach (object id in workItemIds.Take(3)) { + foreach (object id in workItemIds.Take(3)) + { _logger.LogTrace("Checking: " + id); Assert.True(await db.KeyExistsAsync("q:SimpleWorkItem:" + id)); } @@ -395,7 +437,8 @@ public async Task CanTrimDeadletterItems() { } [Fact] - public async Task VerifyFirstDequeueTimeout() { + public async Task VerifyFirstDequeueTimeout() + { var workItemTimeout = TimeSpan.FromMilliseconds(100); var itemData = "blah"; @@ -405,7 +448,8 @@ public async Task VerifyFirstDequeueTimeout() { if (queue == null) return; - using (queue) { + using (queue) + { await queue.DeleteQueueAsync(); // Start DequeueAsync but allow it to yield. @@ -416,7 +460,8 @@ public async Task VerifyFirstDequeueTimeout() { await SystemClock.SleepAsync(workItemTimeout.Add(TimeSpan.FromMilliseconds(1))); // Add an item. DequeueAsync can now return. - string id = await queue.EnqueueAsync(new SimpleWorkItem { + string id = await queue.EnqueueAsync(new SimpleWorkItem + { Data = itemData, Id = itemId }); @@ -438,7 +483,8 @@ public async Task VerifyFirstDequeueTimeout() { // test to reproduce issue #64 - https://github.com/FoundatioFx/Foundatio.Redis/issues/64 //[Fact(Skip ="This test needs to simulate database timeout which makes the runtime ~5 sec which might be too big to be run automatically")] [Fact] - public async Task DatabaseTimeoutDuringDequeueHandledCorectly_Issue64() { + public async Task DatabaseTimeoutDuringDequeueHandledCorectly_Issue64() + { // not using GetQueue() here because I need to change the ops timeout in the redis connection string const int OPS_TIMEOUT_MS = 100; string connectionString = Configuration.GetConnectionString("RedisConnectionString") + $",syncTimeout={OPS_TIMEOUT_MS},asyncTimeout={OPS_TIMEOUT_MS}"; ; @@ -452,7 +498,8 @@ public async Task DatabaseTimeoutDuringDequeueHandledCorectly_Issue64() { .RunMaintenanceTasks(false) ); - using (queue) { + using (queue) + { await queue.DeleteQueueAsync(); // enqueue item to queue, no reader yet @@ -477,7 +524,8 @@ public async Task DatabaseTimeoutDuringDequeueHandledCorectly_Issue64() { database.ScriptEvaluateAsync(databaseDelayScript); var completion = new TaskCompletionSource(); - await queue.StartWorkingAsync(async (item) => { + await queue.StartWorkingAsync(async (item) => + { await item.CompleteAsync(); completion.SetResult(true); }); @@ -489,14 +537,16 @@ await queue.StartWorkingAsync(async (item) => { // or it might have moved to work, in this case we want to make sure the correct keys were created var stopwatch = Stopwatch.StartNew(); bool success = false; - while (stopwatch.Elapsed.TotalSeconds < 10) { + while (stopwatch.Elapsed.TotalSeconds < 10) + { string workListName = $"q:{QUEUE_NAME}:work"; long workListLen = await database.ListLengthAsync(new RedisKey(workListName)); var item = await database.ListLeftPopAsync(workListName); string dequeuedItemKey = String.Concat("q:", QUEUE_NAME, ":", item, ":dequeued"); bool dequeuedItemKeyExists = await database.KeyExistsAsync(new RedisKey(dequeuedItemKey)); - if (workListLen == 1) { + if (workListLen == 1) + { Assert.True(dequeuedItemKeyExists); success = true; break; @@ -504,7 +554,8 @@ await queue.StartWorkingAsync(async (item) => { var timeoutCancellationTokenSource = new CancellationTokenSource(); var completedTask = await Task.WhenAny(completion.Task, Task.Delay(TimeSpan.FromMilliseconds(100), timeoutCancellationTokenSource.Token)); - if (completion.Task == completedTask) { + if (completion.Task == completedTask) + { success = true; break; } @@ -513,21 +564,25 @@ await queue.StartWorkingAsync(async (item) => { Assert.True(success); } } - + // TODO: Need to write tests that verify the cache data is correct after each operation. [Fact(Skip = "Performance Test")] - public async Task MeasureThroughputWithRandomFailures() { + public async Task MeasureThroughputWithRandomFailures() + { var queue = GetQueue(retries: 3, workItemTimeout: TimeSpan.FromSeconds(2), retryDelay: TimeSpan.Zero); if (queue == null) return; - using (queue) { + using (queue) + { await queue.DeleteQueueAsync(); const int workItemCount = 1000; - 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" }); } @@ -535,7 +590,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); var workItem = await queue.DequeueAsync(TimeSpan.Zero); - while (workItem != null) { + while (workItem != null) + { Assert.Equal("Hello", workItem.Value.Data); if (RandomData.GetBool(10)) await workItem.AbandonAsync(); @@ -558,17 +614,21 @@ await queue.EnqueueAsync(new SimpleWorkItem { } [Fact(Skip = "Performance Test")] - public async Task MeasureThroughput() { + public async Task MeasureThroughput() + { var queue = GetQueue(retries: 3, workItemTimeout: TimeSpan.FromSeconds(2), retryDelay: TimeSpan.FromSeconds(1)); if (queue == null) return; - using (queue) { + using (queue) + { await queue.DeleteQueueAsync(); const int workItemCount = 1000; - 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" }); } @@ -576,7 +636,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); var workItem = await queue.DequeueAsync(TimeSpan.Zero); - while (workItem != null) { + while (workItem != null) + { Assert.Equal("Hello", workItem.Value.Data); await workItem.CompleteAsync(); metrics.Counter("work"); @@ -596,17 +657,21 @@ await queue.EnqueueAsync(new SimpleWorkItem { } [Fact(Skip = "Performance Test")] - public async Task MeasureWorkerThroughput() { + public async Task MeasureWorkerThroughput() + { var queue = GetQueue(retries: 3, workItemTimeout: TimeSpan.FromSeconds(2), retryDelay: TimeSpan.FromSeconds(1)); if (queue == null) return; - using (queue) { + using (queue) + { await queue.DeleteQueueAsync(); const int workItemCount = 1; - 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" }); } @@ -614,7 +679,8 @@ await queue.EnqueueAsync(new SimpleWorkItem { var countdown = new AsyncCountdownEvent(workItemCount); var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - await queue.StartWorkingAsync(async workItem => { + await queue.StartWorkingAsync(async workItem => + { Assert.Equal("Hello", workItem.Value.Data); await workItem.CompleteAsync(); metrics.Counter("work"); @@ -636,17 +702,20 @@ await queue.StartWorkingAsync(async workItem => { } [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 async Task CanHaveDifferentMessageTypeInQueueWithSameNameAsync() { + public async Task CanHaveDifferentMessageTypeInQueueWithSameNameAsync() + { await HandlerCommand1Async(); await HandlerCommand2Async(); @@ -656,7 +725,8 @@ public async Task CanHaveDifferentMessageTypeInQueueWithSameNameAsync() { await Publish2Async(); } - private IQueue CreateQueue(bool allQueuesTheSameName = true) where T : class { + private IQueue CreateQueue(bool allQueuesTheSameName = true) where T : class + { var name = typeof(T).FullName.Trim().Replace(".", string.Empty).ToLower(); if (allQueuesTheSameName) @@ -672,7 +742,8 @@ private IQueue CreateQueue(bool allQueuesTheSameName = true) where T : cla return queue; } - private Task HandlerCommand1Async() { + private Task HandlerCommand1Async() + { var q = CreateQueue(); return q.StartWorkingAsync((entry, token) => @@ -683,7 +754,8 @@ private Task HandlerCommand1Async() { }); } - private Task HandlerCommand2Async() { + private Task HandlerCommand2Async() + { var q = CreateQueue(); return q.StartWorkingAsync((entry, token) => @@ -694,44 +766,52 @@ private Task HandlerCommand2Async() { }, true); } - private async Task Publish1Async() { + private async Task Publish1Async() + { var q = CreateQueue(); - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) + { var cmd = new Command1(100 + i); _logger.LogInformation($"{DateTime.UtcNow:O}: Publish\tCommand1 {cmd.Id}"); await q.EnqueueAsync(cmd); } } - private async Task Publish2Async() { + private async Task Publish2Async() + { var q = CreateQueue(); - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) + { var cmd = new Command2(200 + i); _logger.LogInformation($"{DateTime.UtcNow:O}: Publish\tCommand2 {cmd.Id}"); await q.EnqueueAsync(cmd); } } - public class Command1 { + public class Command1 + { public Command1() { } - public Command1(int id) { + public Command1(int id) + { Id = id; } public int Id { get; set; } } - public class Command2 { - public Command2() {} + public class Command2 + { + public Command2() { } - public Command2(int id) { + public Command2(int id) + { Id = id; } public int Id { get; set; } } } -} \ No newline at end of file +} diff --git a/tests/Foundatio.Redis.Tests/SharedConnection.cs b/tests/Foundatio.Redis.Tests/SharedConnection.cs index 386c8f2..3ad7ebd 100644 --- a/tests/Foundatio.Redis.Tests/SharedConnection.cs +++ b/tests/Foundatio.Redis.Tests/SharedConnection.cs @@ -3,11 +3,14 @@ using Microsoft.Extensions.Logging; using StackExchange.Redis; -namespace Foundatio.Redis.Tests { - public static class SharedConnection { +namespace Foundatio.Redis.Tests +{ + public static class SharedConnection + { private static ConnectionMultiplexer _muxer; - public static ConnectionMultiplexer GetMuxer(ILoggerFactory loggerFactory) { + public static ConnectionMultiplexer GetMuxer(ILoggerFactory loggerFactory) + { string connectionString = Configuration.GetConnectionString("RedisConnectionString"); if (String.IsNullOrEmpty(connectionString)) return null; diff --git a/tests/Foundatio.Redis.Tests/Storage/RedisFileStorageTests.cs b/tests/Foundatio.Redis.Tests/Storage/RedisFileStorageTests.cs index d92f922..0edc101 100644 --- a/tests/Foundatio.Redis.Tests/Storage/RedisFileStorageTests.cs +++ b/tests/Foundatio.Redis.Tests/Storage/RedisFileStorageTests.cs @@ -5,104 +5,126 @@ using Xunit; using Xunit.Abstractions; -namespace Foundatio.Redis.Tests.Storage { - public class RedisFileStorageTests : FileStorageTestsBase { - public RedisFileStorageTests(ITestOutputHelper output) : base(output) { +namespace Foundatio.Redis.Tests.Storage +{ + public class RedisFileStorageTests : FileStorageTestsBase + { + public RedisFileStorageTests(ITestOutputHelper output) : base(output) + { var muxer = SharedConnection.GetMuxer(Log); muxer.FlushAllAsync().GetAwaiter().GetResult(); } - protected override IFileStorage GetStorage() { + protected override IFileStorage GetStorage() + { return new RedisFileStorage(o => o.ConnectionMultiplexer(SharedConnection.GetMuxer(Log)).LoggerFactory(Log)); } [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 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(); } }