diff --git a/Core/SmartIOT.Connector.Core/Scheduler/ITagScheduler.cs b/Core/SmartIOT.Connector.Core/Scheduler/ITagScheduler.cs index e4601d3..9e90811 100644 --- a/Core/SmartIOT.Connector.Core/Scheduler/ITagScheduler.cs +++ b/Core/SmartIOT.Connector.Core/Scheduler/ITagScheduler.cs @@ -27,9 +27,9 @@ public interface ITagScheduler public IDeviceDriver DeviceDriver { get; } public Device Device { get; } - void Start(); + Task StartAsync(); - void Stop(); + Task StopAsync(); /// /// Questo metodo consente di eseguire una action di inizializzazione, diff --git a/Core/SmartIOT.Connector.Core/Scheduler/TagScheduler.cs b/Core/SmartIOT.Connector.Core/Scheduler/TagScheduler.cs index e7cccb2..ffc5210 100644 --- a/Core/SmartIOT.Connector.Core/Scheduler/TagScheduler.cs +++ b/Core/SmartIOT.Connector.Core/Scheduler/TagScheduler.cs @@ -12,7 +12,9 @@ public class TagScheduler : ITagScheduler private readonly SchedulerConfiguration _configuration; private readonly CancellationTokenSource _terminatingToken = new CancellationTokenSource(); private readonly Thread _schedulerThread; + private readonly SemaphoreSlim _schedulerTerminated = new SemaphoreSlim(0, 1); private readonly Thread _monitorThread; + private readonly SemaphoreSlim _monitorThreadTerminated = new SemaphoreSlim(0, 1); private DateTime _terminatingInstant; private DateTime _lastWriteOnDevice; private readonly ConcurrentDictionary _lastDeviceStatusEvents = new ConcurrentDictionary(); @@ -96,116 +98,134 @@ private void OnEngineExceptionHandler(object? sender, ExceptionEventArgs e) private void SchedulerThreadRun() { - while (true) + try { - if (!_terminatingToken.IsCancellationRequested && IsPaused) + while (true) { - try - { - _terminatingToken.Token.WaitHandle.WaitOne(1000); - } - catch (OperationCanceledException) + if (!_terminatingToken.IsCancellationRequested && IsPaused) { - // we must verify if stopping application is possible or if there is something more to write to tags - } - - continue; - } - - if (_terminatingToken.IsCancellationRequested) - { - var lastWriteOrTerminateInstant = _terminatingInstant < _lastWriteOnDevice ? _lastWriteOnDevice : _terminatingInstant; - var now = _timeService.Now; - - if (_timeService.IsTimeoutElapsed(lastWriteOrTerminateInstant, now, _configuration.TerminateAfterNoWriteRequestsDelay) - && _timeService.IsTimeoutElapsed(_terminatingInstant, now, _configuration.TerminateMinimumDelay)) - break; - } - - try - { - try - { - var schedule = _tagSchedulerEngine.ScheduleNextTag(_terminatingToken.IsCancellationRequested); - if (schedule != null) + try { - if (schedule.Type == TagScheduleType.WRITE) - _lastWriteOnDevice = _timeService.Now; + _terminatingToken.Token.WaitHandle.WaitOne(1000); } - else + catch (OperationCanceledException) { - Thread.Sleep(1000); + // we must verify if stopping application is possible or if there is something more to write to tags } + + continue; } - catch (TagSchedulerWaitException ex) + + if (_terminatingToken.IsCancellationRequested) { - TagSchedulerWaitExceptionEvent?.Invoke(this, new TagSchedulerWaitExceptionEventArgs(ex)); + var lastWriteOrTerminateInstant = _terminatingInstant < _lastWriteOnDevice ? _lastWriteOnDevice : _terminatingInstant; + var now = _timeService.Now; - Thread.Sleep(ex.WaitTime); + if (_timeService.IsTimeoutElapsed(lastWriteOrTerminateInstant, now, _configuration.TerminateAfterNoWriteRequestsDelay) + && _timeService.IsTimeoutElapsed(_terminatingInstant, now, _configuration.TerminateMinimumDelay)) + break; } - } - catch (Exception ex) - { + try { - ExceptionHandler?.Invoke(this, new ExceptionEventArgs(ex)); + try + { + var schedule = _tagSchedulerEngine.ScheduleNextTag(_terminatingToken.IsCancellationRequested); + if (schedule != null) + { + if (schedule.Type == TagScheduleType.WRITE) + _lastWriteOnDevice = _timeService.Now; + } + else + { + Thread.Sleep(1000); + } + } + catch (TagSchedulerWaitException ex) + { + TagSchedulerWaitExceptionEvent?.Invoke(this, new TagSchedulerWaitExceptionEventArgs(ex)); + + Thread.Sleep(ex.WaitTime); + } } - catch + catch (Exception ex) { - // ignore this + try + { + ExceptionHandler?.Invoke(this, new ExceptionEventArgs(ex)); + } + catch + { + // ignore this + } } } } + finally + { + _schedulerTerminated.Release(); + } } private void MonitorThreadRun() { - while (true) + try { - try - { - _tagSchedulerEngine.RestartDriver(); - - if (!_terminatingToken.IsCancellationRequested) - _terminatingToken.Token.WaitHandle.WaitOne(500); - else - break; - } - catch (OperationCanceledException) - { - // stop request: exit - } - catch (Exception ex) + while (true) { try { - ExceptionHandler?.Invoke(this, new ExceptionEventArgs(ex)); + _tagSchedulerEngine.RestartDriver(); + + if (!_terminatingToken.IsCancellationRequested) + _terminatingToken.Token.WaitHandle.WaitOne(500); + else + break; + } + catch (OperationCanceledException) + { + // stop request: exit } - catch + catch (Exception ex) { - // ignore this + try + { + ExceptionHandler?.Invoke(this, new ExceptionEventArgs(ex)); + } + catch + { + // ignore this + } } } } + finally + { + _monitorThreadTerminated.Release(); + } } - public void Start() + public Task StartAsync() { SchedulerStarting?.Invoke(this, new SchedulerStartingEventArgs(this)); _schedulerThread.Start(); _monitorThread.Start(); + + return Task.CompletedTask; } - public void Stop() + public async Task StopAsync() { SchedulerStopping?.Invoke(this, new SchedulerStoppingEventArgs(this)); _terminatingToken.Cancel(); _terminatingInstant = _timeService.Now; - _schedulerThread.Join(); - _monitorThread.Join(); + await _schedulerTerminated.WaitAsync(); + await _monitorThreadTerminated.WaitAsync(); + + _terminatingToken.Dispose(); _tagSchedulerEngine.ExceptionHandler -= OnEngineExceptionHandler; _tagSchedulerEngine.DeviceStatusEvent -= OnEngineDeviceStatusEvent; diff --git a/Core/SmartIOT.Connector.Core/SmartIotConnector.cs b/Core/SmartIOT.Connector.Core/SmartIotConnector.cs index b6b1b60..09678d4 100644 --- a/Core/SmartIOT.Connector.Core/SmartIotConnector.cs +++ b/Core/SmartIOT.Connector.Core/SmartIotConnector.cs @@ -83,7 +83,7 @@ public async Task StartAsync() foreach (var scheduler in _schedulers) { - scheduler.Start(); + await scheduler.StartAsync(); } IsStarted = true; @@ -95,17 +95,14 @@ public async Task StopAsync() { Stopping?.Invoke(this, new EventArgs()); - foreach (var scheduler in _schedulers) + await Task.WhenAll(_connectors.Select(async x => { - scheduler.Stop(); - } + await x.StopAsync(); - foreach (var connector in _connectors) - { - await connector.StopAsync(); + RemoveConnectorEvents(x); + })); - RemoveConnectorEvents(connector); - } + await Task.WhenAll(_schedulers.Select(x => x.StopAsync())); Stopped?.Invoke(this, new EventArgs()); } @@ -199,17 +196,34 @@ public void AddScheduler(ITagScheduler scheduler) scheduler.SchedulerStarting += OnSchedulerStarting; scheduler.SchedulerStopping += OnSchedulerStopping; + } + + public async Task AddSchedulerAsync(ITagScheduler scheduler) + { + _schedulers.Add(scheduler); + + scheduler.TagReadEvent += OnSchedulerTagReadEvent; + scheduler.TagWriteEvent += OnSchedulerTagWriteEvent; + scheduler.DeviceStatusEvent += OnSchedulerDeviceStatusEvent; + scheduler.ExceptionHandler += OnSchedulerException; + + scheduler.EngineRestartingEvent += OnSchedulerRestartingEvent; + scheduler.EngineRestartedEvent += OnSchedulerRestartedEvent; + scheduler.TagSchedulerWaitExceptionEvent += OnSchedulerWaitExceptionEvent; + + scheduler.SchedulerStarting += OnSchedulerStarting; + scheduler.SchedulerStopping += OnSchedulerStopping; if (IsStarted) - scheduler.Start(); + await scheduler.StartAsync(); } - public void RemoveScheduler(ITagScheduler scheduler) + public async Task RemoveSchedulerAsync(ITagScheduler scheduler) { _schedulers.Remove(scheduler); if (IsStarted) - scheduler.Stop(); + await scheduler.StopAsync(); scheduler.TagReadEvent -= OnSchedulerTagReadEvent; scheduler.TagWriteEvent -= OnSchedulerTagWriteEvent; diff --git a/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs b/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs index bb8d835..8e7279c 100644 --- a/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs +++ b/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs @@ -12,9 +12,11 @@ public static IServiceCollection AddSmartIOTConnector(this IServiceCollection se var builder = new SmartIotConnectorBuilder(); configure?.Invoke(builder); + ArgumentNullException.ThrowIfNull(builder.Configuration); // add main stuffs services.AddSingleton(builder); + services.AddSingleton(builder.Configuration); services.AddSingleton(builder.Build); // expose more things on DI diff --git a/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj b/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj index fbd473e..9ee5154 100644 --- a/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj +++ b/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj @@ -17,13 +17,20 @@ - - - + - + + + + + + + + + + diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs index 9fac066..55837fd 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs @@ -63,11 +63,11 @@ public IActionResult GetDeviceConfiguration(string deviceId) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Route("configuration")] - public IActionResult AddDevice([FromBody] DeviceConfiguration deviceConfiguration) + public async Task AddDevice([FromBody] DeviceConfiguration deviceConfiguration) { try { - _deviceService.AddDevice(deviceConfiguration); + await _deviceService.AddDeviceAsync(deviceConfiguration); return Ok(); } catch (DeviceException ex) @@ -85,11 +85,11 @@ public IActionResult AddDevice([FromBody] DeviceConfiguration deviceConfiguratio [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Route("configuration/{deviceId}")] - public IActionResult RemoveDevice(string deviceId) + public async Task RemoveDevice(string deviceId) { try { - _deviceService.RemoveDevice(deviceId); + await _deviceService.RemoveDeviceAsync(deviceId); return Ok(); } catch (DeviceException ex) diff --git a/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs b/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs index cf6e013..8bbaee3 100644 --- a/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs +++ b/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs @@ -50,16 +50,16 @@ public IList GetDevices() .FirstOrDefault(); } - public void AddDevice(DeviceConfiguration deviceConfiguration) + public async Task AddDeviceAsync(DeviceConfiguration deviceConfiguration) { var driver = _deviceDriverFactory.CreateDriver(deviceConfiguration); if (driver == null) throw new DeviceException($"DeviceConfiguration not valid {deviceConfiguration.ConnectionString}"); - _smartIotConnector.AddScheduler(_schedulerFactory.CreateScheduler(driver.Name, driver, _timeService, _smartIotConnector.SchedulerConfiguration)); + await _smartIotConnector.AddSchedulerAsync(_schedulerFactory.CreateScheduler(driver.Name, driver, _timeService, _smartIotConnector.SchedulerConfiguration)); } - public void RemoveDevice(string deviceId) + public async Task RemoveDeviceAsync(string deviceId) { var scheduler = _smartIotConnector.Schedulers .FirstOrDefault(x => x.Device.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); @@ -67,7 +67,7 @@ public void RemoveDevice(string deviceId) if (scheduler == null) throw new DeviceException($"Device {deviceId} does not exists"); - _smartIotConnector.RemoveScheduler(scheduler); + await _smartIotConnector.RemoveSchedulerAsync(scheduler); } public TagData? GetTagData(string deviceId, string tagId) diff --git a/Core/SmartIOT.Connector.RestApi/Services/IDeviceService.cs b/Core/SmartIOT.Connector.RestApi/Services/IDeviceService.cs index 14ac04e..a476aa6 100644 --- a/Core/SmartIOT.Connector.RestApi/Services/IDeviceService.cs +++ b/Core/SmartIOT.Connector.RestApi/Services/IDeviceService.cs @@ -13,9 +13,9 @@ public interface IDeviceService public Device? GetDevice(string deviceId); - public void AddDevice(DeviceConfiguration deviceConfiguration); + public Task AddDeviceAsync(DeviceConfiguration deviceConfiguration); - public void RemoveDevice(string deviceId); + public Task RemoveDeviceAsync(string deviceId); public TagData? GetTagData(string deviceId, string tagId); diff --git a/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj b/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj index b18422a..a4728c4 100644 --- a/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj +++ b/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj @@ -26,8 +26,6 @@ - - diff --git a/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj b/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj index 46aade7..3dbaf65 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj +++ b/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj @@ -12,8 +12,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Core.Tests/TagSchedulerTests.cs b/Tests/SmartIOT.Connector.Core.Tests/TagSchedulerTests.cs index 16f5a0e..d89ec77 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/TagSchedulerTests.cs +++ b/Tests/SmartIOT.Connector.Core.Tests/TagSchedulerTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Xunit; namespace SmartIOT.Connector.Core.Tests; @@ -38,7 +39,7 @@ private static (TagSchedulerEngine engine, Model.Device device, Tag tag20, Tag? } [Fact] - public void TestScheduler() + public async Task TestScheduler() { var timeService = new TimeService(); @@ -77,12 +78,12 @@ public void TestScheduler() { deviceStatusEvent.Set(); }; - scheduler.Start(); + await scheduler.StartAsync(); Assert.True(readEvent.WaitOne(100)); Assert.True(deviceStatusEvent.WaitOne(100)); - scheduler.Stop(); + await scheduler.StopAsync(); Assert.NotEmpty(schedulerEventListener.TagReadEvents); Assert.Empty(schedulerEventListener.TagWriteEvents); diff --git a/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj b/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj index 53a94cc..164d7f0 100644 --- a/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj +++ b/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj @@ -10,8 +10,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj b/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj index 0822bd0..163b86d 100644 --- a/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj +++ b/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj @@ -10,8 +10,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Prometheus.Tests/TestMetrics.cs b/Tests/SmartIOT.Connector.Prometheus.Tests/TestMetrics.cs index ddcec54..ab5788c 100644 --- a/Tests/SmartIOT.Connector.Prometheus.Tests/TestMetrics.cs +++ b/Tests/SmartIOT.Connector.Prometheus.Tests/TestMetrics.cs @@ -25,6 +25,7 @@ public async Task Test_prometheus_metrics() smartiot.Stopped += (s, e) => stopped.Set(); await smartiot.StartAsync(); + bool wasStopped = false; try { Assert.True(started.WaitOne(TimeSpan.FromSeconds(2))); @@ -40,10 +41,12 @@ public async Task Test_prometheus_metrics() await smartiot.StopAsync(); Assert.True(stopped.WaitOne(TimeSpan.FromSeconds(2))); + wasStopped = true; } finally { - await smartiot.StopAsync(); + if (!wasStopped) + await smartiot.StopAsync(); } } } \ No newline at end of file diff --git a/Tests/SmartIOT.Connector.RestApi.Tests/DeviceControllerTests.cs b/Tests/SmartIOT.Connector.RestApi.Tests/DeviceControllerTests.cs index 7a3492d..7a3418f 100644 --- a/Tests/SmartIOT.Connector.RestApi.Tests/DeviceControllerTests.cs +++ b/Tests/SmartIOT.Connector.RestApi.Tests/DeviceControllerTests.cs @@ -159,11 +159,11 @@ public void Test_EnableDevice() } [Fact] - public void Test_AddRemoveUpdate_Device_and_Tag() + public async Task Test_AddRemoveUpdate_Device_and_Tag() { var controller = SetupController(); - Assert.IsType(controller.AddDevice(new DeviceConfiguration("mock://", "2", true, "D2", new List() + Assert.IsType(await controller.AddDevice(new DeviceConfiguration("mock://", "2", true, "D2", new List() { new TagConfiguration("DB100", TagType.READ, 0, 10, 1) }))); @@ -207,7 +207,7 @@ public void Test_AddRemoveUpdate_Device_and_Tag() Assert.Equal(100, d.Tags[0].Size); Assert.Equal(2, d.Tags[0].Weight); - Assert.IsType(controller.RemoveDevice("2")); + Assert.IsType(await controller.RemoveDevice("2")); Assert.IsType(controller.GetDeviceConfiguration("2")); } diff --git a/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj b/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj index a05d4d2..2219f69 100644 --- a/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj +++ b/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj @@ -10,8 +10,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj b/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj index ef49813..1a199ce 100644 --- a/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj +++ b/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj @@ -11,8 +11,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all