From 41f01f866e41e61909ce1345f999be6ad6cb4533 Mon Sep 17 00:00:00 2001 From: Matvey Mednikov Date: Mon, 23 Feb 2026 09:47:00 +0500 Subject: [PATCH 1/5] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20ParallelCluster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/ParallelClusterClient.cs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs index 5531800..f1613bd 100644 --- a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs +++ b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using log4net; @@ -13,9 +15,36 @@ public ParallelClusterClient(string[] replicaAddresses) : base(replicaAddresses) { } - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - throw new NotImplementedException(); + var tasks = ReplicaAddresses.Select(async replica => + { + var webRequest = CreateRequest(replica + "?query=" + query); + Log.InfoFormat($"Processing {webRequest.RequestUri}"); + return await ProcessRequestAsync(webRequest); + }).ToList(); + + var timeoutTask = Task.Delay(timeout); + + while (tasks.Count != 0) + { + var completed = await Task.WhenAny(tasks.Append(timeoutTask)); + + if (completed == timeoutTask) + throw new TimeoutException(); + + var result = (Task)completed; + tasks.Remove(result); + try + { + return await result; + } + catch (Exception e) + { + + } + } + throw new Exception(); } protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); From d885421b9620305254a946179a2ffd5533b0f792 Mon Sep 17 00:00:00 2001 From: Matvey Mednikov Date: Mon, 23 Feb 2026 10:33:54 +0500 Subject: [PATCH 2/5] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20RoundRobin=20=D0=BA=D0=BB=D0=B8=D0=B5?= =?UTF-8?q?=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/RoundRobinClusterClient.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 0293628..47e3526 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -13,9 +13,23 @@ public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresse { } - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - throw new NotImplementedException(); + var replicaTimeout = timeout/ReplicaAddresses.Length; + foreach (var replica in ReplicaAddresses ) + { + var webRequest = CreateRequest(replica + "?query=" + query); + Log.InfoFormat($"Processing {webRequest.RequestUri}"); + var resultTask = ProcessRequestAsync(webRequest); + + await Task.WhenAny(resultTask, Task.Delay(replicaTimeout)); + if (resultTask.IsFaulted || !resultTask.IsCompleted) + { + continue; + } + return await resultTask; + } + throw new TimeoutException(); } protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); From 304cea4ae3c163c0baf7a8d6598d1c1b85deaeb1 Mon Sep 17 00:00:00 2001 From: Matvey Mednikov Date: Tue, 24 Feb 2026 14:25:01 +0500 Subject: [PATCH 3/5] =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20SmartCLuster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/SmartClusterClient.cs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index eb06d8b..4871c3a 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; @@ -12,12 +11,53 @@ public class SmartClusterClient : ClusterClientBase public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) { } - - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - throw new NotImplementedException(); - } + var replicaTimeout = timeout / ReplicaAddresses.Length; + var deadline = DateTime.UtcNow + timeout; + var runningTasks = new List>(); + foreach (var replica in ReplicaAddresses) + { + var webRequest = CreateRequest(replica + "?query=" + query); + Log.InfoFormat($"Processing {webRequest.RequestUri}"); + var task = ProcessRequestAsync(webRequest); + runningTasks.Add(task); + + var finished = await WaitOneAsync(runningTasks, replicaTimeout);; + if(finished != null) + return finished; + } + + while (runningTasks.Count > 0) + { + var remaining = deadline - DateTime.UtcNow; + if (remaining <= TimeSpan.Zero) + throw new TimeoutException(); + + var finished = await WaitOneAsync(runningTasks, remaining); + if (finished != null) + return finished; + } + throw new TimeoutException(); + } protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); + + private async Task WaitOneAsync(List> runningTasks, TimeSpan timeout) + { + if (timeout <= TimeSpan.Zero) + return null; + + var delayTask = Task.Delay(timeout); + var completed = await Task.WhenAny( + runningTasks.Append(delayTask)); + if (completed == delayTask) + return null; + + var finished = (Task)completed; + runningTasks.Remove(finished); + return finished.IsCompletedSuccessfully ? finished.Result : null; + } } } From 4a041169b482d1f8c308ecd86b2a492c6801bf4d Mon Sep 17 00:00:00 2001 From: Matvey Mednikov Date: Wed, 25 Feb 2026 14:02:55 +0500 Subject: [PATCH 4/5] =?UTF-8?q?=D0=9F=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB?= =?UTF-8?q?=20=D0=B2=D1=8B=D1=87=D0=B8=D1=81=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D0=B5?= =?UTF-8?q?=20=D0=BE=D1=82=20=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=88=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=D1=81=D1=8F=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B0=D0=B4=D1=80=D0=B5=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/RoundRobinClusterClient.cs | 18 ++++++++++-------- .../Clients/SmartClusterClient.cs | 8 ++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 47e3526..8f361f5 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -15,19 +15,21 @@ public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresse public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - var replicaTimeout = timeout/ReplicaAddresses.Length; - foreach (var replica in ReplicaAddresses ) + var deadline = DateTime.UtcNow + timeout; + var addressesLeft = ReplicaAddresses.Length; + + foreach (var replica in ReplicaAddresses) { + var remaining = deadline - DateTime.UtcNow; + var replicaTimeout = remaining / addressesLeft; + var webRequest = CreateRequest(replica + "?query=" + query); Log.InfoFormat($"Processing {webRequest.RequestUri}"); var resultTask = ProcessRequestAsync(webRequest); - + await Task.WhenAny(resultTask, Task.Delay(replicaTimeout)); - if (resultTask.IsFaulted || !resultTask.IsCompleted) - { - continue; - } - return await resultTask; + if (resultTask.IsCompletedSuccessfully) return await resultTask; + addressesLeft--; } throw new TimeoutException(); } diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index 4871c3a..b38aa26 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -14,20 +14,24 @@ public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - var replicaTimeout = timeout / ReplicaAddresses.Length; var deadline = DateTime.UtcNow + timeout; var runningTasks = new List>(); + var addressesLeft = ReplicaAddresses.Length; foreach (var replica in ReplicaAddresses) { + var remaining = deadline - DateTime.UtcNow; + var replicaTimeout = remaining / addressesLeft; + var webRequest = CreateRequest(replica + "?query=" + query); Log.InfoFormat($"Processing {webRequest.RequestUri}"); var task = ProcessRequestAsync(webRequest); runningTasks.Add(task); - var finished = await WaitOneAsync(runningTasks, replicaTimeout);; + var finished = await WaitOneAsync(runningTasks, replicaTimeout); if(finished != null) return finished; + addressesLeft--; } while (runningTasks.Count > 0) From 424b2d83e6bc5256458996cf6ddc9f10eb1b87dd Mon Sep 17 00:00:00 2001 From: Matvey Mednikov Date: Wed, 25 Feb 2026 23:05:07 +0500 Subject: [PATCH 5/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=83=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BE=D1=87=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B5=D0=BF=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=20=D1=81=20=D1=83=D1=87=D0=B5=D1=82=D0=BE=D0=BC=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/ReplicaStatistics.cs | 34 ++++++++++++++ .../Clients/RoundRobinClusterClient.cs | 24 ++++++---- .../Clients/SmartClusterClient.cs | 46 +++++++++++++------ 3 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 homework 2/ClusterClient/Clients/ReplicaStatistics.cs diff --git a/homework 2/ClusterClient/Clients/ReplicaStatistics.cs b/homework 2/ClusterClient/Clients/ReplicaStatistics.cs new file mode 100644 index 0000000..3d51c7d --- /dev/null +++ b/homework 2/ClusterClient/Clients/ReplicaStatistics.cs @@ -0,0 +1,34 @@ +using System.Collections.Concurrent; +using System.Linq; + +namespace ClusterClient.Clients +{ + public static class ReplicaStatistics + { + private static readonly ConcurrentDictionary> _responseTimes = new(); + private const int MaxStoredTimes = 100; + + public static void RecordResponseTime(string replicaAddress, long elapsedMilliseconds) + { + var bag = _responseTimes.GetOrAdd(replicaAddress, _ => new ConcurrentBag()); + bag.Add(elapsedMilliseconds); + + var newBag = new ConcurrentBag(bag.TakeLast(MaxStoredTimes)); + _responseTimes.TryUpdate(replicaAddress, newBag, bag); + } + + private static double GetAverageResponseTime(string replicaAddress) + { + if (!_responseTimes.TryGetValue(replicaAddress, out var times) || times.IsEmpty) + { + return double.MaxValue; + } + return times.Average(); + } + + public static string[] OrderBySpeed(string[] replicaAddresses) + { + return replicaAddresses.OrderBy(GetAverageResponseTime).ToArray(); + } + } +} diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 8f361f5..e1931e2 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,7 +1,5 @@ - using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; +using System.Diagnostics; using System.Threading.Tasks; using log4net; @@ -15,20 +13,30 @@ public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresse public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { + var orderedReplicas = ReplicaStatistics.OrderBySpeed(ReplicaAddresses); + var deadline = DateTime.UtcNow + timeout; - var addressesLeft = ReplicaAddresses.Length; + var addressesLeft = orderedReplicas.Length; - foreach (var replica in ReplicaAddresses) + foreach (var replica in orderedReplicas) { var remaining = deadline - DateTime.UtcNow; var replicaTimeout = remaining / addressesLeft; var webRequest = CreateRequest(replica + "?query=" + query); Log.InfoFormat($"Processing {webRequest.RequestUri}"); + + var timer = Stopwatch.StartNew(); var resultTask = ProcessRequestAsync(webRequest); await Task.WhenAny(resultTask, Task.Delay(replicaTimeout)); - if (resultTask.IsCompletedSuccessfully) return await resultTask; + timer.Stop(); + + if (resultTask.IsCompletedSuccessfully) + { + ReplicaStatistics.RecordResponseTime(replica, timer.ElapsedMilliseconds); + return await resultTask; + } addressesLeft--; } throw new TimeoutException(); @@ -36,4 +44,4 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); } -} +} \ No newline at end of file diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index b38aa26..c64427f 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,23 +15,32 @@ public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { + var orderedReplicas = ReplicaStatistics.OrderBySpeed(ReplicaAddresses); + var deadline = DateTime.UtcNow + timeout; - var runningTasks = new List>(); - var addressesLeft = ReplicaAddresses.Length; + var runningTasks = new List<(Task task, string address, Stopwatch timer)>(); + var addressesLeft = orderedReplicas.Length; - foreach (var replica in ReplicaAddresses) + foreach (var replica in orderedReplicas) { var remaining = deadline - DateTime.UtcNow; var replicaTimeout = remaining / addressesLeft; var webRequest = CreateRequest(replica + "?query=" + query); Log.InfoFormat($"Processing {webRequest.RequestUri}"); + + var timer = Stopwatch.StartNew(); var task = ProcessRequestAsync(webRequest); - runningTasks.Add(task); + runningTasks.Add((task, replica, timer)); var finished = await WaitOneAsync(runningTasks, replicaTimeout); if(finished != null) - return finished; + { + var (resultTask, resultReplica, resultTimer) = finished.Value; + resultTimer.Stop(); + ReplicaStatistics.RecordResponseTime(resultReplica, resultTimer.ElapsedMilliseconds); + return resultTask.Result; + } addressesLeft--; } @@ -41,27 +51,35 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti throw new TimeoutException(); var finished = await WaitOneAsync(runningTasks, remaining); - if (finished != null) - return finished; + if (finished == null) continue; + var (resultTask, resultReplica, resultTimer) = finished.Value; + resultTimer.Stop(); + ReplicaStatistics.RecordResponseTime(resultReplica, resultTimer.ElapsedMilliseconds); + return resultTask.Result; } throw new TimeoutException(); } + protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); - private async Task WaitOneAsync(List> runningTasks, TimeSpan timeout) + private async Task<(Task, string, Stopwatch)?> WaitOneAsync + (List<(Task task, string address, Stopwatch timer)> runningTasks, TimeSpan timeout) { if (timeout <= TimeSpan.Zero) return null; var delayTask = Task.Delay(timeout); - var completed = await Task.WhenAny( - runningTasks.Append(delayTask)); + var tasksList = runningTasks.Select(Task (x) => x.task).Append(delayTask).ToList(); + var completed = await Task.WhenAny(tasksList); + if (completed == delayTask) return null; - var finished = (Task)completed; - runningTasks.Remove(finished); - return finished.IsCompletedSuccessfully ? finished.Result : null; + var finishedTask = (Task)completed; + var result = runningTasks.First(x => x.task == finishedTask); + runningTasks.Remove(result); + + return finishedTask.IsCompletedSuccessfully ? (finishedTask, result.address, result.timer) : null; } } -} +} \ No newline at end of file