Skip to content

Commit

Permalink
Merge branch 'grpc:master' into grpc.statuspro
Browse files Browse the repository at this point in the history
  • Loading branch information
tonydnewell authored Nov 9, 2023
2 parents 9b94477 + e52049a commit 956ad47
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 242 deletions.
2 changes: 1 addition & 1 deletion build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<GoogleProtobufPackageVersion>3.23.1</GoogleProtobufPackageVersion>
<GrpcDotNetPackageVersion>2.55.0</GrpcDotNetPackageVersion> <!-- Used by example projects -->
<GrpcPackageVersion>2.46.6</GrpcPackageVersion>
<GrpcToolsPackageVersion>2.57.0</GrpcToolsPackageVersion>
<GrpcToolsPackageVersion>2.59.0</GrpcToolsPackageVersion>
<MicrosoftAspNetCoreAppPackageVersion>8.0.0-rc.1.23378.7</MicrosoftAspNetCoreAppPackageVersion>
<MicrosoftAspNetCoreApp7PackageVersion>7.0.5</MicrosoftAspNetCoreApp7PackageVersion>
<MicrosoftAspNetCoreApp6PackageVersion>6.0.11</MicrosoftAspNetCoreApp6PackageVersion>
Expand Down
393 changes: 204 additions & 189 deletions examples/Spar/Server/ClientApp/package-lock.json

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion src/Grpc.AspNetCore.Server/Internal/GrpcProtocolHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Copyright notice and license
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
Expand Down Expand Up @@ -234,4 +234,17 @@ internal static bool ShouldSkipHeader(string name)
{
return name.StartsWith(':') || GrpcProtocolConstants.FilteredHeaders.Contains(name);
}

internal static IHttpRequestLifetimeFeature GetRequestLifetimeFeature(HttpContext httpContext)
{
var lifetimeFeature = httpContext.Features.Get<IHttpRequestLifetimeFeature>();
if (lifetimeFeature is null)
{
// This should only run in tests where the HttpContext is manually created.
lifetimeFeature = new HttpRequestLifetimeFeature();
httpContext.Features.Set(lifetimeFeature);
}

return lifetimeFeature;
}
}
14 changes: 12 additions & 2 deletions src/Grpc.AspNetCore.Server/Internal/HttpContextStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#endregion

using System.Diagnostics;
using System.IO.Pipelines;
using Grpc.Core;
using Grpc.Shared;
using Microsoft.AspNetCore.Http.Features;

namespace Grpc.AspNetCore.Server.Internal;

Expand All @@ -28,6 +30,8 @@ internal class HttpContextStreamReader<TRequest> : IAsyncStreamReader<TRequest>
{
private readonly HttpContextServerCallContext _serverCallContext;
private readonly Func<DeserializationContext, TRequest> _deserializer;
private readonly PipeReader _bodyReader;
private readonly IHttpRequestLifetimeFeature _requestLifetimeFeature;
private bool _completed;
private long _readCount;
private bool _endOfStream;
Expand All @@ -36,6 +40,12 @@ public HttpContextStreamReader(HttpContextServerCallContext serverCallContext, F
{
_serverCallContext = serverCallContext;
_deserializer = deserializer;

// Copy HttpContext values.
// This is done to avoid a race condition when reading them from HttpContext later when running in a separate thread.
_bodyReader = _serverCallContext.HttpContext.Request.BodyReader;
// Copy lifetime feature because HttpContext.RequestAborted on .NET 6 doesn't return the real cancellation token.
_requestLifetimeFeature = GrpcProtocolHelpers.GetRequestLifetimeFeature(_serverCallContext.HttpContext);
}

public TRequest Current { get; private set; } = default!;
Expand All @@ -54,7 +64,7 @@ async Task<bool> MoveNextAsync(ValueTask<TRequest?> readStreamTask)
return Task.FromCanceled<bool>(cancellationToken);
}

if (_completed || _serverCallContext.CancellationToken.IsCancellationRequested)
if (_completed || _requestLifetimeFeature.RequestAborted.IsCancellationRequested)
{
return Task.FromException<bool>(new InvalidOperationException("Can't read messages after the request is complete."));
}
Expand All @@ -63,7 +73,7 @@ async Task<bool> MoveNextAsync(ValueTask<TRequest?> readStreamTask)
// In a long running stream this can allow the previous value to be GCed.
Current = null!;

var request = _serverCallContext.HttpContext.Request.BodyReader.ReadStreamMessageAsync(_serverCallContext, _deserializer, cancellationToken);
var request = _bodyReader.ReadStreamMessageAsync(_serverCallContext, _deserializer, cancellationToken);
if (!request.IsCompletedSuccessfully)
{
return MoveNextAsync(request);
Expand Down
14 changes: 12 additions & 2 deletions src/Grpc.AspNetCore.Server/Internal/HttpContextStreamWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#endregion

using System.Diagnostics;
using System.IO.Pipelines;
using Grpc.Core;
using Grpc.Shared;
using Microsoft.AspNetCore.Http.Features;

namespace Grpc.AspNetCore.Server.Internal;

Expand All @@ -29,6 +31,8 @@ internal class HttpContextStreamWriter<TResponse> : IServerStreamWriter<TRespons
{
private readonly HttpContextServerCallContext _context;
private readonly Action<TResponse, SerializationContext> _serializer;
private readonly PipeWriter _bodyWriter;
private readonly IHttpRequestLifetimeFeature _requestLifetimeFeature;
private readonly object _writeLock;
private Task? _writeTask;
private bool _completed;
Expand All @@ -39,6 +43,12 @@ public HttpContextStreamWriter(HttpContextServerCallContext context, Action<TRes
_context = context;
_serializer = serializer;
_writeLock = new object();

// Copy HttpContext values.
// This is done to avoid a race condition when reading them from HttpContext later when running in a separate thread.
_bodyWriter = context.HttpContext.Response.BodyWriter;
// Copy lifetime feature because HttpContext.RequestAborted on .NET 6 doesn't return the real cancellation token.
_requestLifetimeFeature = GrpcProtocolHelpers.GetRequestLifetimeFeature(context.HttpContext);
}

public WriteOptions? WriteOptions
Expand Down Expand Up @@ -77,7 +87,7 @@ private async Task WriteCoreAsync(TResponse message, CancellationToken cancellat
{
cancellationToken.ThrowIfCancellationRequested();

if (_completed || _context.CancellationToken.IsCancellationRequested)
if (_completed || _requestLifetimeFeature.RequestAborted.IsCancellationRequested)
{
throw new InvalidOperationException("Can't write the message because the request is complete.");
}
Expand All @@ -91,7 +101,7 @@ private async Task WriteCoreAsync(TResponse message, CancellationToken cancellat
}

// Save write task to track whether it is complete. Must be set inside lock.
_writeTask = _context.HttpContext.Response.BodyWriter.WriteStreamedMessageAsync(message, _context, _serializer, cancellationToken);
_writeTask = _bodyWriter.WriteStreamedMessageAsync(message, _context, _serializer, cancellationToken);
}

await _writeTask;
Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.Auth/Grpc.Auth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<IsGrpcPublishedPackage>true</IsGrpcPublishedPackage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TargetFrameworks>net462;netstandard1.5;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.Core.Api/Grpc.Core.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<IsGrpcPublishedPackage>true</IsGrpcPublishedPackage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TargetFrameworks>net462;netstandard1.5;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageReadmeFile>README.md</PackageReadmeFile>

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
12 changes: 0 additions & 12 deletions src/Grpc.Core.Api/Internal/ClientDebuggerHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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))
{
Expand Down Expand Up @@ -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
}
}
17 changes: 17 additions & 0 deletions src/Grpc.Core.Api/Status.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
#endregion

using System;
using System.Diagnostics;

namespace Grpc.Core;

// TODO(jtattermusch): make the Status struct readonly
/// <summary>
/// Represents RPC result, which consists of <see cref="StatusCode"/> and an optional detail string.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
public struct Status
{
/// <summary>
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion src/Grpc.HealthCheck/Grpc.HealthCheck.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<IsGrpcPublishedPackage>true</IsGrpcPublishedPackage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TargetFrameworks>net462;netstandard1.5;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ public async ValueTask<ConnectResult> 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)
Expand Down
37 changes: 15 additions & 22 deletions src/Grpc.Net.Client/GrpcChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SocketsHttpHandler>(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.
Expand All @@ -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<HttpClientHandler>(channelOptions.HttpHandler) != null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.Reflection/Grpc.Reflection.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<IsGrpcPublishedPackage>true</IsGrpcPublishedPackage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TargetFrameworks>net462;netstandard1.5;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

Expand Down
22 changes: 18 additions & 4 deletions src/Shared/HttpRequestHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Copyright notice and license
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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<T>(HttpMessageHandler handler) where T : HttpMessageHandler
{
return GetHttpHandlerType<T>(handler) != null;
Expand Down
Loading

0 comments on commit 956ad47

Please sign in to comment.