diff --git a/AsbCloudApp/Data/BackgroundWorkDto.cs b/AsbCloudApp/Data/BackgroundWorkDto.cs
index 9d4f2105..3374148c 100644
--- a/AsbCloudApp/Data/BackgroundWorkDto.cs
+++ b/AsbCloudApp/Data/BackgroundWorkDto.cs
@@ -172,7 +172,7 @@ namespace AsbCloudApp.Data
if (progress.HasValue)
CurrentState.Progress = progress.Value;
- Trace.TraceInformation($"{WorkNameForTrace} state: {newState} [{100*progress:#}%]");
+ Trace.TraceInformation($"{WorkNameForTrace} state[{100*progress:#}%]: {newState}");
}
///
diff --git a/AsbCloudDb/Setup db replication.md b/AsbCloudDb/Setup db replication.md
new file mode 100644
index 00000000..f4a529af
--- /dev/null
+++ b/AsbCloudDb/Setup db replication.md
@@ -0,0 +1,265 @@
+# Репликация данных PostgreSQL
+## 1. Требования
+1. Primary и Replica сервера должны принадлежать одной версии postgreSQL
+2. Сервера должны иметь удаленный доступ
+
+
+## 2. Настройка Primary-сервера
+1. Открыть postgres.conf на редактирование
+
+```
+cd /etc/postgresql/15/main/
+
+sudo nano postgresql.conf
+ ```
+
+ 2. В postgres.conf найти запись listen_addresses и добавить туда ip standby-сервера
+
+ > listen_addresses = '*, '
+
+ 3. Открыть клиент для работы с postgres
+
+ ```
+ sudo -u postgres psql
+ ```
+
+ 4. Создать пользователя с атрибутом REPLICATION.
+ P.S: В данном примере создается пользователь с логином replicator и паролем q
+
+ ```
+ CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'q';
+ ```
+
+ 5. Открыть на редактирование файл pg_hba.conf
+ ```
+cd /etc/postgresql/15/main/
+
+sudo nano pg_hba.conf
+ ```
+
+ 6. Вставить в pg_hba.conf запись.
+ Запись вставлять после комментария "Allow replication connections from localhost..."
+ Данные для вставки записи:
+ - replicator - имя пользователя, созданного на предыдущем шаге
+- , например, 192.168.0.0/24
+ ```
+ host replication replicator 192.168.0.0/24 md5
+ ```
+
+ 7. Рестарт сервера
+ ```
+ sudo systemctl restart postgresql
+ ```
+
+ ## 3. Настройка replica-сервера
+ 1. Остановить сервер
+ ```
+ sudo systemctl stop postgresql
+ ```
+
+ 2. Важно! Зайти под пользователем postgres
+ ```
+ sudo su - postgres
+ ```
+
+ 3. Сделать резервную копию содержимого /var/lib/postgresql/15/main/ в папку main_old
+ ```
+ cp -R /var/lib/postgresql/15/main/ /var/lib/postgresql/15/main_old/
+ ```
+
+ 4. Удалить папку main
+ ```
+ rm -rf /var/lib/postgresql/15/main/
+ ```
+
+ 5. Используя утилиту basebackup создать базовую резервную копию с правами владения postgres (либо любого пользователя с соответствующими разрешениями).
+
+ ```
+ pg_basebackup -h -D /var/lib/postgresql/14/main/ -U replicator -P -v -R -X stream -C -S slaveslot1
+
+ где: /var/lib/postgresql/15/main/ - каталог replica-сервера
+ ```
+
+ 6. Убедиться, что в папке main созданы файлы standby.signal и postgresql.auto.conf.
+```
+ls -ltrh /var/lib/postgresql/15/main/
+```
+
+7. Запустить сервер
+```
+systemctl start postgresql
+```
+
+ ## 4. Проверка настроек
+ 1. Подсоединиться к primary-серверу
+ ```
+sudo -u postgres psql
+ ```
+
+ 2. На primary-сервере выполнить команду
+ ```
+ SELECT * FROM pg_replication_slots;
+ ```
+
+ 3. Убедиться, что в представлении отображается слот репликации с именем slotslave1
+ 4. На standby-сервере выпонить команду
+ ```
+ SELECT * FROM pg_stat_wal_receiver;
+ ```
+ 5. Убедиться, что появилась запись с ip primary-сервера
+
+ 6. На primary - сервере проверить режим репликации. Он может быть синхронным или асинхронным. Для проверки необходимо выполнить команду
+ ```
+ SELECT * FROM pg_stat_replication;
+ ```
+
+ 7. Для включения синхронного режима необходимо выполнить следующую команду
+ ```
+ ALTER SYSTEM SET synchronous_standby_names TO '*';
+ ```
+
+ 8. Сделать рестарт primary-сервера.
+
+ 9. Внести запись в любую таблицу базы данных primary-сервера
+ 10. Убедиться, что соответствующая запись появилась в таблице базы данных standby-сервера
+ 11. Попытаться внести запись в таблицу базы данных standby-сервера.
+ 12. Убедиться, что операция завершилась с ошибкой
+ > cannot execute OPERATION in a read-only transaction
+
+
+
+## 5. Установка PgPool-II
+
+
+ 1. Установить на primary-сервер pgpool2 и postgresql-14-pgpool2
+```
+apt-get -y install pgpool2 postgresql-15-pgpool2
+
+```
+ 2. Установить на standby-сервер только postgresql-14-pgpool2
+```
+apt-get -y install postgresql-15-pgpool2
+```
+### Далее все настройки выполнить на primary-сервере
+ 3. Зайти на редактирование в конфигурационный файл pgpool2
+ ```
+ sudo nano /etc/pgpool2/pgpool.conf
+ ```
+ 4. Задать параметры следующим образом:
+ ```
+ backend_clustering_mode = 'streaming_replication'
+ listen_addresses = '*, '
+ port = 9999
+ ___
+ backend_hostname0 = ''
+ backend_port0 = '<порт primary-сервера>'
+ backend_weight0 = 0
+ backend_data_directory0 = '/var/lib/postgresql/14/main'
+ ___
+ backend_hostname1 = ''
+ backend_port1 = '<порт primary-сервера>'
+ backend_weight1 = 1
+ ___
+ enable_pool_hba = on
+ log_statement = on
+ log_per_node_statement = on
+ pid_file_name = "pgpool.pid"
+ load_balance_mode = on
+ statement_level_load_balance = on
+ sr_check_period = 1
+ sr_check_user = '<имя пользователя>'
+ sr_check_password = '<пароль пользователя>'
+ health_check_period = 10
+ health_check_user = '<имя пользователя>'
+ health_check_password = '<пароль пользователя>'
+ ```
+5. Поскольку enable_pool_hba указан в режиме on, это значит, что Pgpool-II будет использовать pool_hba.conf для аутентификации клиента. Поэтому открываем на редактирование pool_hba.conf
+```
+sudo nano /etc/pgpool2/pool_hba.conf
+```
+6. Добавить строку
+```
+host all all md5
+```
+7. Pgpool-II извлекает пароль пользователя из файла pool_passwd
+```
+sudo nano /etc/pgpool2/pool_passwd
+```
+Файл паролей представляет собой текстовый файл следующего формата:
+```
+пользователь1:пароль1
+пользователь2:пароль2
+```
+Файл может содержать 3 типа паролей. Pgpool-II идентифицирует тип формата пароля по его префиксу, поэтому каждая запись пароля в pool_passwd должна иметь префикс формата пароля.
+
+- Обычный текст : пароль в текстовом формате с использованием префикса TEXT (например, TEXTmypassword ) .
+- Зашифрованный пароль AES256 : зашифрованный пароль AES256, используя префикс AES (например, AESmzVzywsN1Z5GABhSAhwLSA== ) .
+- Хешированный пароль MD5 : хешированный пароль MD5, используя префикс md5 (например, md5270e98c3db83dbc0e40f98d9bfe20972 ) .
+
+8. В примере в качестве пароля используется обычный текст (пароль q)
+```
+postgres:TEXTq
+```
+9. Запустить pgpool
+```
+sudo pgpool -n
+```
+10. Убедиться, что процесс успешно запущен и подключены 2 ноды с разными индексами. В данном примере для primary node установлен индекс 0, а для standBy ноды установден индекс 1
+```
+
+2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: primary node is 0
+2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: standby node is 1
+2023-09-14 06:08:08.343: pcp_main pid 3977: LOG: PCP process: 3977 started
+2023-09-14 06:08:08.343: sr_check_worker pid 3978: LOG: process started
+2023-09-14 06:08:08.345: health_check pid 3979: LOG: process started
+2023-09-14 06:08:08.349: health_check pid 3980: LOG: process started
+2023-09-14 06:08:08.559: main pid 3941: LOG: pgpool-II successfully started. version 4.3.5 (tamahomeboshi)
+2023-09-14 06:08:08.662: main pid 3941: LOG: node status[0]: 1
+2023-09-14 06:08:08.662: main pid 3941: LOG: node status[1]: 2
+
+```
+11. При старте pgpool возможны следующие ошибки:
+- файл pgpool_status не найден / нет прав
+- pgpool стартует, но ноды имеют одинаковый индекс и балансировка идет только на первую ноду (как проверить балансировку указано ниже)
+
+Проблема решилась удалением файла pgpool_status, откуда pgpool пытался считывать статусы для нод.
+
+```
+cd /var/log/postgresql
+rm -rf pgpool_status
+sudo systemctl restart postgresql
+sudo pgpool -n
+```
+
+## 6. Тестирование балансировки PgPool-II
+1. При запущенном pgpool (он должен выводить логи), открыть еще один терминал. Зайти в базу, используя Pgpool-II на 9999-порте, выполнив команду
+```
+psql -h -p 9999 -d postgres -U postgres
+```
+
+2. Выполнить команду
+```
+show pool_nodes;
+```
+3. Убедиться, что обе ноды находятся в статусе up, а балансировка установлена на standBy-сервере (load_balance_node = true)
+```
+ node_id | hostname | port | status | pg_status | lb_weight | role | pg_role | select_cnt | load_balance_node | replication_delay | replication_state | replication_sync_state | last_status_change
+---------+--------------+------+--------+-----------+-----------+---------+---------+------------+-------------------+-------------------+-------------------+------------------------+---------------------
+ 0 | 192.168.0.71 | 5432 | up | up | 0.000000 | primary | primary | 0 | false | 0 | | | 2023-09-14 06:36:16
+ 1 | 192.168.0.72 | 5432 | up | up | 1.000000 | standby | standby | 0 | true | 0 | | | 2023-09-14 06:36:16
+(2 rows)
+
+```
+4. Выполнить команды Insert / Update / Delete (в качесве примера была внесена запись в таблицу public.t_company). Убедиться, что запрос приходит на primary-сервер (нода с индексом 0).
+
+```
+2023-09-14 07:04:31.800: DBeaver 23.1.2 - Main pid 4805: LOG: DB node id: 0 backend pid: 4814 statement: Execute: INSERT INTO public.t_company (id,caption,id_company_type)
+ VALUES ($1,$2,$3)
+
+```
+5. Выполинть команду Select. Убедиться, что запрос приходит на standBy-сервер (нода с индексом 1).
+```
+
+2023-09-14 07:53:19.275: DBeaver 23.1.2 - Main pid 5069: LOG: DB node id: 1 backend pid: 2745 statement: Execute: SELECT x.* FROM public.t_company x
+
+```
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
index 101a8273..f56b0ae4 100644
--- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs
+++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs
@@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -12,13 +14,37 @@ namespace AsbCloudInfrastructure.Background;
///
public class BackgroundWorker : BackgroundService
{
- private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
- private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
+ private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
private readonly IServiceProvider serviceProvider;
- public WorkStore WorkStore { get; } = new WorkStore();
+ ///
+ /// Очередь работ
+ ///
+ private Queue works = new(8);
+
+ ///
+ /// Список периодических работ
+ ///
+ public IEnumerable Works => works;
+
+ ///
+ /// Работа выполняемая в данный момент
+ ///
public Work? CurrentWork;
+ ///
+ /// последние 16 завершившиеся с ошибкой
+ ///
+ public CyclycArray Felled { get; } = new(16);
+
+ ///
+ /// последние 16 успешно завершенных
+ ///
+ public CyclycArray Done { get; } = new(16);
+
+ ///
+ /// Ошибка в главном цикле, никогда не должна появляться
+ ///
public string MainLoopLastException { get; private set; } = string.Empty;
public BackgroundWorker(IServiceProvider serviceProvider)
@@ -28,26 +54,18 @@ public class BackgroundWorker : BackgroundService
protected override async Task ExecuteAsync(CancellationToken token)
{
- while (!token.IsCancellationRequested)
+ while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork))
{
try
{
- var work = WorkStore.GetNext();
- if (work is null)
- {
- await Task.Delay(executePeriod, token);
- continue;
- }
-
- CurrentWork = work;
using var scope = serviceProvider.CreateScope();
- var result = await work.Start(scope.ServiceProvider, token);
+ var result = await CurrentWork.Start(scope.ServiceProvider, token);
if (!result)
- WorkStore.Felled.Add(work);
+ Felled.Add(CurrentWork);
else
- WorkStore.Done.Add(work);
+ Done.Add(CurrentWork);
CurrentWork = null;
await Task.Delay(minDelay, token);
@@ -64,4 +82,36 @@ 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 TryRemoveFromQueue(string id)
+ {
+ var work = Works.FirstOrDefault(w => w.Id == id);
+ if (work is not null)
+ {
+ works = new Queue(Works.Where(w => w.Id != id));
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
new file mode 100644
index 00000000..a7490ed7
--- /dev/null
+++ b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
@@ -0,0 +1,115 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudInfrastructure.Background;
+
+///
+/// Сервис для фонового выполнения периодической работы
+///
+public class PeriodicBackgroundWorker : BackgroundService
+{
+ private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
+ private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
+ private readonly IServiceProvider serviceProvider;
+
+ private readonly List works = new(8);
+
+ ///
+ /// Список периодических работ
+ ///
+ public IEnumerable Works => works;
+
+ ///
+ /// Работа выполняемая в данный момент
+ ///
+ public Work? CurrentWork;
+
+ ///
+ /// Ошибка в главном цикле, никогда не должна появляться
+ ///
+ public string MainLoopLastException { get; private set; } = string.Empty;
+
+ public PeriodicBackgroundWorker(IServiceProvider serviceProvider)
+ {
+ this.serviceProvider = serviceProvider;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ var periodicWork = GetNext();
+ if (periodicWork is null)
+ {
+ await Task.Delay(executePeriod, token);
+ continue;
+ }
+
+ CurrentWork = periodicWork.Work;
+
+ using var scope = serviceProvider.CreateScope();
+
+ var result = await periodicWork.Work.Start(scope.ServiceProvider, token);
+
+ CurrentWork = null;
+ await Task.Delay(minDelay, token);
+ }
+ catch (Exception ex)
+ {
+ MainLoopLastException = $"BackgroundWorker " +
+ $"MainLoopLastException: \r\n" +
+ $"date: {DateTime.Now:O}\r\n" +
+ $"message: {ex.Message}\r\n" +
+ $"inner: {ex.InnerException?.Message}\r\n" +
+ $"stackTrace: {ex.StackTrace}";
+ Trace.TraceError(MainLoopLastException);
+ }
+ }
+ }
+
+ ///
+ /// Добавить фоновую работу выполняющуюся с заданным периодом
+ ///
+ ///
+ ///
+ public void Add(TimeSpan period)
+ where T : Work, new()
+ {
+ var work = new T();
+ var periodic = new WorkPeriodic(work, period);
+ works.Add(periodic);
+ }
+
+ ///
+ /// Добавить фоновую работу выполняющуюся с заданным периодом
+ ///
+ ///
+ ///
+ public void Add(Work work, TimeSpan period)
+ {
+ var periodic = new WorkPeriodic(work, period);
+ works.Add(periodic);
+ if (ExecuteTask is null || ExecuteTask.IsCompleted)
+ StartAsync(CancellationToken.None).Wait();
+ }
+
+ private WorkPeriodic? GetNext()
+ {
+ var work = works
+ .OrderBy(w => w.NextStart)
+ .FirstOrDefault();
+
+ if (work is null || work.NextStart > DateTime.Now)
+ return null;
+
+ return work;
+ }
+}
diff --git a/AsbCloudInfrastructure/Background/Work.cs b/AsbCloudInfrastructure/Background/Work.cs
index 4820aa26..1177892c 100644
--- a/AsbCloudInfrastructure/Background/Work.cs
+++ b/AsbCloudInfrastructure/Background/Work.cs
@@ -13,6 +13,8 @@ namespace AsbCloudInfrastructure.Background;
///
public abstract class Work : BackgroundWorkDto
{
+ private CancellationTokenSource? stoppingCts;
+
private sealed class WorkBase : Work
{
private Func, CancellationToken, Task> ActionAsync { get; }
@@ -69,8 +71,9 @@ public abstract class Work : BackgroundWorkDto
SetStatusStart();
try
{
- var task = Action(Id, services, UpdateStatus, token);
- await task.WaitAsync(Timeout, token);
+ stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(token);
+ var task = Action(Id, services, UpdateStatus, stoppingCts.Token);
+ await task.WaitAsync(Timeout, stoppingCts.Token);
SetStatusComplete();
return true;
}
@@ -97,6 +100,11 @@ public abstract class Work : BackgroundWorkDto
return false;
}
+ public void Stop()
+ {
+ stoppingCts?.Cancel();
+ }
+
private static string FormatExceptionMessage(Exception exception)
{
var firstException = FirstException(exception);
diff --git a/AsbCloudInfrastructure/Background/WorkStore.cs b/AsbCloudInfrastructure/Background/WorkStore.cs
deleted file mode 100644
index be7386e6..00000000
--- a/AsbCloudInfrastructure/Background/WorkStore.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace AsbCloudInfrastructure.Background;
-
-///
-///
-/// Очередь работ
-///
-/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
-///
-public class WorkStore
-{
- private readonly List periodics = new(8);
-
- ///
- /// Список периодических задач
- ///
- public IEnumerable Periodics => periodics;
-
- ///
- /// Работы выполняемые один раз
- ///
- public Queue RunOnceQueue { get; private set; } = new(8);
-
- ///
- /// последние 16 завершившиеся с ошибкой
- ///
- public CyclycArray Felled { get; } = new(16);
-
- ///
- /// последние 16 успешно завершенных
- ///
- public CyclycArray Done { get; } = new(16);
-
- ///
- /// Добавить фоновую работу выполняющуюся с заданным периодом
- ///
- ///
- ///
- public void AddPeriodic(TimeSpan period)
- where T : Work, new()
- {
- var work = new T();
- var periodic = new WorkPeriodic(work, period);
- periodics.Add(periodic);
- }
-
- ///
- /// Добавить фоновую работу выполняющуюся с заданным периодом
- ///
- ///
- ///
- public void AddPeriodic(Work work, TimeSpan period)
- {
- var periodic = new WorkPeriodic(work, period);
- periodics.Add(periodic);
- }
-
- ///
- /// Удаление работы по ID из одноразовой очереди
- ///
- ///
- ///
- public bool TryRemoveFromRunOnceQueue(string id)
- {
- var work = RunOnceQueue.FirstOrDefault(w => w.Id == id);
- if (work is not null)
- {
- RunOnceQueue = new Queue(RunOnceQueue.Where(w => w.Id != id));
- return true;
- }
-
- return false;
- }
-
- ///
- ///
- /// Возвращает приоритетную задачу.
- ///
- ///
- /// Если приоритетные закончились, то ищет ближайшую периодическую.
- /// Если до старта ближайшей периодической работы меньше 20 сек,
- /// то этой задаче устанавливается время последнего запуска в now и она возвращается.
- /// Если больше 20 сек, то возвращается null.
- ///
- ///
- ///
- ///
- public Work? GetNext()
- {
- if (RunOnceQueue.Any())
- return RunOnceQueue.Dequeue();
-
- var work = GetNextPeriodic();
- if (work is null || work.NextStart > DateTime.Now)
- return null;
-
- return work.Work;
- }
-
- private WorkPeriodic? GetNextPeriodic()
- {
- var work = Periodics
- .OrderBy(w => w.NextStart)
- .FirstOrDefault();
- return work;
- }
-}
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 228a3564..c91689a3 100644
--- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
+++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
@@ -513,7 +513,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
if (state.IdState == idStateCreating)
{
var workId = MakeWorkId(idWell);
- if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
+ if (!backgroundWorker.Works.Any(w => w.Id == workId))
{
var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf";
@@ -542,7 +542,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
var work = Work.CreateByDelegate(workId, workAction);
work.OnErrorAsync = onErrorAction;
- backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
+ backgroundWorker.Enqueue(work);
}
}
}
@@ -556,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
- backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId);
+ backgroundWorker.TryRemoveFromQueue(workId);
var filesIds = await context.Files
.Where(f => f.IdWell == idWell &&
diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
index bbbc8196..e56e423f 100644
--- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
+++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs
@@ -52,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email
}
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
- if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId))
+ if (!backgroundWorker.Works.Any(w=>w.Id==workId))
{
var workAction = MakeEmailSendWorkAction(notification);
var work = Work.CreateByDelegate(workId, workAction);
- backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
+ backgroundWorker.Enqueue(work);
}
return Task.CompletedTask;
diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs
index 6a66c30e..ed24bc3e 100644
--- a/AsbCloudInfrastructure/Services/ReportService.cs
+++ b/AsbCloudInfrastructure/Services/ReportService.cs
@@ -95,7 +95,7 @@ namespace AsbCloudInfrastructure.Services
};
var work = Work.CreateByDelegate(workId, workAction);
- backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work);
+ backgroundWorkerService.Enqueue(work);
progressHandler.Invoke(new ReportProgressDto
{
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
index eabfcf77..3a6eac6f 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
@@ -52,7 +52,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
await instance.InitializeCacheFromDBAsync(db, onProgress, token);
});
work.Timeout = TimeSpan.FromMinutes(15);
- worker.WorkStore.RunOnceQueue.Enqueue(work);
+ worker.Enqueue(work);
}
return instance;
}
diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs
index cef6745b..64191e5e 100644
--- a/AsbCloudInfrastructure/Startup.cs
+++ b/AsbCloudInfrastructure/Startup.cs
@@ -33,12 +33,12 @@ namespace AsbCloudInfrastructure
_ = provider.GetRequiredService>();
_ = provider.GetRequiredService>();
- var backgroundWorker = provider.GetRequiredService();
- backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
- backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(15));
- backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
- backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30));
- backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
+ var backgroundWorker = provider.GetRequiredService();
+ backgroundWorker.Add(TimeSpan.FromMinutes(30));
+ backgroundWorker.Add(TimeSpan.FromMinutes(15));
+ backgroundWorker.Add(TimeSpan.FromMinutes(30));
+ backgroundWorker.Add(TimeSpan.FromMinutes(30));
+ backgroundWorker.Add(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
var notificationBackgroundWorker = provider.GetRequiredService();
diff --git a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
index 17a14ab1..7ced750b 100644
--- a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
+++ b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs
@@ -53,11 +53,21 @@ namespace AsbCloudWebApi.Tests.Middlware
throw new NotImplementedException();
}
+ public DatesRangeDto? GetRange(int idWell)
+ {
+ throw new NotImplementedException();
+ }
+
public Task GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token)
{
throw new NotImplementedException();
}
+ public Task GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
+ {
+ throw new NotImplementedException();
+ }
+
public Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException();
public Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
diff --git a/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
new file mode 100644
index 00000000..0f54dddf
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
@@ -0,0 +1,113 @@
+using AsbCloudInfrastructure.Background;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using System;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Services;
+
+public class BackgroundWorkerTest
+{
+ private IServiceProvider provider;
+ private BackgroundWorker service;
+
+ public BackgroundWorkerTest()
+ {
+ provider = Substitute.For();
+ var serviceScope = Substitute.For();
+ var serviceScopeFactory = Substitute.For();
+ serviceScopeFactory.CreateScope().Returns(serviceScope);
+ ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
+
+ service = new BackgroundWorker(provider);
+ typeof(BackgroundWorker)
+ .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
+ .SetValue(service, TimeSpan.FromMilliseconds(1));
+ }
+
+ [Fact]
+ public async Task Enqueue_n_works()
+ {
+ var workCount = 10;
+ var result = 0;
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result++;
+ return Task.Delay(1);
+ }
+
+ //act
+ for (int i = 0; i < workCount; i++)
+ {
+ var work = Work.CreateByDelegate(i.ToString(), workAction);
+ service.Enqueue(work);
+ }
+
+ await service.ExecuteTask;
+
+ //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.Enqueue(badWork);
+ service.Enqueue(goodWork);
+
+ 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/ServicesTests/CrudServiceTestAbstract.cs b/AsbCloudWebApi.Tests/Services/CrudServiceTestAbstract.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/CrudServiceTestAbstract.cs
rename to AsbCloudWebApi.Tests/Services/CrudServiceTestAbstract.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs b/AsbCloudWebApi.Tests/Services/DepositCrudCacheServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/DepositCrudCacheServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs b/AsbCloudWebApi.Tests/Services/DetectedOperationServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/DetectedOperationServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs b/AsbCloudWebApi.Tests/Services/DrillerServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/DrillerServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/Services/DrillingProgramServiceTest.cs
similarity index 99%
rename from AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/DrillingProgramServiceTest.cs
index f0ccd6c5..2a9635c9 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs
+++ b/AsbCloudWebApi.Tests/Services/DrillingProgramServiceTest.cs
@@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
Assert.Equal(2, state.IdState);
- backgroundWorkerMock.Verify(s => s.WorkStore.RunOnceQueue.Enqueue(It.IsAny()));
+ backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny()));
}
[Fact]
diff --git a/AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs b/AsbCloudWebApi.Tests/Services/FileCategoryServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/FileCategoryServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs b/AsbCloudWebApi.Tests/Services/FileServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/FileServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/FileServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/HelpPageServiceTest.cs b/AsbCloudWebApi.Tests/Services/HelpPageServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/HelpPageServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/HelpPageServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/LimitingParameterServiceTest.cs b/AsbCloudWebApi.Tests/Services/LimitingParameterServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/LimitingParameterServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/LimitingParameterServiceTest.cs
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/ServicesTests/SAUB/TelemetryDataSaubCacheTests.cs b/AsbCloudWebApi.Tests/Services/SAUB/TelemetryDataSaubCacheTests.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/SAUB/TelemetryDataSaubCacheTests.cs
rename to AsbCloudWebApi.Tests/Services/SAUB/TelemetryDataSaubCacheTests.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/TrajectoryVisualizationServiceTest.cs b/AsbCloudWebApi.Tests/Services/TrajectoryVisualizationServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/TrajectoryVisualizationServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/TrajectoryVisualizationServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/WellCompositeRepositoryTest.cs b/AsbCloudWebApi.Tests/Services/WellCompositeRepositoryTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/WellCompositeRepositoryTest.cs
rename to AsbCloudWebApi.Tests/Services/WellCompositeRepositoryTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellFinalDocumentsServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/WellFinalDocumentsServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/ServicesTests/WellboreServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellboreServiceTest.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/ServicesTests/WellboreServiceTest.cs
rename to AsbCloudWebApi.Tests/Services/WellboreServiceTest.cs
diff --git a/AsbCloudWebApi.Tests/Services/WorkTest.cs b/AsbCloudWebApi.Tests/Services/WorkTest.cs
new file mode 100644
index 00000000..4f144d98
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/WorkTest.cs
@@ -0,0 +1,150 @@
+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);
+ }
+
+ [Fact]
+ public async Task Stop()
+ {
+ var workTime = TimeSpan.FromMilliseconds(1_000);
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => Task.Delay(workTime, token);
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ _ = work.Start(provider, CancellationToken.None);
+ await Task.Delay(10);
+ work.Stop();
+ await Task.Delay(10);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(0, work.CountErrors);
+ Assert.Null(work.LastComplete);
+ Assert.Null(work.LastError);
+ }
+ }
+}
diff --git a/AsbCloudWebApi/Controllers/BackgroundWorkController.cs b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs
index 0a8c02c0..bb0cdc43 100644
--- a/AsbCloudWebApi/Controllers/BackgroundWorkController.cs
+++ b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs
@@ -14,23 +14,22 @@ namespace AsbCloudWebApi.Controllers
[ApiController]
public class BackgroundWorkController : ControllerBase
{
- private readonly BackgroundWorker backgroundWorker;
+ private readonly BackgroundWorker worker;
- public BackgroundWorkController(BackgroundWorker backgroundWorker)
+ public BackgroundWorkController(BackgroundWorker worker)
{
- this.backgroundWorker = backgroundWorker;
+ this.worker = worker;
}
[HttpGet]
public IActionResult GetAll()
{
var result = new {
- CurrentWork = (BackgroundWorkDto?)backgroundWorker.CurrentWork,
- backgroundWorker.MainLoopLastException,
- RunOnceQueue = backgroundWorker.WorkStore.RunOnceQueue.Select(work => (BackgroundWorkDto)work),
- Periodics = backgroundWorker.WorkStore.Periodics.Select(work => (BackgroundWorkDto)work.Work),
- Done = backgroundWorker.WorkStore.Done.Select(work => (BackgroundWorkDto)work),
- Felled = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work),
+ CurrentWork = (BackgroundWorkDto?)worker.CurrentWork,
+ worker.MainLoopLastException,
+ RunOnceQueue = worker.Works.Select(work => (BackgroundWorkDto)work),
+ Done = worker.Done.Select(work => (BackgroundWorkDto)work),
+ Felled = worker.Felled.Select(work => (BackgroundWorkDto)work),
};
return Ok(result);
}
@@ -38,7 +37,7 @@ namespace AsbCloudWebApi.Controllers
[HttpGet("current")]
public IActionResult GetCurrent()
{
- var work = backgroundWorker.CurrentWork;
+ var work = worker.CurrentWork;
if (work == null)
return NoContent();
@@ -48,22 +47,22 @@ namespace AsbCloudWebApi.Controllers
[HttpGet("failed")]
public IActionResult GetFelled()
{
- var result = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work);
+ var result = worker.Felled.Select(work => (BackgroundWorkDto)work);
return Ok(result);
}
[HttpGet("done")]
public IActionResult GetDone()
{
- var result = backgroundWorker.WorkStore.Done.Select(work => (BackgroundWorkDto)work);
+ var result = worker.Done.Select(work => (BackgroundWorkDto)work);
return Ok(result);
}
[HttpPost("restart"), Obsolete("temporary method")]
public async Task RestartAsync(CancellationToken token)
{
- await backgroundWorker.StopAsync(token);
- await backgroundWorker.StartAsync(token);
+ await worker.StopAsync(token);
+ await worker.StartAsync(token);
return Ok();
}
}
diff --git a/AsbCloudWebApi/Controllers/PeriodicBackgroundWorkerController.cs b/AsbCloudWebApi/Controllers/PeriodicBackgroundWorkerController.cs
new file mode 100644
index 00000000..d7f666a0
--- /dev/null
+++ b/AsbCloudWebApi/Controllers/PeriodicBackgroundWorkerController.cs
@@ -0,0 +1,44 @@
+using AsbCloudApp.Data;
+using AsbCloudInfrastructure.Background;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudWebApi.Controllers
+{
+ [Route("api/[controller]")]
+ [Authorize]
+ [ApiController]
+ public class PeriodicBackgroundWorkerController : ControllerBase
+ {
+ private readonly PeriodicBackgroundWorker worker;
+
+ public PeriodicBackgroundWorkerController(PeriodicBackgroundWorker worker)
+ {
+ this.worker = worker;
+ }
+
+ [HttpGet]
+ public IActionResult GetAll()
+ {
+ var result = new
+ {
+ currentWork = (BackgroundWorkDto?)worker.CurrentWork,
+ worker.MainLoopLastException,
+ works = worker.Works.Select(work => (BackgroundWorkDto)work.Work),
+ };
+ return Ok(result);
+ }
+
+ [HttpPost("restart"), Obsolete("temporary method")]
+ public async Task RestartAsync(CancellationToken token)
+ {
+ await worker.StopAsync(token);
+ await worker.StartAsync(token);
+ return Ok();
+ }
+ }
+}
diff --git a/AsbCloudWebApi/SignalR/ReportsHub.cs b/AsbCloudWebApi/SignalR/ReportsHub.cs
index 1097c5ef..66292d05 100644
--- a/AsbCloudWebApi/SignalR/ReportsHub.cs
+++ b/AsbCloudWebApi/SignalR/ReportsHub.cs
@@ -26,7 +26,7 @@ namespace AsbCloudWebApi.SignalR
await base.AddToGroup(groupName);
var workId = groupName.Replace("Report_", "");
- var work = backgroundWorker.WorkStore.RunOnceQueue.FirstOrDefault(work => work.Id == workId);
+ var work = backgroundWorker.Works.FirstOrDefault(work => work.Id == workId);
var progress = new ReportProgressDto()
{
diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
index 962e5e0f..899d4ad7 100644
--- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
+++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs
@@ -29,12 +29,12 @@ public class SignalRNotificationTransportService : INotificationTransportService
{
var workId = HashCode.Combine(notifications.Select(n => n.Id)).ToString("x");
- if (backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
+ if (backgroundWorker.Works.Any(w => w.Id == workId))
return Task.CompletedTask;
var workAction = MakeSignalRSendWorkAction(notifications);
var work = Work.CreateByDelegate(workId, workAction);
- backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
+ backgroundWorker.Enqueue(work);
return Task.CompletedTask;
}