diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
index beaca27e..f56b0ae4 100644
--- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs
+++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
@@ -52,17 +52,6 @@ public class BackgroundWorker : BackgroundService
this.serviceProvider = serviceProvider;
}
- ///
- /// Добавить в очередь
- ///
- ///
- public void Enqueue(Work work)
- {
- works.Enqueue(work);
- if (ExecuteTask is null || ExecuteTask.IsCompleted)
- StartAsync(CancellationToken.None).Wait();
- }
-
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork))
@@ -94,12 +83,27 @@ public class BackgroundWorker : BackgroundService
}
}
+ ///
+ /// Добавить в очередь
+ ///
+ /// work.Id может быть не уникальным,
+ /// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id
+ ///
+ ///
+ ///
+ public void Enqueue(Work work)
+ {
+ works.Enqueue(work);
+ if (ExecuteTask is null || ExecuteTask.IsCompleted)
+ StartAsync(CancellationToken.None).Wait();
+ }
+
///
/// Удаление работы по ID из одноразовой очереди
///
///
///
- public bool TryRemoveFromRunOnceQueue(string id)
+ public bool TryRemoveFromQueue(string id)
{
var work = Works.FirstOrDefault(w => w.Id == id);
if (work is not null)
diff --git a/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
index fd12c13a..a7490ed7 100644
--- a/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
+++ b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
@@ -14,8 +14,8 @@ namespace AsbCloudInfrastructure.Background;
///
public class PeriodicBackgroundWorker : BackgroundService
{
- private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
- private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
+ private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
+ private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
private readonly IServiceProvider serviceProvider;
private readonly List works = new(8);
@@ -97,6 +97,8 @@ public class PeriodicBackgroundWorker : BackgroundService
{
var periodic = new WorkPeriodic(work, period);
works.Add(periodic);
+ if (ExecuteTask is null || ExecuteTask.IsCompleted)
+ StartAsync(CancellationToken.None).Wait();
}
private WorkPeriodic? GetNext()
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index 93c6575e..28bde671 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -165,6 +165,7 @@ namespace AsbCloudInfrastructure
services.AddSingleton>(provider => TelemetryDataCache.GetInstance(provider));
services.AddSingleton>(provider => TelemetryDataCache.GetInstance(provider));
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton(provider => ReduceSamplingService.GetInstance(configuration));
diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
index 5557707d..c91689a3 100644
--- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
+++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
@@ -556,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
- backgroundWorker.TryRemoveFromRunOnceQueue(workId);
+ backgroundWorker.TryRemoveFromQueue(workId);
var filesIds = await context.Files
.Where(f => f.IdWell == idWell &&
diff --git a/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
index a5690d84..0f54dddf 100644
--- a/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
+++ b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
@@ -1,16 +1,7 @@
-using AsbCloudApp.Data;
-using AsbCloudApp.Data.SAUB;
-using AsbCloudApp.Repositories;
-using AsbCloudApp.Requests;
-using AsbCloudApp.Services;
-using AsbCloudInfrastructure.Background;
-using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.SAUB;
+using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -18,12 +9,12 @@ using Xunit;
namespace AsbCloudWebApi.Tests.Services;
-public class BackgroundWorkertest
+public class BackgroundWorkerTest
{
private IServiceProvider provider;
private BackgroundWorker service;
- public BackgroundWorkertest()
+ public BackgroundWorkerTest()
{
provider = Substitute.For();
var serviceScope = Substitute.For();
@@ -33,7 +24,7 @@ public class BackgroundWorkertest
service = new BackgroundWorker(provider);
typeof(BackgroundWorker)
- .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)?
+ .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(service, TimeSpan.FromMilliseconds(1));
}
@@ -55,11 +46,8 @@ public class BackgroundWorkertest
service.Enqueue(work);
}
- var waitI = workCount;
- await Task.Delay(1_000);
- //while (waitI-- > 0 && service.ExecuteTask is not null && service.ExecuteTask.IsCompleted)
- // await Task.Delay(4);
-
+ await service.ExecuteTask;
+
//assert
Assert.Equal(workCount, result);
}
@@ -69,15 +57,16 @@ public class BackgroundWorkertest
{
var expectadResult = 42;
var result = 0;
+
Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
{
result = expectadResult;
return Task.CompletedTask;
}
+ var goodWork = Work.CreateByDelegate("", workAction);
Task failAction(string id, IServiceProvider services, Action callback, CancellationToken token)
=> throw new Exception();
- var goodWork = Work.CreateByDelegate("", workAction);
var badWork = Work.CreateByDelegate("", failAction);
badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
@@ -85,9 +74,40 @@ public class BackgroundWorkertest
//act
service.Enqueue(badWork);
service.Enqueue(goodWork);
- await Task.Delay(1200);
+
+ await service.ExecuteTask;
//assert
Assert.Equal(expectadResult, result);
+ Assert.Equal(1, service.Felled.Count);
+ Assert.Equal(1, service.Done.Count);
+ }
+
+ [Fact]
+ public async Task TryRemove()
+ {
+ var workCount = 5;
+ var result = 0;
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result++;
+ return Task.Delay(10);
+ }
+
+ //act
+ for (int i = 0; i < workCount; i++)
+ {
+ var work = Work.CreateByDelegate(i.ToString(), workAction);
+ service.Enqueue(work);
+ }
+
+ var removed = service.TryRemoveFromQueue((workCount - 1).ToString());
+
+ await service.ExecuteTask;
+
+ //assert
+ Assert.True(removed);
+ Assert.Equal(workCount - 1, result);
+ Assert.Equal(workCount - 1, service.Done.Count);
}
}
diff --git a/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs b/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs
new file mode 100644
index 00000000..d0c16285
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs
@@ -0,0 +1,97 @@
+using AsbCloudInfrastructure.Background;
+using DocumentFormat.OpenXml.Drawing.Charts;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Services;
+
+public class PeriodicBackgroundWorkerTest
+{
+ private IServiceProvider provider;
+ private PeriodicBackgroundWorker service;
+
+ public PeriodicBackgroundWorkerTest()
+ {
+ provider = Substitute.For();
+ var serviceScope = Substitute.For();
+ var serviceScopeFactory = Substitute.For();
+ serviceScopeFactory.CreateScope().Returns(serviceScope);
+ ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
+
+ service = new PeriodicBackgroundWorker(provider);
+ typeof(PeriodicBackgroundWorker)
+ .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)?
+ .SetValue(service, TimeSpan.FromMilliseconds(1));
+
+ typeof(PeriodicBackgroundWorker)
+ .GetField("executePeriod", BindingFlags.NonPublic | BindingFlags.Instance)?
+ .SetValue(service, TimeSpan.FromMilliseconds(1));
+ }
+
+ [Fact]
+ public async Task WorkRunsTwice()
+ {
+ var workCount = 2;
+ var periodMs = 100d;
+
+ var period = TimeSpan.FromMilliseconds(periodMs);
+
+ var result = 0;
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result++;
+ return Task.CompletedTask;
+ }
+
+ //act
+ var work = Work.CreateByDelegate("", workAction);
+
+ var stopwatch = Stopwatch.StartNew();
+ service.Add(work, period);
+
+ var delay = (periodMs / 20) + (periodMs * workCount) - stopwatch.ElapsedMilliseconds;
+ await Task.Delay(TimeSpan.FromMilliseconds(delay));
+
+ //assert
+ Assert.Equal(workCount, result);
+ }
+
+
+ [Fact]
+ public async Task Enqueue_continues_after_exceptions()
+ {
+ var expectadResult = 42;
+ var result = 0;
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result = expectadResult;
+ return Task.CompletedTask;
+ }
+ var goodWork = Work.CreateByDelegate("", workAction);
+
+ Task failAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => throw new Exception();
+
+ var badWork = Work.CreateByDelegate("", failAction);
+ badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
+
+ //act
+ service.Add(badWork, TimeSpan.FromSeconds(2));
+ service.Add(goodWork, TimeSpan.FromSeconds(2));
+
+ await Task.Delay(TimeSpan.FromMilliseconds(20));
+
+ //assert
+ Assert.Equal(expectadResult, result);
+ Assert.Equal(1, badWork.CountErrors);
+ Assert.Equal(1, goodWork.CountComplete);
+ Assert.Equal(1, goodWork.CountStart);
+ }
+}
diff --git a/AsbCloudWebApi.Tests/Services/WorkTest.cs b/AsbCloudWebApi.Tests/Services/WorkTest.cs
new file mode 100644
index 00000000..ea826565
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/WorkTest.cs
@@ -0,0 +1,126 @@
+using AsbCloudInfrastructure.Background;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Services
+{
+ public class WorkTest
+ {
+ private IServiceProvider provider;
+
+ public WorkTest()
+ {
+ provider = Substitute.For();
+ var serviceScope = Substitute.For();
+ var serviceScopeFactory = Substitute.For();
+ serviceScopeFactory.CreateScope().Returns(serviceScope);
+ ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
+ }
+
+ [Fact]
+ public async Task Work_done_with_success()
+ {
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => Task.CompletedTask;
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ await work.Start(provider, CancellationToken.None);
+ var done = DateTime.Now;
+ var executionTime = done - begin;
+
+ //assert
+ Assert.Equal(1, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(0, work.CountErrors);
+ Assert.Null(work.CurrentState);
+ Assert.Null(work.LastError);
+
+ var lastState = work.LastComplete;
+ Assert.NotNull(lastState);
+ Assert.InRange(lastState.Start, begin, done - 0.5 * executionTime);
+ Assert.InRange(lastState.End, done - 0.5 * executionTime, done);
+ Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime);
+ }
+
+ [Fact]
+ public async Task Work_calls_callback()
+ {
+ var expectedState = "42";
+ var expectedProgress = 42d;
+
+ var timeout = TimeSpan.FromMilliseconds(40);
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ callback.Invoke(expectedState, expectedProgress);
+ return Task.Delay(timeout);
+ }
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ _ = work.Start(provider, CancellationToken.None);
+ await Task.Delay(timeout/3);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(0, work.CountErrors);
+ Assert.NotNull(work.CurrentState);
+ Assert.Null(work.LastComplete);
+ Assert.Null(work.LastError);
+
+ var currentState = work.CurrentState;
+ Assert.NotNull(currentState);
+ Assert.InRange(currentState.Start, begin, begin + timeout);
+ Assert.InRange(currentState.StateUpdate, begin, begin + timeout);
+ Assert.Equal(expectedState, currentState.State);
+ Assert.Equal(expectedProgress, currentState.Progress);
+ }
+
+
+ [Fact]
+ public async Task Work_fails_with_info()
+ {
+ var expectedState = "41";
+ var expectedErrorText = "42";
+ var minWorkTime = TimeSpan.FromMilliseconds(10);
+
+ async Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ await Task.Delay(minWorkTime);
+ callback(expectedState, 0);
+ throw new Exception(expectedErrorText);
+ }
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ await work.Start(provider, CancellationToken.None);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(1, work.CountErrors);
+ Assert.Null(work.CurrentState);
+ Assert.Null(work.LastComplete);
+
+ var error = work.LastError;
+ Assert.NotNull(error);
+ Assert.InRange(error.Start, begin, DateTime.Now);
+ Assert.InRange(error.End, begin, DateTime.Now);
+ Assert.InRange(error.ExecutionTime, minWorkTime, DateTime.Now - begin);
+ Assert.Contains(expectedErrorText, error.ErrorText, StringComparison.InvariantCultureIgnoreCase);
+ Assert.Equal(expectedState, error.State);
+ }
+ }
+}