From b2fa463da624d17bad07fdf14dbb2597ff8315fe Mon Sep 17 00:00:00 2001 From: beppemarazzi <beppemarazzi@gmail.com> Date: Tue, 12 Nov 2024 15:20:44 +0100 Subject: [PATCH 1/2] Fix ignored exceptions in MqttHostedServer startup --- .../MQTTnet.AspNetCore.csproj | 5 ++ Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 46 ++++++++++++- .../ServiceCollectionExtensions.cs | 8 +++ .../ASP/MqttHostedServerStartup.cs | 66 +++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 5357dd702..022747b51 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -1,5 +1,10 @@ <Project Sdk="Microsoft.NET.Sdk"> + <!--TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 WIP: Fix ignored exceptions in MqttHostedServer startup #2102 --> + <PropertyGroup> + <DefineConstants>$(DefineConstants);HOSTEDSERVER_WITHOUT_STARTUP_YIELD</DefineConstants> + </PropertyGroup> + <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <AssemblyName>MQTTnet.AspNetCore</AssemblyName> diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs index 4c74f6a43..69ce0b39a 100644 --- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs +++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -1,3 +1,6 @@ +//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 +////WIP: Fix ignored exceptions in MqttHostedServer startup #2102 +#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -45,4 +48,45 @@ void OnStarted() { _ = StartAsync(); } -} \ No newline at end of file +} + +#else +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using MQTTnet.Diagnostics.Logger; +using MQTTnet.Server; + +namespace MQTTnet.AspNetCore; + +public sealed class MqttHostedServer : BackgroundService +{ + readonly MqttServerFactory _mqttFactory; + public MqttHostedServer( + MqttServerFactory mqttFactory, + MqttServerOptions options, + IEnumerable<IMqttServerAdapter> adapters, + IMqttNetLogger logger + ) + { + MqttServer = new(options, adapters, logger); + _mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory)); + } + + public MqttServer MqttServer { get; } + protected override Task ExecuteAsync(CancellationToken stoppingToken) + => MqttServer.StartAsync(); + public override async Task StopAsync(CancellationToken cancellationToken) + { + await MqttServer.StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build()); + await base.StopAsync(cancellationToken); + } +} + +#endif \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 915f6791c..6f889ae7c 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -45,8 +45,16 @@ public static void AddHostedMqttServer(this IServiceCollection services) services.TryAddSingleton(new MqttServerFactory()); services.AddSingleton<MqttHostedServer>(); + //TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 + ////WIP: Fix ignored exceptions in MqttHostedServer startup #2102 +#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>()); services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>()); +#else + services.AddHostedService(s => s.GetService<MqttHostedServer>()); + services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>().MqttServer); +#endif + } public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure) diff --git a/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs b/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs new file mode 100644 index 000000000..d3b1e9cd5 --- /dev/null +++ b/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.AspNetCore; +using System.Net.Sockets; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.Server; +using Microsoft.AspNetCore.Hosting; + +namespace MQTTnet.Tests.ASP +{ + [TestClass] + public class MqttHostedServerStartup + { + private async Task TestStartup(bool useOccupiedPort) + { + using TcpListener l = new TcpListener(IPAddress.Any, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + + if(!useOccupiedPort) + l.Stop(); + + + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseUrls("http://127.0.0.1:0"); + + builder.Services.AddMqttTcpServerAdapter(); + builder.Services.AddHostedMqttServer(cfg => + { + cfg + .WithDefaultEndpoint() + .WithDefaultEndpointPort(port); + }); + + + var app = builder.Build(); + + if(!useOccupiedPort) + { + await app.StartAsync(); + var server = app.Services.GetRequiredService<MqttServer>(); + Assert.IsTrue(server.IsStarted); + await app.StopAsync(); + } + else + { + await Assert.ThrowsExceptionAsync<SocketException>(() => + app.StartAsync() + ); + } + } + + [TestMethod] + [DoNotParallelize] + public Task TestSuccessfullyStartup() + => TestStartup(useOccupiedPort: false); + + [TestMethod] + [DoNotParallelize] + public Task TestFailedStartup() + => TestStartup(useOccupiedPort: true); + + } +} From 43bb94a704067403b372e94746bfcde2a4d45ab6 Mon Sep 17 00:00:00 2001 From: beppemarazzi <beppemarazzi@gmail.com> Date: Mon, 3 Mar 2025 09:19:12 +0100 Subject: [PATCH 2/2] Cleaning WIP --- .../MQTTnet.AspNetCore.csproj | 7 +-- Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 55 ------------------- .../ServiceCollectionExtensions.cs | 9 +-- 3 files changed, 2 insertions(+), 69 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 022747b51..fb585a66b 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -1,10 +1,5 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> - <!--TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 WIP: Fix ignored exceptions in MqttHostedServer startup #2102 --> - <PropertyGroup> - <DefineConstants>$(DefineConstants);HOSTEDSERVER_WITHOUT_STARTUP_YIELD</DefineConstants> - </PropertyGroup> - <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <AssemblyName>MQTTnet.AspNetCore</AssemblyName> diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs index 69ce0b39a..3dfd6c648 100644 --- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs +++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -1,56 +1,3 @@ -//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 -////WIP: Fix ignored exceptions in MqttHostedServer startup #2102 -#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using MQTTnet.Diagnostics.Logger; -using MQTTnet.Server; - -namespace MQTTnet.AspNetCore; - -public sealed class MqttHostedServer : MqttServer, IHostedService -{ - readonly IHostApplicationLifetime _hostApplicationLifetime; - readonly MqttServerFactory _mqttFactory; - - public MqttHostedServer( - IHostApplicationLifetime hostApplicationLifetime, - MqttServerFactory mqttFactory, - MqttServerOptions options, - IEnumerable<IMqttServerAdapter> adapters, - IMqttNetLogger logger) : base(options, adapters, logger) - { - _mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory)); - _hostApplicationLifetime = hostApplicationLifetime; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - // The yield makes sure that the hosted service is considered up and running. - await Task.Yield(); - - _hostApplicationLifetime.ApplicationStarted.Register(OnStarted); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build()); - } - - void OnStarted() - { - _ = StartAsync(); - } -} - -#else // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -88,5 +35,3 @@ public override async Task StopAsync(CancellationToken cancellationToken) await base.StopAsync(cancellationToken); } } - -#endif \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 6f889ae7c..3816ad700 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -45,15 +45,8 @@ public static void AddHostedMqttServer(this IServiceCollection services) services.TryAddSingleton(new MqttServerFactory()); services.AddSingleton<MqttHostedServer>(); - //TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 - ////WIP: Fix ignored exceptions in MqttHostedServer startup #2102 -#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD - services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>()); - services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>()); -#else services.AddHostedService(s => s.GetService<MqttHostedServer>()); - services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>().MqttServer); -#endif + services.AddSingleton(s => s.GetService<MqttHostedServer>().MqttServer); }