114 lines
5.7 KiB
C#
114 lines
5.7 KiB
C#
using AyCode.Core.Serializers.Binaries;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AyCode.Services.SignalRs;
|
|
|
|
/// <summary>
|
|
/// Configuration for <see cref="AcBinaryHubProtocol"/> and derived protocols.
|
|
/// Use via <c>services.Configure<AcBinaryHubProtocolOptions>(...)</c> in Program.cs,
|
|
/// or pass an instance directly to the protocol constructor.
|
|
/// </summary>
|
|
public sealed class AcBinaryHubProtocolOptions
|
|
{
|
|
// --- Serializer (standalone sub-group, also usable without SignalR via ToBinary/BinaryTo) ---
|
|
|
|
/// <summary>Binary serializer options. Default: <see cref="AcBinarySerializerOptions.Default"/>.</summary>
|
|
public AcBinarySerializerOptions SerializerOptions { get; set; } = AcBinarySerializerOptions.Default;
|
|
|
|
// --- Transport ---
|
|
|
|
/// <summary>Wire format and pipeline strategy. Default: <see cref="BinaryProtocolMode.Bytes"/>.</summary>
|
|
public BinaryProtocolMode ProtocolMode { get; set; } = BinaryProtocolMode.Bytes;
|
|
|
|
/// <summary>Chunk size for BufferWriterBinaryOutput / AsyncPipeWriterOutput.
|
|
/// Default: 4096 (aligns with Kestrel's slab size for optimal latency-to-first-byte).</summary>
|
|
public int BufferSize { get; set; } = 4096;
|
|
|
|
/// <summary>
|
|
/// Per-chunk flush synchronization in <see cref="BinaryProtocolMode.AsyncSegment"/> mode.
|
|
/// <list type="bullet">
|
|
/// <item><c>true</c> (default): every Grow waits for the previous FlushAsync to complete
|
|
/// before starting the next chunk. Guarantees end-to-end zero-copy (no owned-buffer fallback)
|
|
/// and maximum pipeline parallelism. Best for high-throughput servers with fast consumers.</item>
|
|
/// <item><c>false</c>: fire-and-forget flush per chunk; blocks only when committed bytes exceed
|
|
/// the memory threshold (~60 KB). Itself an adaptive backpressure mode — a fast consumer
|
|
/// never triggers a wait, a slow consumer naturally throttles through buffer pressure.
|
|
/// Safer for mixed consumer speeds and memory-sensitive environments.</item>
|
|
/// </list>
|
|
/// Ignored for Bytes and Segment modes.
|
|
/// </summary>
|
|
public bool WaitForFlush { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Maximum wait for a single synchronous <c>FlushAsync</c> before throwing
|
|
/// <see cref="TimeoutException"/>. Protects against slow/stuck/disconnected consumers
|
|
/// blocking the server thread indefinitely.
|
|
/// <para>
|
|
/// Default: 10 seconds. Rationale: AsyncSegment chunks are max 65 KB (UINT16 size field);
|
|
/// even GPRS-class connections (~60 Kbit/s) transfer 65 KB in ~9 s. If a flush exceeds 10 s,
|
|
/// the consumer is effectively stuck — faster failure detection is preferable to indefinite blocking.
|
|
/// </para>
|
|
/// <para>
|
|
/// Note: complementary to SignalR's connection-level timeouts (<c>ClientTimeoutInterval</c>,
|
|
/// <c>KeepAliveInterval</c>). This is a per-operation guard; SignalR timeouts manage the overall
|
|
/// connection lifetime. Set <c>FlushTimeout < ClientTimeoutInterval</c> so this guard fires first.
|
|
/// </para>
|
|
/// Set to <see cref="System.Threading.Timeout.InfiniteTimeSpan"/> to disable.
|
|
/// </summary>
|
|
public TimeSpan FlushTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
|
|
|
// --- Identity ---
|
|
|
|
/// <summary>Protocol name in the SignalR handshake. Client and server must match. Default: "acbinary".</summary>
|
|
public string Name { get; set; } = "acbinary";
|
|
|
|
// --- Diagnostics ---
|
|
|
|
/// <summary>Optional logger. When null, no protocol-internal logging occurs.</summary>
|
|
public ILogger? Logger { get; set; }
|
|
|
|
// --- Validation ---
|
|
|
|
/// <summary>
|
|
/// Validates option values and platform compatibility.
|
|
/// Throws <see cref="ArgumentOutOfRangeException"/> / <see cref="ArgumentException"/> on invalid values,
|
|
/// or <see cref="PlatformNotSupportedException"/> for unsupported platform/mode combinations.
|
|
/// </summary>
|
|
public void Validate()
|
|
{
|
|
// NOTE: WASM + AsyncSegment send-path guard is currently commented out in the protocol
|
|
// constructor for testing. Once BinaryProtocolMode becomes runtime-configurable in
|
|
// Program.cs, this validation will be re-enabled here as the primary guard.
|
|
//if (OperatingSystem.IsBrowser() && ProtocolMode == BinaryProtocolMode.AsyncSegment)
|
|
// throw new PlatformNotSupportedException(
|
|
// "BinaryProtocolMode.AsyncSegment is not supported on WebAssembly. " +
|
|
// "Use BinaryProtocolMode.Bytes or BinaryProtocolMode.Segment instead.");
|
|
|
|
if (BufferSize < 256 || BufferSize > AsyncPipeWriterOutput.MaxChunkSize)
|
|
throw new ArgumentOutOfRangeException(nameof(BufferSize), BufferSize,
|
|
$"BufferSize must be between 256 and {AsyncPipeWriterOutput.MaxChunkSize} (UINT16 max).");
|
|
|
|
if (FlushTimeout <= TimeSpan.Zero && FlushTimeout != System.Threading.Timeout.InfiniteTimeSpan)
|
|
throw new ArgumentOutOfRangeException(nameof(FlushTimeout), FlushTimeout,
|
|
"FlushTimeout must be positive, or Timeout.InfiniteTimeSpan to disable.");
|
|
|
|
if (string.IsNullOrWhiteSpace(Name))
|
|
throw new ArgumentException("Name cannot be null or whitespace.", nameof(Name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a shallow copy. Required for DI <c>IOptions<T></c> integration —
|
|
/// the singleton from DI must not be mutated by per-connection <c>configure</c> callbacks.
|
|
/// </summary>
|
|
public AcBinaryHubProtocolOptions Clone() => new()
|
|
{
|
|
SerializerOptions = SerializerOptions,
|
|
ProtocolMode = ProtocolMode,
|
|
BufferSize = BufferSize,
|
|
WaitForFlush = WaitForFlush,
|
|
FlushTimeout = FlushTimeout,
|
|
Name = Name,
|
|
Logger = Logger
|
|
};
|
|
}
|