513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
98 lines
4.1 KiB
C#
98 lines
4.1 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Hosting.Server;
|
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.ScadaBridge.Communication;
|
|
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.LoadHarness.Probes;
|
|
|
|
/// <summary>
|
|
/// The site half of the wire-path stream probe: a real Kestrel HTTP/2 (h2c) listener on
|
|
/// a loopback ephemeral port, serving the PRODUCTION <see cref="SiteStreamGrpcServer"/>
|
|
/// over the site's real <see cref="ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming.SiteStreamManager"/>.
|
|
///
|
|
/// <para>
|
|
/// This is the hop <see cref="StreamSubscriberProbe"/> deliberately substitutes with a
|
|
/// controllable in-process reader (which the slow-subscriber scenario needs), and
|
|
/// therefore the hop the original runs never exercised: the
|
|
/// <c>SiteStreamEventBatcher</c> pump (R2), protobuf serialization,
|
|
/// <c>IServerStreamWriter.WriteAsync</c>, and HTTP/2 flow control. The server is built
|
|
/// through its production DI constructor with default <see cref="CommunicationOptions"/>,
|
|
/// so the batch negotiation (100 events / 25 ms window), channel capacities and the
|
|
/// concurrency cap are exactly what ships.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Deliberately NOT in the path: <c>ControlPlaneAuthInterceptor</c> (it lives in the
|
|
/// Host project and authenticates once per CALL — one header compare per stream
|
|
/// lifetime, nothing on the per-event path) and TLS (production is h2c too).
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class SiteWireStreamHost : IAsyncDisposable
|
|
{
|
|
private readonly WebApplication _app;
|
|
|
|
/// <summary>The hosted production gRPC server (drop counters, active stream count).</summary>
|
|
public SiteStreamGrpcServer Server { get; }
|
|
|
|
/// <summary>The dialable endpoint, e.g. <c>http://127.0.0.1:54321</c>.</summary>
|
|
public string Endpoint { get; }
|
|
|
|
private SiteWireStreamHost(WebApplication app, SiteStreamGrpcServer server, string endpoint)
|
|
{
|
|
_app = app;
|
|
Server = server;
|
|
Endpoint = endpoint;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds, starts and readies the listener for one site.
|
|
/// </summary>
|
|
/// <param name="site">The site whose stream manager and actor system back the server.</param>
|
|
/// <returns>The started host.</returns>
|
|
public static async Task<SiteWireStreamHost> StartAsync(SiteRuntimeFixture site)
|
|
{
|
|
var server = new SiteStreamGrpcServer(
|
|
site.StreamManager,
|
|
Microsoft.Extensions.Logging.Abstractions.NullLogger<SiteStreamGrpcServer>.Instance,
|
|
Options.Create(new CommunicationOptions()));
|
|
|
|
var builder = WebApplication.CreateSlimBuilder();
|
|
builder.Logging.ClearProviders();
|
|
builder.WebHost.ConfigureKestrel(options =>
|
|
options.Listen(System.Net.IPAddress.Loopback, 0, listenOptions =>
|
|
listenOptions.Protocols = HttpProtocols.Http2));
|
|
builder.Services.AddGrpc();
|
|
builder.Services.AddSingleton(server);
|
|
|
|
var app = builder.Build();
|
|
app.MapGrpcService<SiteStreamGrpcServer>();
|
|
await app.StartAsync();
|
|
|
|
// Same post-start readiness handoff the production host performs.
|
|
server.SetReady(site.System);
|
|
|
|
var address = app.Services.GetRequiredService<IServer>()
|
|
.Features.Get<IServerAddressesFeature>()?.Addresses.FirstOrDefault()
|
|
?? throw new InvalidOperationException("Kestrel bound no address.");
|
|
|
|
return new SiteWireStreamHost(app, server, address);
|
|
}
|
|
|
|
/// <summary>Cancels active streams and stops and disposes the underlying listener.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
// Mirrors the production shutdown ordering: cancel active streams first so
|
|
// their pumps observe cancellation, then stop the listener.
|
|
Server.CancelAllStreams();
|
|
await _app.StopAsync();
|
|
await _app.DisposeAsync();
|
|
}
|
|
}
|