diff --git a/build/dependencies.props b/build/dependencies.props index 93a8386b7..b42ef0c5c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,7 +5,7 @@ 3.23.1 2.55.0 2.46.6 - 2.57.0 + 2.58.0 8.0.0-rc.1.23378.7 7.0.5 6.0.11 diff --git a/build/version.props b/build/version.props index cb4e8e316..5ce5fb472 100644 --- a/build/version.props +++ b/build/version.props @@ -2,13 +2,13 @@ - 2.57.0-dev + 2.58.0-pre1 2.0.0.0 - 2.57.0.0 + 2.58.0.0 diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs index b78bb5653..f570dae64 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -39,7 +39,7 @@ internal ProviderServiceBinder(ServiceMethodProviderContext context, T _declaringType = declaringType; } - public override void AddMethod(Method method, ClientStreamingServerMethod handler) + public override void AddMethod(Method method, ClientStreamingServerMethod? handler) { var (invoker, metadata) = CreateModelCore>( method.Name, @@ -48,7 +48,7 @@ public override void AddMethod(Method _context.AddClientStreamingMethod(method, metadata, invoker); } - public override void AddMethod(Method method, DuplexStreamingServerMethod handler) + public override void AddMethod(Method method, DuplexStreamingServerMethod? handler) { var (invoker, metadata) = CreateModelCore>( method.Name, @@ -57,7 +57,7 @@ public override void AddMethod(Method _context.AddDuplexStreamingMethod(method, metadata, invoker); } - public override void AddMethod(Method method, ServerStreamingServerMethod handler) + public override void AddMethod(Method method, ServerStreamingServerMethod? handler) { var (invoker, metadata) = CreateModelCore>( method.Name, @@ -66,7 +66,7 @@ public override void AddMethod(Method _context.AddServerStreamingMethod(method, metadata, invoker); } - public override void AddMethod(Method method, UnaryServerMethod handler) + public override void AddMethod(Method method, UnaryServerMethod? handler) { var (invoker, metadata) = CreateModelCore>( method.Name, diff --git a/src/Grpc.Auth/Grpc.Auth.csproj b/src/Grpc.Auth/Grpc.Auth.csproj index 8edcfcf4f..af058881c 100755 --- a/src/Grpc.Auth/Grpc.Auth.csproj +++ b/src/Grpc.Auth/Grpc.Auth.csproj @@ -5,7 +5,7 @@ true true - net462;netstandard1.5;netstandard2.0 + net462;netstandard2.0 README.md diff --git a/src/Grpc.Core.Api/AsyncClientStreamingCall.cs b/src/Grpc.Core.Api/AsyncClientStreamingCall.cs index 86dc07855..789ed9c3d 100644 --- a/src/Grpc.Core.Api/AsyncClientStreamingCall.cs +++ b/src/Grpc.Core.Api/AsyncClientStreamingCall.cs @@ -180,12 +180,13 @@ public AsyncClientStreamingCallDebugView(AsyncClientStreamingCall _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null; public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null; public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState); public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.GetAwaiter().GetResult() : null; public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState); public IClientStreamWriter RequestStream => _call.RequestStream; public TResponse? Response => _call.ResponseAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseAsync.Result : default; + public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null; + public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.ChannelKey); } } diff --git a/src/Grpc.Core.Api/AsyncDuplexStreamingCall.cs b/src/Grpc.Core.Api/AsyncDuplexStreamingCall.cs index 721240f26..a9b275d3e 100644 --- a/src/Grpc.Core.Api/AsyncDuplexStreamingCall.cs +++ b/src/Grpc.Core.Api/AsyncDuplexStreamingCall.cs @@ -157,12 +157,13 @@ public AsyncDuplexStreamingCallDebugView(AsyncDuplexStreamingCall _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null; public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null; public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState); public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null; public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState); public IAsyncStreamReader ResponseStream => _call.ResponseStream; public IClientStreamWriter RequestStream => _call.RequestStream; + public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null; + public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.ChannelKey); } } diff --git a/src/Grpc.Core.Api/AsyncServerStreamingCall.cs b/src/Grpc.Core.Api/AsyncServerStreamingCall.cs index 59be107d0..efdf2b887 100644 --- a/src/Grpc.Core.Api/AsyncServerStreamingCall.cs +++ b/src/Grpc.Core.Api/AsyncServerStreamingCall.cs @@ -138,11 +138,13 @@ public AsyncServerStreamingCallDebugView(AsyncServerStreamingCall cal _call = call; } - public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null; public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null; public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState); public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null; public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState); public IAsyncStreamReader ResponseStream => _call.ResponseStream; + public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null; + public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.ChannelKey); + public object? Request => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.RequestKey); } } diff --git a/src/Grpc.Core.Api/AsyncUnaryCall.cs b/src/Grpc.Core.Api/AsyncUnaryCall.cs index 8367be92c..e3292b33a 100644 --- a/src/Grpc.Core.Api/AsyncUnaryCall.cs +++ b/src/Grpc.Core.Api/AsyncUnaryCall.cs @@ -161,11 +161,13 @@ public AsyncUnaryCallDebugView(AsyncUnaryCall call) _call = call; } - public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null; public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null; public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState); public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null; public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState); public TResponse? Response => _call.ResponseAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseAsync.Result : default; + public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null; + public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.ChannelKey); + public object? Request => CallDebuggerHelpers.GetDebugValue(_call.callState, CallDebuggerHelpers.RequestKey); } } diff --git a/src/Grpc.Core.Api/Grpc.Core.Api.csproj b/src/Grpc.Core.Api/Grpc.Core.Api.csproj index 6cf865ff7..bb6756d1e 100755 --- a/src/Grpc.Core.Api/Grpc.Core.Api.csproj +++ b/src/Grpc.Core.Api/Grpc.Core.Api.csproj @@ -5,7 +5,7 @@ true true - net462;netstandard1.5;netstandard2.0;netstandard2.1 + net462;netstandard2.0;netstandard2.1 README.md true diff --git a/src/Grpc.Core.Api/Internal/CallDebuggerHelpers.cs b/src/Grpc.Core.Api/Internal/CallDebuggerHelpers.cs index 0c2f7d002..a7e5a0d30 100644 --- a/src/Grpc.Core.Api/Internal/CallDebuggerHelpers.cs +++ b/src/Grpc.Core.Api/Internal/CallDebuggerHelpers.cs @@ -17,18 +17,28 @@ #endregion using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; namespace Grpc.Core.Internal; internal static class CallDebuggerHelpers { + public const string MethodKey = "Method"; + public const string ChannelKey = "Channel"; + public const string RequestKey = "Request"; + public static string DebuggerToString(AsyncCallState callState) { string debugText = string.Empty; - if (callState.State is IMethod method) + if (GetDebugValue(callState, ChannelKey) is { } channel) + { + debugText = $"Channel = {channel.Target}, "; + } + if (GetDebugValue(callState, MethodKey) is { } method) { - debugText = $"Method = {method.FullName}, "; + debugText += $"Method = {method.FullName}, "; } var status = GetStatus(callState); @@ -40,6 +50,30 @@ public static string DebuggerToString(AsyncCallState callState) return debugText; } + public static T? GetDebugValue(AsyncCallState callState, string key) where T : class + { + // We want to get information about a call to display during debugging, but Grpc.Core.Api does + // doesn't have access to the implementation's internal fields. + // GetDebugValue accesses values by IEnumerable + key from the implementation state. + if (callState.State is IEnumerable> enumerable) + { + foreach (var entry in enumerable) + { + if (entry.Key == key) + { + if (entry.Value is T t) + { + return t; + } + + return null; + } + } + } + + return null; + } + public static Status? GetStatus(AsyncCallState callState) { // This is the only public API to get this value and there is no way to check if it's available. diff --git a/src/Grpc.Core.Api/Internal/ClientDebuggerHelpers.cs b/src/Grpc.Core.Api/Internal/ClientDebuggerHelpers.cs index f309fd8ce..3ca7b0dba 100644 --- a/src/Grpc.Core.Api/Internal/ClientDebuggerHelpers.cs +++ b/src/Grpc.Core.Api/Internal/ClientDebuggerHelpers.cs @@ -25,11 +25,7 @@ namespace Grpc.Core.Internal; internal static class ClientDebuggerHelpers { -#if NETSTANDARD1_5 - private static TypeInfo? GetParentType(Type clientType) -#else private static Type? GetParentType(Type clientType) -#endif { // Attempt to get the parent type for a generated client. // A generated client is always nested inside a static type that contains information about the client. @@ -48,11 +44,7 @@ internal static class ClientDebuggerHelpers return null; } -#if NETSTANDARD1_5 - var parentType = clientType.DeclaringType.GetTypeInfo(); -#else var parentType = clientType.DeclaringType; -#endif // Check parent type is static. A C# static type is sealed and abstract. if (parentType == null || (!parentType.IsSealed && !parentType.IsAbstract)) { @@ -101,10 +93,6 @@ internal static class ClientDebuggerHelpers return methods; static bool IsMethodField(FieldInfo field) => -#if NETSTANDARD1_5 - typeof(IMethod).GetTypeInfo().IsAssignableFrom(field.FieldType); -#else typeof(IMethod).IsAssignableFrom(field.FieldType); -#endif } } diff --git a/src/Grpc.Core.Api/ServiceBinderBase.cs b/src/Grpc.Core.Api/ServiceBinderBase.cs index 39decdb89..cd24b2096 100644 --- a/src/Grpc.Core.Api/ServiceBinderBase.cs +++ b/src/Grpc.Core.Api/ServiceBinderBase.cs @@ -40,7 +40,7 @@ public class ServiceBinderBase /// The method handler. public virtual void AddMethod( Method method, - UnaryServerMethod handler) + UnaryServerMethod? handler) where TRequest : class where TResponse : class { @@ -56,7 +56,7 @@ public virtual void AddMethod( /// The method handler. public virtual void AddMethod( Method method, - ClientStreamingServerMethod handler) + ClientStreamingServerMethod? handler) where TRequest : class where TResponse : class { @@ -72,7 +72,7 @@ public virtual void AddMethod( /// The method handler. public virtual void AddMethod( Method method, - ServerStreamingServerMethod handler) + ServerStreamingServerMethod? handler) where TRequest : class where TResponse : class { @@ -88,7 +88,7 @@ public virtual void AddMethod( /// The method handler. public virtual void AddMethod( Method method, - DuplexStreamingServerMethod handler) + DuplexStreamingServerMethod? handler) where TRequest : class where TResponse : class { diff --git a/src/Grpc.Core.Api/Status.cs b/src/Grpc.Core.Api/Status.cs index 01055cd9e..45d795562 100644 --- a/src/Grpc.Core.Api/Status.cs +++ b/src/Grpc.Core.Api/Status.cs @@ -15,6 +15,7 @@ #endregion using System; +using System.Diagnostics; namespace Grpc.Core; @@ -22,6 +23,7 @@ namespace Grpc.Core; /// /// Represents RPC result, which consists of and an optional detail string. /// +[DebuggerDisplay("{DebuggerToString(),nq}")] public struct Status { /// @@ -93,4 +95,19 @@ public override string ToString() } return $"Status(StatusCode=\"{StatusCode}\", Detail=\"{Detail}\")"; } + + private string DebuggerToString() + { + var text = $"StatusCode = {StatusCode}"; + if (!string.IsNullOrEmpty(Detail)) + { + text += $@", Detail = ""{Detail}"""; + } + if (DebugException != null) + { + text += $@", DebugException = ""{DebugException.GetType()}: {DebugException.Message}"""; + } + + return text; + } } diff --git a/src/Grpc.Core.Api/VersionInfo.cs b/src/Grpc.Core.Api/VersionInfo.cs index 556fa630a..630413929 100644 --- a/src/Grpc.Core.Api/VersionInfo.cs +++ b/src/Grpc.Core.Api/VersionInfo.cs @@ -36,10 +36,10 @@ public static class VersionInfo /// /// Current AssemblyFileVersion of gRPC C# assemblies /// - public const string CurrentAssemblyFileVersion = "2.57.0.0"; + public const string CurrentAssemblyFileVersion = "2.58.0.0"; /// /// Current version of gRPC C# /// - public const string CurrentVersion = "2.57.0-dev"; + public const string CurrentVersion = "2.58.0-pre1"; } diff --git a/src/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/Grpc.HealthCheck/Grpc.HealthCheck.csproj index c6a40f099..d3e02fc34 100755 --- a/src/Grpc.HealthCheck/Grpc.HealthCheck.csproj +++ b/src/Grpc.HealthCheck/Grpc.HealthCheck.csproj @@ -5,7 +5,7 @@ true true - net462;netstandard1.5;netstandard2.0 + net462;netstandard2.0 README.md diff --git a/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs b/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs index b602843f2..85669a691 100644 --- a/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs +++ b/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs @@ -190,6 +190,9 @@ public async ValueTask TryConnectAsync(ConnectContext context) } catch (Exception ex) { + // Socket is recreated every connect attempt. Explicitly dispose failed socket before next attempt. + socket.Dispose(); + SocketConnectivitySubchannelTransportLog.ErrorConnectingSocket(_logger, _subchannel.Id, currentEndPoint, ex); if (firstConnectionError == null) diff --git a/src/Grpc.Net.Client/GrpcChannel.cs b/src/Grpc.Net.Client/GrpcChannel.cs index 71adcda91..47a782ad0 100644 --- a/src/Grpc.Net.Client/GrpcChannel.cs +++ b/src/Grpc.Net.Client/GrpcChannel.cs @@ -266,17 +266,9 @@ private static HttpHandlerContext CalculateHandlerContext(ILogger logger, Uri ad } if (HttpRequestHelpers.HasHttpHandlerType(channelOptions.HttpHandler, "System.Net.Http.SocketsHttpHandler")) { - HttpHandlerType type; - TimeSpan? connectTimeout; - TimeSpan? connectionIdleTimeout; - #if NET5_0_OR_GREATER var socketsHttpHandler = HttpRequestHelpers.GetHttpHandlerType(channelOptions.HttpHandler)!; - type = HttpHandlerType.SocketsHttpHandler; - connectTimeout = socketsHttpHandler.ConnectTimeout; - connectionIdleTimeout = GetConnectionIdleTimeout(socketsHttpHandler); - // Check if the SocketsHttpHandler is being shared by channels. // It has already been setup by another channel (i.e. ConnectCallback is set) then // additional channels can use advanced connectivity features. @@ -286,33 +278,34 @@ private static HttpHandlerContext CalculateHandlerContext(ILogger logger, Uri ad // This channel can't support advanced connectivity features. if (socketsHttpHandler.ConnectCallback != null) { - type = HttpHandlerType.Custom; - connectTimeout = null; - connectionIdleTimeout = null; + return new HttpHandlerContext(HttpHandlerType.Custom); } } + // Load balancing has been disabled on the SocketsHttpHandler. + if (socketsHttpHandler.Properties.TryGetValue("__GrpcLoadBalancingDisabled", out var value) + && value is bool loadBalancingDisabled && loadBalancingDisabled) + { + return new HttpHandlerContext(HttpHandlerType.Custom); + } + // If a proxy is specified then requests could be sent via an SSL tunnel. // A CONNECT request is made to the proxy to establish the transport stream and then // gRPC calls are sent via stream. This feature isn't supported by load balancer. // Proxy can be specified via: // - SocketsHttpHandler.Proxy. Set via app code. // - HttpClient.DefaultProxy. Set via environment variables, e.g. HTTPS_PROXY. - if (type == HttpHandlerType.SocketsHttpHandler) + if (IsProxied(socketsHttpHandler, address, isSecure)) { - if (IsProxied(socketsHttpHandler, address, isSecure)) - { - logger.LogInformation("Proxy configuration is detected. How the gRPC client creates connections can cause unexpected behavior when a proxy is configured. " + - "To ensure the client correctly uses a proxy, configure GrpcChannelOptions.HttpHandler to use HttpClientHandler. " + - "Note that HttpClientHandler isn't compatible with load balancing."); - } + logger.LogInformation("Proxy configuration is detected. How the gRPC client creates connections can cause unexpected behavior when a proxy is configured. " + + "To ensure the client correctly uses a proxy, configure GrpcChannelOptions.HttpHandler to use HttpClientHandler. " + + "Note that HttpClientHandler isn't compatible with load balancing."); } + + return new HttpHandlerContext(HttpHandlerType.SocketsHttpHandler, socketsHttpHandler.ConnectTimeout, GetConnectionIdleTimeout(socketsHttpHandler)); #else - type = HttpHandlerType.SocketsHttpHandler; - connectTimeout = null; - connectionIdleTimeout = null; + return new HttpHandlerContext(HttpHandlerType.SocketsHttpHandler); #endif - return new HttpHandlerContext(type, connectTimeout, connectionIdleTimeout); } if (HttpRequestHelpers.GetHttpHandlerType(channelOptions.HttpHandler) != null) { diff --git a/src/Grpc.Net.Client/Internal/GrpcCall.cs b/src/Grpc.Net.Client/Internal/GrpcCall.cs index 54f08c45d..15aa25325 100644 --- a/src/Grpc.Net.Client/Internal/GrpcCall.cs +++ b/src/Grpc.Net.Client/Internal/GrpcCall.cs @@ -16,6 +16,7 @@ #endregion +using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -53,6 +54,7 @@ internal sealed partial class GrpcCall : GrpcCall, IGrpcCal // These are set depending on the type of gRPC call private TaskCompletionSource? _responseTcs; + private TRequest? _request; public int MessagesWritten { get; private set; } public int MessagesRead { get; private set; } @@ -99,12 +101,11 @@ private void ValidateDeadline(DateTime? deadline) public object? CallWrapper { get; set; } - MethodType IMethod.Type => Method.Type; - string IMethod.ServiceName => Method.ServiceName; - string IMethod.Name => Method.Name; - string IMethod.FullName => Method.FullName; - - public void StartUnary(TRequest request) => StartUnaryCore(CreatePushUnaryContent(request)); + public void StartUnary(TRequest request) + { + _request = request; + StartUnaryCore(CreatePushUnaryContent(request)); + } public void StartClientStreaming() { @@ -1161,4 +1162,7 @@ private static void WriteDiagnosticEvent< { diagnosticSource.Write(name, value); } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(Channel, Method, _request); } diff --git a/src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs b/src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs index b323f80c8..e2d5f4390 100644 --- a/src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs +++ b/src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs @@ -16,6 +16,7 @@ #endregion +using System.Collections.Generic; using System.Net.Http.Headers; using Grpc.Core; using Grpc.Net.Compression; @@ -83,6 +84,24 @@ internal static string GetMessageAcceptEncoding(Dictionary + /// Gets key value pairs used by debugging. These are provided as an enumerator instead of a dictionary + /// because it's one method to implement an enumerator on gRPC calls compared to a dozen members for a dictionary. + /// + public static IEnumerator> GetDebugEnumerator(ChannelBase channel, IMethod method, object? request) + { + const string MethodKey = "Method"; + const string ChannelKey = "Channel"; + const string RequestKey = "Request"; + + yield return new KeyValuePair(ChannelKey, channel); + yield return new KeyValuePair(MethodKey, method); + if (request != null) + { + yield return new KeyValuePair(RequestKey, request); + } + } + static GrpcProtocolConstants() { UserAgentHeader = "User-Agent"; diff --git a/src/Grpc.Net.Client/Internal/IGrpcCall.cs b/src/Grpc.Net.Client/Internal/IGrpcCall.cs index 8272adb1e..07f26f8e0 100644 --- a/src/Grpc.Net.Client/Internal/IGrpcCall.cs +++ b/src/Grpc.Net.Client/Internal/IGrpcCall.cs @@ -21,7 +21,7 @@ namespace Grpc.Net.Client.Internal; -internal interface IGrpcCall : IDisposable, IMethod +internal interface IGrpcCall : IDisposable, IEnumerable> where TRequest : class where TResponse : class { diff --git a/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs b/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs index bab9fa974..e481dfda5 100644 --- a/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs +++ b/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs @@ -16,6 +16,7 @@ #endregion +using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Grpc.Core; @@ -35,6 +36,7 @@ internal abstract partial class RetryCallBase : IGrpcCall? _retryBaseClientStreamWriter; private Task? _responseTask; private Task? _responseHeadersTask; + private TRequest? _request; // Internal for unit testing. internal CancellationTokenRegistration? _ctsRegistration; @@ -64,11 +66,6 @@ internal abstract partial class RetryCallBase : IGrpcCall Method.Type; - string IMethod.ServiceName => Method.ServiceName; - string IMethod.Name => Method.Name; - string IMethod.FullName => Method.FullName; - protected RetryCallBase(GrpcChannel channel, Method method, CallOptions options, string loggerName, int retryAttempts) { Logger = channel.LoggerFactory.CreateLogger(loggerName); @@ -170,6 +167,7 @@ public Metadata GetTrailers() public void StartUnary(TRequest request) { + _request = request; StartCore(call => call.StartUnaryCore(CreatePushUnaryContent(request, call))); } @@ -520,7 +518,7 @@ internal void ClearRetryBuffer() protected StatusGrpcCall CreateStatusCall(Status status) { - var call = new StatusGrpcCall(status, Channel, Method, MessagesRead); + var call = new StatusGrpcCall(status, Channel, Method, MessagesRead, _request); call.CallWrapper = CallWrapper; return call; } @@ -595,4 +593,7 @@ public Exception CreateFailureStatusException(Status status) { throw new NotSupportedException(); } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(Channel, Method, _request); } diff --git a/src/Grpc.Net.Client/Internal/Retry/StatusGrpcCall.cs b/src/Grpc.Net.Client/Internal/Retry/StatusGrpcCall.cs index c1826a010..0b9426aaf 100644 --- a/src/Grpc.Net.Client/Internal/Retry/StatusGrpcCall.cs +++ b/src/Grpc.Net.Client/Internal/Retry/StatusGrpcCall.cs @@ -16,6 +16,7 @@ #endregion +using System.Collections; using System.Diagnostics.CodeAnalysis; using Grpc.Core; @@ -28,6 +29,7 @@ internal sealed class StatusGrpcCall : IGrpcCall _method; + private readonly TRequest? _request; private IClientStreamWriter? _clientStreamWriter; private IAsyncStreamReader? _clientStreamReader; @@ -39,17 +41,13 @@ internal sealed class StatusGrpcCall : IGrpcCall _method.Type; - string IMethod.ServiceName => _method.ServiceName; - string IMethod.Name => _method.Name; - string IMethod.FullName => _method.FullName; - - public StatusGrpcCall(Status status, GrpcChannel channel, Method method, int messagesRead) + public StatusGrpcCall(Status status, GrpcChannel channel, Method method, int messagesRead, TRequest? request) { _status = status; _channel = channel; _method = method; MessagesRead = messagesRead; + _request = request; } public void Dispose() @@ -124,6 +122,9 @@ public Exception CreateFailureStatusException(Status status) } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(_channel, _method, _request); + private sealed class StatusClientStreamWriter : IClientStreamWriter { private readonly Status _status; diff --git a/src/Grpc.Reflection/Grpc.Reflection.csproj b/src/Grpc.Reflection/Grpc.Reflection.csproj index 0a52e1642..63b23d665 100755 --- a/src/Grpc.Reflection/Grpc.Reflection.csproj +++ b/src/Grpc.Reflection/Grpc.Reflection.csproj @@ -5,7 +5,7 @@ true true - net462;netstandard1.5;netstandard2.0 + net462;netstandard2.0 README.md diff --git a/src/Shared/HttpRequestHelpers.cs b/src/Shared/HttpRequestHelpers.cs index 2f75e9c68..a08accd5e 100644 --- a/src/Shared/HttpRequestHelpers.cs +++ b/src/Shared/HttpRequestHelpers.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -53,7 +53,7 @@ public static bool HasHttpHandlerType(HttpMessageHandler handler, string handler public static HttpMessageHandler? GetHttpHandlerType(HttpMessageHandler handler, string handlerTypeName) { - if (handler?.GetType().FullName == handlerTypeName) + if (IsType(handler.GetType(), handlerTypeName)) { return handler; } @@ -62,8 +62,7 @@ public static bool HasHttpHandlerType(HttpMessageHandler handler, string handler while (currentHandler is DelegatingHandler delegatingHandler) { currentHandler = delegatingHandler.InnerHandler; - - if (currentHandler?.GetType().FullName == handlerTypeName) + if (currentHandler != null && IsType(currentHandler.GetType(), handlerTypeName)) { return currentHandler; } @@ -72,6 +71,21 @@ public static bool HasHttpHandlerType(HttpMessageHandler handler, string handler return null; } + private static bool IsType(Type type, string handlerTypeName) + { + Type? currentType = type; + do + { + if (currentType.FullName == handlerTypeName) + { + return true; + } + + } while ((currentType = currentType.BaseType) != null); + + return false; + } + public static bool HasHttpHandlerType(HttpMessageHandler handler) where T : HttpMessageHandler { return GetHttpHandlerType(handler) != null; diff --git a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs index c2c3f2aa3..15a9abb12 100644 --- a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs +++ b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs @@ -649,8 +649,11 @@ public void WinHttpHandler_UnsupportedWindows_Throw() "For more information, see https://aka.ms/aspnet/grpc/netframework."); } - [Test] - public void WinHttpHandler_SupportedWindows_Success() +#pragma warning disable CS0436 // Just need to have a type called WinHttpHandler to activate new behavior. + [TestCase(typeof(WinHttpHandler))] +#pragma warning restore CS0436 + [TestCase(typeof(WinHttpHandlerInherited))] + public void WinHttpHandler_SupportedWindows_Success(Type handlerType) { // Arrange var services = new ServiceCollection(); @@ -660,9 +663,7 @@ public void WinHttpHandler_SupportedWindows_Success() OSVersion = Version.Parse("10.0.20348.169") }); -#pragma warning disable CS0436 // Just need to have a type called WinHttpHandler to activate new behavior. - var winHttpHandler = new WinHttpHandler(new TestHttpMessageHandler()); -#pragma warning restore CS0436 + var winHttpHandler = (HttpMessageHandler)Activator.CreateInstance(handlerType, new TestHttpMessageHandler())!; // Act var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions @@ -675,6 +676,15 @@ public void WinHttpHandler_SupportedWindows_Success() Assert.AreEqual(HttpHandlerType.WinHttpHandler, channel.HttpHandlerType); } +#pragma warning disable CS0436 // Just need to have a type called WinHttpHandler to activate new behavior. + private class WinHttpHandlerInherited : WinHttpHandler + { + public WinHttpHandlerInherited(HttpMessageHandler innerHandler) : base(innerHandler) + { + } + } +#pragma warning restore CS0436 + #if SUPPORT_LOAD_BALANCING [Test] public void Resolver_SocketHttpHandlerWithConnectCallback_Error()