From dd03138fe34b2e292c20e0d634098a97b8f3756b Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 25 Feb 2026 16:46:58 +0500 Subject: [PATCH 1/5] feat: create and run single request task --- homework 2/ClusterClient/Clients/ClusterClientBase.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homework 2/ClusterClient/Clients/ClusterClientBase.cs b/homework 2/ClusterClient/Clients/ClusterClientBase.cs index 23a2ffd..a7db03a 100644 --- a/homework 2/ClusterClient/Clients/ClusterClientBase.cs +++ b/homework 2/ClusterClient/Clients/ClusterClientBase.cs @@ -40,5 +40,16 @@ protected async Task ProcessRequestAsync(WebRequest request) return result; } } + + protected Task CreateAndRunRequestTask(string replicaAddress, string query) + { + var uri = new UriBuilder(replicaAddress) + { + Query = $"query={query}" + }; + var request = CreateRequest(uri.ToString()); + Log.InfoFormat($"Processing {request.RequestUri}"); + return ProcessRequestAsync(request); + } } } \ No newline at end of file From 3a3f2ee99d258738f1de4c9c015877918387da6f Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 25 Feb 2026 16:47:45 +0500 Subject: [PATCH 2/5] feat: parallel cluster client --- .../Clients/ParallelClusterClient.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs index 5531800..a080d6b 100644 --- a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs +++ b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs @@ -1,23 +1,38 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class ParallelClusterClient : ClusterClientBase { - public class ParallelClusterClient : ClusterClientBase + public ParallelClusterClient(string[] replicaAddresses) : base(replicaAddresses) { - public ParallelClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + } - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) + { + var requestTasks = ReplicaAddresses + .Select(t => CreateAndRunRequestTask(t, query)) + .ToHashSet(); + var timeoutTask = Task.Delay(timeout); + + while (requestTasks.Count > 0 && !timeoutTask.IsCompleted) { - throw new NotImplementedException(); + var completedTask = await Task.WhenAny(requestTasks.Append(timeoutTask)); + + if (completedTask == timeoutTask) + throw new TimeoutException(); + + if (completedTask.IsCompletedSuccessfully) + return await (Task)completedTask; + + requestTasks.Remove((Task)completedTask); } - protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); + throw new TimeoutException(); } -} + + protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); +} \ No newline at end of file From d94f244c35f5f3a3e18e789cb9808ce8220bf2e5 Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 25 Feb 2026 16:48:09 +0500 Subject: [PATCH 3/5] feat: round robin cluster client --- .../Clients/RoundRobinClusterClient.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 0293628..d0f568c 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,23 +1,27 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class RoundRobinClusterClient : ClusterClientBase { - public class RoundRobinClusterClient : ClusterClientBase + public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresses) { - public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + } - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) + { + foreach (var replicaAddress in ReplicaAddresses) { - throw new NotImplementedException(); + var requestTask = CreateAndRunRequestTask(replicaAddress, query); + await Task.WhenAny(requestTask, Task.Delay(timeout / ReplicaAddresses.Length)); + if (requestTask.IsCompletedSuccessfully) + return requestTask.Result; } - protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); + throw new TimeoutException(); } -} + + protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); +} \ No newline at end of file From 21a51d360f744ebfb88466d5df5bd021d4a6d755 Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 25 Feb 2026 16:48:30 +0500 Subject: [PATCH 4/5] feat: smart cluster client --- .../Clients/SmartClusterClient.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index eb06d8b..b04f87d 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -1,23 +1,38 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class SmartClusterClient : ClusterClientBase { - public class SmartClusterClient : ClusterClientBase + public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) { - public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + } - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) + { + var requestTasks = new HashSet>(); + foreach (var replicaAddress in ReplicaAddresses) { - throw new NotImplementedException(); + var timeoutTask = Task.Delay(timeout / ReplicaAddresses.Length); + var requestTask = CreateAndRunRequestTask(replicaAddress, query); + requestTasks.Add(requestTask); + var completedTask = await Task.WhenAny(requestTasks.Append(timeoutTask)); + + if (completedTask == timeoutTask) + continue; + + if (completedTask.IsCompletedSuccessfully) + return await (Task)completedTask; + + requestTasks.Remove((Task)completedTask); } - protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); + throw new TimeoutException(); } -} + + protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); +} \ No newline at end of file From 4d57de6a539110ac9904b0a6ad4790d093f7c132 Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 25 Feb 2026 22:01:29 +0500 Subject: [PATCH 5/5] feat: taking into account the history of queries --- .../Clients/ClusterClientBase.cs | 15 +++++++++++++ .../Clients/ResponseStatistics.cs | 5 +++++ .../Clients/RoundRobinClusterClient.cs | 22 +++++++++++++++++-- .../Clients/SmartClusterClient.cs | 20 +++++++++++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 homework 2/ClusterClient/Clients/ResponseStatistics.cs diff --git a/homework 2/ClusterClient/Clients/ClusterClientBase.cs b/homework 2/ClusterClient/Clients/ClusterClientBase.cs index a7db03a..693d3f4 100644 --- a/homework 2/ClusterClient/Clients/ClusterClientBase.cs +++ b/homework 2/ClusterClient/Clients/ClusterClientBase.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; @@ -10,6 +12,8 @@ namespace ClusterClient.Clients { public abstract class ClusterClientBase { + protected static ConcurrentDictionary ResponseStatisticsHistory { get; } = []; + protected string[] ReplicaAddresses { get; set; } protected ClusterClientBase(string[] replicaAddresses) @@ -51,5 +55,16 @@ protected Task CreateAndRunRequestTask(string replicaAddress, string que Log.InfoFormat($"Processing {request.RequestUri}"); return ProcessRequestAsync(request); } + + protected string[] GetFilteredAndSortedAddresses() + { + return ReplicaAddresses + .Where(t => !ResponseStatisticsHistory.TryGetValue(t, out var statistics) + || statistics.IsSuccessful) + .OrderBy(t => ResponseStatisticsHistory.TryGetValue(t, out var statistics) + ? statistics.ResponseTime + : TimeSpan.Zero) + .ToArray(); + } } } \ No newline at end of file diff --git a/homework 2/ClusterClient/Clients/ResponseStatistics.cs b/homework 2/ClusterClient/Clients/ResponseStatistics.cs new file mode 100644 index 0000000..fc3cb5f --- /dev/null +++ b/homework 2/ClusterClient/Clients/ResponseStatistics.cs @@ -0,0 +1,5 @@ +using System; + +namespace ClusterClient.Clients; + +public record ResponseStatistics(bool IsSuccessful, TimeSpan ResponseTime); \ No newline at end of file diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index d0f568c..6cd4c1a 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using log4net; @@ -12,10 +13,27 @@ public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresse public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - foreach (var replicaAddress in ReplicaAddresses) + var preparedAddresses = GetFilteredAndSortedAddresses(); + var goodAddressesCount = preparedAddresses.Length; + var sw = new Stopwatch(); + foreach (var replicaAddress in preparedAddresses) { + if (ResponseStatisticsHistory.TryGetValue(replicaAddress, out var statistics) && !statistics.IsSuccessful) + { + goodAddressesCount--; + continue; + } + + sw.Restart(); var requestTask = CreateAndRunRequestTask(replicaAddress, query); - await Task.WhenAny(requestTask, Task.Delay(timeout / ReplicaAddresses.Length)); + var completedTask = await Task.WhenAny(requestTask, Task.Delay(timeout / goodAddressesCount)); + + if (!completedTask.IsCompletedSuccessfully) + goodAddressesCount--; + + ResponseStatisticsHistory[replicaAddress] = + new ResponseStatistics(completedTask.IsCompletedSuccessfully, sw.Elapsed); + if (requestTask.IsCompletedSuccessfully) return requestTask.Result; } diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index b04f87d..f769d84 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using log4net; @@ -14,20 +15,35 @@ public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { + var preparedAddresses = GetFilteredAndSortedAddresses(); + var goodAddressesCount = preparedAddresses.Length; var requestTasks = new HashSet>(); - foreach (var replicaAddress in ReplicaAddresses) + foreach (var replicaAddress in preparedAddresses) { - var timeoutTask = Task.Delay(timeout / ReplicaAddresses.Length); + if (ResponseStatisticsHistory.TryGetValue(replicaAddress, out var statistics) && !statistics.IsSuccessful) + { + goodAddressesCount--; + continue; + } + + var sw = Stopwatch.StartNew(); var requestTask = CreateAndRunRequestTask(replicaAddress, query); + _ = requestTask.ContinueWith(t => ResponseStatisticsHistory[replicaAddress] + = new ResponseStatistics(t.IsCompletedSuccessfully, sw.Elapsed)); requestTasks.Add(requestTask); + var timeoutTask = Task.Delay(timeout / goodAddressesCount); var completedTask = await Task.WhenAny(requestTasks.Append(timeoutTask)); if (completedTask == timeoutTask) + { + ResponseStatisticsHistory[replicaAddress] = new ResponseStatistics(true, sw.Elapsed); continue; + } if (completedTask.IsCompletedSuccessfully) return await (Task)completedTask; + goodAddressesCount--; requestTasks.Remove((Task)completedTask); }