Replace per-message async fire-and-forget with direct-buffer write loop mirroring NatsClient pattern: SpinLock-guarded buffer append, double- buffer swap, single WriteAsync per batch. - MqttConnection: add _directBuf/_writeBuf + RunMqttWriteLoopAsync - MqttConnection: add EnqueuePublishNoFlush (zero-alloc PUBLISH format) - MqttPacketWriter: add WritePublishTo(Span<byte>) + MeasurePublish - MqttTopicMapper: add NatsToMqttBytes with bounded ConcurrentDictionary - MqttNatsClientAdapter: synchronous SendMessageNoFlush + SignalFlush - Skip FlushAsync on plain TCP sockets (TCP auto-flushes)
94 lines
3.1 KiB
C#
94 lines
3.1 KiB
C#
using NATS.Client.Core;
|
|
|
|
namespace NATS.Server.Benchmark.Tests.Infrastructure;
|
|
|
|
/// <summary>
|
|
/// Starts both a Go and .NET NATS server with MQTT and JetStream enabled for MQTT benchmarks.
|
|
/// Shared across all tests in the "Benchmark-Mqtt" collection.
|
|
/// </summary>
|
|
public sealed class MqttServerFixture : IAsyncLifetime
|
|
{
|
|
private GoServerProcess? _goServer;
|
|
private DotNetServerProcess? _dotNetServer;
|
|
private string? _goStoreDir;
|
|
private string? _dotNetStoreDir;
|
|
|
|
public int GoNatsPort => _goServer?.Port ?? throw new InvalidOperationException("Go server not started");
|
|
public int GoMqttPort { get; private set; }
|
|
public int DotNetNatsPort => _dotNetServer?.Port ?? throw new InvalidOperationException(".NET server not started");
|
|
public int DotNetMqttPort { get; private set; }
|
|
public bool GoAvailable => _goServer is not null;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
DotNetMqttPort = PortAllocator.AllocateFreePort();
|
|
_dotNetStoreDir = Path.Combine(Path.GetTempPath(), "nats-bench-dotnet-mqtt-" + Guid.NewGuid().ToString("N")[..8]);
|
|
Directory.CreateDirectory(_dotNetStoreDir);
|
|
|
|
var dotNetConfig = $$"""
|
|
jetstream {
|
|
store_dir: "{{_dotNetStoreDir}}"
|
|
max_mem_store: 64mb
|
|
max_file_store: 256mb
|
|
}
|
|
mqtt {
|
|
listen: 127.0.0.1:{{DotNetMqttPort}}
|
|
}
|
|
""";
|
|
|
|
_dotNetServer = new DotNetServerProcess(dotNetConfig);
|
|
var dotNetTask = _dotNetServer.StartAsync();
|
|
|
|
if (GoServerProcess.IsAvailable())
|
|
{
|
|
GoMqttPort = PortAllocator.AllocateFreePort();
|
|
_goStoreDir = Path.Combine(Path.GetTempPath(), "nats-bench-go-mqtt-" + Guid.NewGuid().ToString("N")[..8]);
|
|
Directory.CreateDirectory(_goStoreDir);
|
|
|
|
var goConfig = $$"""
|
|
jetstream {
|
|
store_dir: "{{_goStoreDir}}"
|
|
max_mem_store: 64mb
|
|
max_file_store: 256mb
|
|
}
|
|
mqtt {
|
|
listen: 127.0.0.1:{{GoMqttPort}}
|
|
}
|
|
""";
|
|
|
|
_goServer = new GoServerProcess(goConfig);
|
|
await Task.WhenAll(dotNetTask, _goServer.StartAsync());
|
|
}
|
|
else
|
|
{
|
|
await dotNetTask;
|
|
}
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
if (_goServer is not null)
|
|
await _goServer.DisposeAsync();
|
|
if (_dotNetServer is not null)
|
|
await _dotNetServer.DisposeAsync();
|
|
|
|
CleanupDir(_goStoreDir);
|
|
CleanupDir(_dotNetStoreDir);
|
|
}
|
|
|
|
public NatsConnection CreateGoNatsClient()
|
|
=> new(new NatsOpts { Url = $"nats://127.0.0.1:{GoNatsPort}" });
|
|
|
|
public NatsConnection CreateDotNetNatsClient()
|
|
=> new(new NatsOpts { Url = $"nats://127.0.0.1:{DotNetNatsPort}" });
|
|
|
|
private static void CleanupDir(string? dir)
|
|
{
|
|
if (dir is not null && Directory.Exists(dir))
|
|
{
|
|
try { Directory.Delete(dir, recursive: true); }
|
|
catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
}
|