Skip to content

Commit b2fa463

Browse files
committed
Fix ignored exceptions in MqttHostedServer startup
1 parent 93a9d8d commit b2fa463

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3+
<!--TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 WIP: Fix ignored exceptions in MqttHostedServer startup #2102 -->
4+
<PropertyGroup>
5+
<DefineConstants>$(DefineConstants);HOSTEDSERVER_WITHOUT_STARTUP_YIELD</DefineConstants>
6+
</PropertyGroup>
7+
38
<PropertyGroup>
49
<TargetFramework>net8.0</TargetFramework>
510
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>

Source/MQTTnet.AspnetCore/MqttHostedServer.cs

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
2+
////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
3+
#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
14
// Licensed to the .NET Foundation under one or more agreements.
25
// The .NET Foundation licenses this file to you under the MIT license.
36
// See the LICENSE file in the project root for more information.
@@ -45,4 +48,45 @@ void OnStarted()
4548
{
4649
_ = StartAsync();
4750
}
48-
}
51+
}
52+
53+
#else
54+
// Licensed to the .NET Foundation under one or more agreements.
55+
// The .NET Foundation licenses this file to you under the MIT license.
56+
// See the LICENSE file in the project root for more information.
57+
58+
using System;
59+
using System.Collections.Generic;
60+
using System.Threading;
61+
using System.Threading.Tasks;
62+
using Microsoft.Extensions.Hosting;
63+
using MQTTnet.Diagnostics.Logger;
64+
using MQTTnet.Server;
65+
66+
namespace MQTTnet.AspNetCore;
67+
68+
public sealed class MqttHostedServer : BackgroundService
69+
{
70+
readonly MqttServerFactory _mqttFactory;
71+
public MqttHostedServer(
72+
MqttServerFactory mqttFactory,
73+
MqttServerOptions options,
74+
IEnumerable<IMqttServerAdapter> adapters,
75+
IMqttNetLogger logger
76+
)
77+
{
78+
MqttServer = new(options, adapters, logger);
79+
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
80+
}
81+
82+
public MqttServer MqttServer { get; }
83+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
84+
=> MqttServer.StartAsync();
85+
public override async Task StopAsync(CancellationToken cancellationToken)
86+
{
87+
await MqttServer.StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
88+
await base.StopAsync(cancellationToken);
89+
}
90+
}
91+
92+
#endif

Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs

+8
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,16 @@ public static void AddHostedMqttServer(this IServiceCollection services)
4545
services.TryAddSingleton(new MqttServerFactory());
4646

4747
services.AddSingleton<MqttHostedServer>();
48+
//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
49+
////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
50+
#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
4851
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
4952
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
53+
#else
54+
services.AddHostedService(s => s.GetService<MqttHostedServer>());
55+
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>().MqttServer);
56+
#endif
57+
5058
}
5159

5260
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using MQTTnet.AspNetCore;
4+
using System.Net.Sockets;
5+
using System.Net;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using MQTTnet.Server;
9+
using Microsoft.AspNetCore.Hosting;
10+
11+
namespace MQTTnet.Tests.ASP
12+
{
13+
[TestClass]
14+
public class MqttHostedServerStartup
15+
{
16+
private async Task TestStartup(bool useOccupiedPort)
17+
{
18+
using TcpListener l = new TcpListener(IPAddress.Any, 0);
19+
l.Start();
20+
int port = ((IPEndPoint)l.LocalEndpoint).Port;
21+
22+
if(!useOccupiedPort)
23+
l.Stop();
24+
25+
26+
var builder = WebApplication.CreateBuilder();
27+
builder.WebHost.UseUrls("http://127.0.0.1:0");
28+
29+
builder.Services.AddMqttTcpServerAdapter();
30+
builder.Services.AddHostedMqttServer(cfg =>
31+
{
32+
cfg
33+
.WithDefaultEndpoint()
34+
.WithDefaultEndpointPort(port);
35+
});
36+
37+
38+
var app = builder.Build();
39+
40+
if(!useOccupiedPort)
41+
{
42+
await app.StartAsync();
43+
var server = app.Services.GetRequiredService<MqttServer>();
44+
Assert.IsTrue(server.IsStarted);
45+
await app.StopAsync();
46+
}
47+
else
48+
{
49+
await Assert.ThrowsExceptionAsync<SocketException>(() =>
50+
app.StartAsync()
51+
);
52+
}
53+
}
54+
55+
[TestMethod]
56+
[DoNotParallelize]
57+
public Task TestSuccessfullyStartup()
58+
=> TestStartup(useOccupiedPort: false);
59+
60+
[TestMethod]
61+
[DoNotParallelize]
62+
public Task TestFailedStartup()
63+
=> TestStartup(useOccupiedPort: true);
64+
65+
}
66+
}

0 commit comments

Comments
 (0)