fix(dotnet-client): no ack retry, caller-owned channels, safe session dispose, typed diagnostics, AttachSession
This commit is contained in:
@@ -14,7 +14,8 @@ namespace ZB.MOM.WW.MxGateway.Client;
|
||||
/// </summary>
|
||||
public sealed class MxGatewayClient : IAsyncDisposable
|
||||
{
|
||||
private readonly GrpcChannel _channel;
|
||||
private readonly GrpcChannel? _channel;
|
||||
private readonly bool _ownsChannel;
|
||||
private readonly IMxGatewayClientTransport _transport;
|
||||
private readonly ResiliencePipeline _safeUnaryRetryPipeline;
|
||||
private bool _disposed;
|
||||
@@ -36,14 +37,16 @@ public sealed class MxGatewayClient : IAsyncDisposable
|
||||
_safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create(
|
||||
options.Retry,
|
||||
options.LoggerFactory?.CreateLogger<MxGatewayClient>());
|
||||
_channel = null!;
|
||||
_channel = null;
|
||||
}
|
||||
|
||||
private MxGatewayClient(
|
||||
GrpcChannel channel,
|
||||
IMxGatewayClientTransport transport)
|
||||
IMxGatewayClientTransport transport,
|
||||
bool ownsChannel)
|
||||
{
|
||||
_channel = channel;
|
||||
_ownsChannel = ownsChannel;
|
||||
_transport = transport;
|
||||
Options = transport.Options;
|
||||
_safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create(
|
||||
@@ -88,7 +91,58 @@ public sealed class MxGatewayClient : IAsyncDisposable
|
||||
channel,
|
||||
new GrpcMxGatewayClientTransport(
|
||||
options,
|
||||
new MxAccessGateway.MxAccessGatewayClient(channel)));
|
||||
new MxAccessGateway.MxAccessGatewayClient(channel)),
|
||||
ownsChannel: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a gateway client over a caller-owned <see cref="GrpcChannel"/>. The client
|
||||
/// does not dispose the channel; the caller controls its lifetime and any TLS/handler
|
||||
/// configuration. <see cref="MxGatewayClientOptions.Endpoint"/> is still validated but
|
||||
/// the channel's own address is used for transport.
|
||||
/// </summary>
|
||||
/// <param name="channel">An existing channel, shared or pre-configured by the caller.</param>
|
||||
/// <param name="options">Client configuration options (API key, timeouts, retry).</param>
|
||||
/// <returns>A new gateway client instance that does not own the channel.</returns>
|
||||
public static MxGatewayClient Create(
|
||||
GrpcChannel channel,
|
||||
MxGatewayClientOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
options.Validate();
|
||||
|
||||
return new MxGatewayClient(
|
||||
channel,
|
||||
new GrpcMxGatewayClientTransport(
|
||||
options,
|
||||
new MxAccessGateway.MxAccessGatewayClient(channel)),
|
||||
ownsChannel: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a session object to an existing gateway session id without calling
|
||||
/// OpenSession, so a reconnecting process can resume
|
||||
/// <see cref="MxGatewaySession.StreamEventsAsync"/> with <c>afterWorkerSequence</c>.
|
||||
/// The gateway only allows the API key that opened the session to stream its events
|
||||
/// or invoke commands on it; any other key is rejected with PermissionDenied. The
|
||||
/// returned session's <see cref="MxGatewaySession.OpenSessionReply"/> carries only the
|
||||
/// session id.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The id of a session previously opened with this client's API key.</param>
|
||||
/// <returns>A session bound to <paramref name="sessionId"/>.</returns>
|
||||
public MxGatewaySession AttachSession(string sessionId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sessionId);
|
||||
ThrowIfDisposed();
|
||||
|
||||
return new MxGatewaySession(
|
||||
this,
|
||||
new OpenSessionReply
|
||||
{
|
||||
SessionId = sessionId,
|
||||
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,11 +253,13 @@ public sealed class MxGatewayClient : IAsyncDisposable
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ThrowIfDisposed();
|
||||
|
||||
// Deliberately not wrapped in the retry pipeline: an acknowledge is a
|
||||
// non-idempotent operator write that re-sends the operator password and
|
||||
// can double-ack in the Historian.
|
||||
try
|
||||
{
|
||||
return await ExecuteSafeUnaryAsync(
|
||||
token => _transport.AcknowledgeAlarmAsync(request, CreateCallOptions(token)),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return await _transport.AcknowledgeAlarmAsync(request, CreateCallOptions(cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (MxGatewayException ex) when (request.OperatorPassword.Length > 0)
|
||||
{
|
||||
@@ -266,7 +322,11 @@ public sealed class MxGatewayClient : IAsyncDisposable
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_channel?.Dispose();
|
||||
if (_ownsChannel)
|
||||
{
|
||||
_channel?.Dispose();
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user