From 1422de08f0d499f4c44c702c70baad22b278725c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 11 Aug 2023 09:31:02 +0800 Subject: [PATCH 1/3] [2.56.x] Support infinite idle connection timeout values (#2234) --- .../SocketConnectivitySubchannelTransport.cs | 2 +- src/Grpc.Net.Client/GrpcChannel.cs | 23 +++++++++- .../Balancer/ConnectionTests.cs | 45 ++++++++++++++++--- test/FunctionalTests/FunctionalTestBase.cs | 8 ++-- .../Grpc.Net.Client.Tests/GrpcChannelTests.cs | 32 ++++++++++--- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs b/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs index 0528f0306..4a28c8b50 100644 --- a/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs +++ b/src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs @@ -332,7 +332,7 @@ public async ValueTask GetStreamAsync(BalancerAddress address, Cancellat var closeSocket = false; - if (DateTime.UtcNow > socketCreatedTime.Value.Add(_socketIdleTimeout)) + if (_socketIdleTimeout != Timeout.InfiniteTimeSpan && DateTime.UtcNow > socketCreatedTime.Value.Add(_socketIdleTimeout)) { SocketConnectivitySubchannelTransportLog.ClosingSocketFromIdleTimeoutOnCreateStream(_logger, _subchannel.Id, address, _socketIdleTimeout); closeSocket = true; diff --git a/src/Grpc.Net.Client/GrpcChannel.cs b/src/Grpc.Net.Client/GrpcChannel.cs index 84fa0ea69..159f6de6e 100644 --- a/src/Grpc.Net.Client/GrpcChannel.cs +++ b/src/Grpc.Net.Client/GrpcChannel.cs @@ -255,7 +255,7 @@ private static HttpHandlerContext CalculateHandlerContext(ILogger logger, Uri ad type = HttpHandlerType.SocketsHttpHandler; connectTimeout = socketsHttpHandler.ConnectTimeout; - connectionIdleTimeout = socketsHttpHandler.PooledConnectionIdleTimeout; + 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 @@ -300,6 +300,27 @@ private static HttpHandlerContext CalculateHandlerContext(ILogger logger, Uri ad } return new HttpHandlerContext(HttpHandlerType.Custom); + +#if NET5_0_OR_GREATER + static TimeSpan? GetConnectionIdleTimeout(SocketsHttpHandler socketsHttpHandler) + { + // Check if either TimeSpan is InfiniteTimeSpan, and return the other one. + if (socketsHttpHandler.PooledConnectionIdleTimeout == Timeout.InfiniteTimeSpan) + { + return socketsHttpHandler.PooledConnectionLifetime; + } + + if (socketsHttpHandler.PooledConnectionLifetime == Timeout.InfiniteTimeSpan) + { + return socketsHttpHandler.PooledConnectionIdleTimeout; + } + + // Return the bigger TimeSpan. + return socketsHttpHandler.PooledConnectionIdleTimeout > socketsHttpHandler.PooledConnectionLifetime + ? socketsHttpHandler.PooledConnectionIdleTimeout + : socketsHttpHandler.PooledConnectionLifetime; + } +#endif } #if NET5_0_OR_GREATER diff --git a/test/FunctionalTests/Balancer/ConnectionTests.cs b/test/FunctionalTests/Balancer/ConnectionTests.cs index 22754901f..61d55c173 100644 --- a/test/FunctionalTests/Balancer/ConnectionTests.cs +++ b/test/FunctionalTests/Balancer/ConnectionTests.cs @@ -141,8 +141,9 @@ async Task UnaryMethod(HelloRequest request, ServerCallContext conte await ExceptionAssert.ThrowsAsync(() => connectTask).DefaultTimeout(); } - [Test] - public async Task Active_UnaryCall_ConnectionIdleTimeout_SocketRecreated() + [TestCase(0)] // TimeSpan.Zero + [TestCase(1000)] // 1 second + public async Task Active_UnaryCall_ConnectionIdleTimeout_SocketRecreated(int milliseconds) { // Ignore errors SetExpectedErrorsFilter(writeContext => @@ -158,7 +159,7 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Arrange using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); - var connectionIdleTimeout = TimeSpan.FromSeconds(1); + var connectionIdleTimeout = TimeSpan.FromMilliseconds(milliseconds); var channel = await BalancerHelpers.CreateChannel( LoggerFactory, new PickFirstConfig(), @@ -168,7 +169,8 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) Logger.LogInformation("Connecting channel."); await channel.ConnectAsync(); - await Task.Delay(connectionIdleTimeout); + // Wait for timeout plus a little extra to avoid issues from imprecise timers. + await Task.Delay(connectionIdleTimeout + TimeSpan.FromMilliseconds(50)); var client = TestClientFactory.Create(channel, endpoint.Method); var response = await client.UnaryCall(new HelloRequest { Name = "Test!" }).ResponseAsync.DefaultTimeout(); @@ -176,10 +178,43 @@ Task UnaryMethod(HelloRequest request, ServerCallContext context) // Assert Assert.AreEqual("Test!", response.Message); - AssertHasLog(LogLevel.Debug, "ClosingSocketFromIdleTimeoutOnCreateStream", "Subchannel id '1' socket 127.0.0.1:50051 is being closed because it exceeds the idle timeout of 00:00:01."); + AssertHasLog(LogLevel.Debug, "ClosingSocketFromIdleTimeoutOnCreateStream"); AssertHasLog(LogLevel.Trace, "ConnectingOnCreateStream", "Subchannel id '1' doesn't have a connected socket available. Connecting new stream socket for 127.0.0.1:50051."); } + public async Task Active_UnaryCall_InfiniteConnectionIdleTimeout_SocketNotClosed() + { + SetExpectedErrorsFilter(writeContext => + { + return true; + }); + + Task UnaryMethod(HelloRequest request, ServerCallContext context) + { + return Task.FromResult(new HelloReply { Message = request.Name }); + } + + // Arrange + using var endpoint = BalancerHelpers.CreateGrpcEndpoint(50051, UnaryMethod, nameof(UnaryMethod), loggerFactory: LoggerFactory); + + var channel = await BalancerHelpers.CreateChannel( + LoggerFactory, + new PickFirstConfig(), + new[] { endpoint.Address }, + connectionIdleTimeout: Timeout.InfiniteTimeSpan).DefaultTimeout(); + + Logger.LogInformation("Connecting channel."); + await channel.ConnectAsync(); + + var client = TestClientFactory.Create(channel, endpoint.Method); + var response = await client.UnaryCall(new HelloRequest { Name = "Test!" }).ResponseAsync.DefaultTimeout(); + + // Assert + Assert.AreEqual("Test!", response.Message); + + Assert.IsFalse(Logs.Any(l => l.EventId.Name == "ClosingSocketFromIdleTimeoutOnCreateStream"), "Shouldn't have a ClosingSocketFromIdleTimeoutOnCreateStream log."); + } + [Test] public async Task Active_UnaryCall_ServerCloseOnKeepAlive_SocketRecreatedOnRequest() { diff --git a/test/FunctionalTests/FunctionalTestBase.cs b/test/FunctionalTests/FunctionalTestBase.cs index 7136a6b17..e4fe0acc6 100644 --- a/test/FunctionalTests/FunctionalTestBase.cs +++ b/test/FunctionalTests/FunctionalTestBase.cs @@ -119,21 +119,21 @@ protected void AssertHasLogRpcConnectionError(StatusCode statusCode, string deta AssertHasLog(LogLevel.Information, "RpcConnectionError", $"Error status code '{statusCode}' with detail '{detail}' raised."); } - protected void AssertHasLog(LogLevel logLevel, string name, string message, Func? exceptionMatch = null) + protected void AssertHasLog(LogLevel logLevel, string name, string? message = null, Func? exceptionMatch = null) { if (HasLog(logLevel, name, message, exceptionMatch)) { return; } - Assert.Fail($"No match. Log level = {logLevel}, name = {name}, message = '{message}'."); + Assert.Fail($"No match. Log level = {logLevel}, name = {name}, message = '{message ?? "(null)"}'."); } - protected bool HasLog(LogLevel logLevel, string name, string message, Func? exceptionMatch = null) + protected bool HasLog(LogLevel logLevel, string name, string? message = null, Func? exceptionMatch = null) { return Logs.Any(r => { - var match = r.LogLevel == logLevel && r.EventId.Name == name && r.Message == message; + var match = r.LogLevel == logLevel && r.EventId.Name == name && (r.Message == message || message == null); if (exceptionMatch != null) { match = match && r.Exception != null && exceptionMatch(r.Exception); diff --git a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs index 2e2e376ef..47ea88743 100644 --- a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs +++ b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs @@ -222,17 +222,35 @@ public void Build_ConnectTimeout_ReadFromSocketsHttpHandler() Assert.AreEqual(TimeSpan.FromSeconds(1), channel.ConnectTimeout); } - [Test] - public void Build_ConnectionIdleTimeout_ReadFromSocketsHttpHandler() + [TestCase(-1, -1, -1)] + [TestCase(0, 0, 0)] + [TestCase(0, -1, 0)] + [TestCase(-1, 0, 0)] + [TestCase(1000, -1, 1000)] + [TestCase(-1, 1000, 1000)] + [TestCase(500, 1000, 1000)] + [TestCase(1000, 500, 1000)] + public void Build_ConnectionIdleTimeout_ReadFromSocketsHttpHandler( + int? pooledConnectionIdleTimeoutMs, + int? pooledConnectionLifetimeMs, + int expectedConnectionIdleTimeoutMs) { - // Arrange & Act - var channel = GrpcChannel.ForAddress("https://localhost", CreateGrpcChannelOptions(o => o.HttpHandler = new SocketsHttpHandler + // Arrange + var handler = new SocketsHttpHandler(); + if (pooledConnectionIdleTimeoutMs != null) { - PooledConnectionIdleTimeout = TimeSpan.FromSeconds(1) - })); + handler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(pooledConnectionIdleTimeoutMs.Value); + } + if (pooledConnectionLifetimeMs != null) + { + handler.PooledConnectionLifetime = TimeSpan.FromMilliseconds(pooledConnectionLifetimeMs.Value); + } + + // Act + var channel = GrpcChannel.ForAddress("https://localhost", CreateGrpcChannelOptions(o => o.HttpHandler = handler)); // Assert - Assert.AreEqual(TimeSpan.FromSeconds(1), channel.ConnectionIdleTimeout); + Assert.AreEqual(TimeSpan.FromMilliseconds(expectedConnectionIdleTimeoutMs), channel.ConnectionIdleTimeout); } #endif From 400bbb7aeadb8584bd0bbe6ba3b2d0590b1a8884 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 16 Aug 2023 17:26:42 +0800 Subject: [PATCH 2/3] [2.56.x] Validate Windows version when using WinHttpHandler (#2233) * Validate Windows version when using WinHttpHandler (#2229) * Improve comment in GrpcChannel for WinHttpHandler + OS validation (#2237) * Update OS version detection to get version directly from Windows (#2239) --- src/Grpc.Net.Client/GrpcChannel.cs | 20 ++++++ src/Grpc.Net.Client/Internal/NtDll.cs | 72 +++++++++++++++++++ .../Internal/OperatingSystem.cs | 20 +++++- test/Grpc.Net.Client.Tests/GetStatusTests.cs | 4 +- .../Grpc.Net.Client.Tests/GrpcChannelTests.cs | 60 ++++++++++++++++ .../OperatingSystemTests.cs | 43 +++++++++++ 6 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/Grpc.Net.Client/Internal/NtDll.cs create mode 100644 test/Grpc.Net.Client.Tests/OperatingSystemTests.cs diff --git a/src/Grpc.Net.Client/GrpcChannel.cs b/src/Grpc.Net.Client/GrpcChannel.cs index 159f6de6e..d5bbdb23c 100644 --- a/src/Grpc.Net.Client/GrpcChannel.cs +++ b/src/Grpc.Net.Client/GrpcChannel.cs @@ -184,6 +184,26 @@ internal GrpcChannel(Uri address, GrpcChannelOptions channelOptions) : base(addr { Log.AddressPathUnused(Logger, Address.OriginalString); } + + // Grpc.Net.Client + .NET Framework + WinHttpHandler requires features in WinHTTP, shipped in Windows, to work correctly. + // This scenario is supported in these versions of Windows or later: + // -Windows Server 2022 has partial support. + // -Unary and server streaming methods are supported. + // -Client and bidi streaming methods aren't supported. + // -Windows 11 has full support. + // + // GrpcChannel validates the Windows version is WinServer2022 or later. Win11 version number is greater than WinServer2022. + // Note that this doesn't block using unsupported client and bidi streaming methods on WinServer2022. + const int WinServer2022BuildVersion = 20348; + if (HttpHandlerType == HttpHandlerType.WinHttpHandler && + OperatingSystem.IsWindows && + OperatingSystem.OSVersion.Build < WinServer2022BuildVersion) + { + throw new InvalidOperationException("The channel configuration isn't valid on this operating system. " + + "The channel is configured to use WinHttpHandler and the current version of Windows " + + "doesn't support HTTP/2 features required by gRPC. Windows Server 2022 or Windows 11 or later is required. " + + "For more information, see https://aka.ms/aspnet/grpc/netframework."); + } } private void ResolveCredentials(GrpcChannelOptions channelOptions, out bool isSecure, out List? callCredentials) diff --git a/src/Grpc.Net.Client/Internal/NtDll.cs b/src/Grpc.Net.Client/Internal/NtDll.cs new file mode 100644 index 000000000..2abdbf5ad --- /dev/null +++ b/src/Grpc.Net.Client/Internal/NtDll.cs @@ -0,0 +1,72 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +#if !NET5_0_OR_GREATER + +using System.Runtime.InteropServices; + +namespace Grpc.Net.Client.Internal; + +/// +/// Types for calling RtlGetVersion. See https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html +/// +internal static class NtDll +{ + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo); + + internal static Version DetectWindowsVersion() + { + var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) }; + + if (RtlGetVersion(ref osVersionInfo) != NTSTATUS.STATUS_SUCCESS) + { + throw new InvalidOperationException($"Failed to call internal {nameof(RtlGetVersion)}."); + } + + return new Version(osVersionInfo.MajorVersion, osVersionInfo.MinorVersion, osVersionInfo.BuildNumber, 0); + } + + internal enum NTSTATUS : uint + { + /// + /// The operation completed successfully. + /// + STATUS_SUCCESS = 0x00000000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct OSVERSIONINFOEX + { + // The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX)) + public int OSVersionInfoSize; + public int MajorVersion; + public int MinorVersion; + public int BuildNumber; + public int PlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string CSDVersion; + public ushort ServicePackMajor; + public ushort ServicePackMinor; + public short SuiteMask; + public byte ProductType; + public byte Reserved; + } +} + +#endif diff --git a/src/Grpc.Net.Client/Internal/OperatingSystem.cs b/src/Grpc.Net.Client/Internal/OperatingSystem.cs index 836771090..4d2e45c73 100644 --- a/src/Grpc.Net.Client/Internal/OperatingSystem.cs +++ b/src/Grpc.Net.Client/Internal/OperatingSystem.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -24,6 +24,8 @@ internal interface IOperatingSystem { bool IsBrowser { get; } bool IsAndroid { get; } + bool IsWindows { get; } + Version OSVersion { get; } } internal sealed class OperatingSystem : IOperatingSystem @@ -32,14 +34,28 @@ internal sealed class OperatingSystem : IOperatingSystem public bool IsBrowser { get; } public bool IsAndroid { get; } + public bool IsWindows { get; } + public Version OSVersion { get; } private OperatingSystem() { - IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser")); #if NET5_0_OR_GREATER IsAndroid = System.OperatingSystem.IsAndroid(); + IsWindows = System.OperatingSystem.IsWindows(); + IsBrowser = System.OperatingSystem.IsBrowser(); + OSVersion = Environment.OSVersion.Version; #else IsAndroid = false; + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser")); + + // Older versions of .NET report an OSVersion.Version based on Windows compatibility settings. + // For example, if an app running on Windows 11 is configured to be "compatible" with Windows 10 + // then the version returned is always Windows 10. + // + // Get correct Windows version directly from Windows by calling RtlGetVersion. + // https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html + OSVersion = IsWindows ? NtDll.DetectWindowsVersion() : Environment.OSVersion.Version; #endif } } diff --git a/test/Grpc.Net.Client.Tests/GetStatusTests.cs b/test/Grpc.Net.Client.Tests/GetStatusTests.cs index 87e0bcb3f..e55a8d0c0 100644 --- a/test/Grpc.Net.Client.Tests/GetStatusTests.cs +++ b/test/Grpc.Net.Client.Tests/GetStatusTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -214,6 +214,8 @@ private class TestOperatingSystem : IOperatingSystem { public bool IsBrowser { get; set; } public bool IsAndroid { get; set; } + public bool IsWindows { get; set; } + public Version OSVersion { get; set; } = new Version(1, 2, 3, 4); } [Test] diff --git a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs index 47ea88743..f3b86b341 100644 --- a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs +++ b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs @@ -613,6 +613,66 @@ private class TestOperatingSystem : IOperatingSystem { public bool IsBrowser { get; set; } public bool IsAndroid { get; set; } + public bool IsWindows { get; set; } + public Version OSVersion { get; set; } = new Version(1, 2, 3, 4); + } + + [Test] + public void WinHttpHandler_UnsupportedWindows_Throw() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new TestOperatingSystem + { + IsWindows = true, + OSVersion = new Version(1, 2, 3, 4) + }); + +#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 + + // Act + var ex = Assert.Throws(() => + { + GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions + { + HttpHandler = winHttpHandler, + ServiceProvider = services.BuildServiceProvider() + }); + }); + + // Assert + Assert.AreEqual(ex!.Message, "The channel configuration isn't valid on this operating system. " + + "The channel is configured to use WinHttpHandler and the current version of Windows " + + "doesn't support HTTP/2 features required by gRPC. Windows Server 2022 or Windows 11 or later is required. " + + "For more information, see https://aka.ms/aspnet/grpc/netframework."); + } + + [Test] + public void WinHttpHandler_SupportedWindows_Success() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new TestOperatingSystem + { + IsWindows = true, + 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 + + // Act + var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions + { + HttpHandler = winHttpHandler, + ServiceProvider = services.BuildServiceProvider() + }); + + // Assert + Assert.AreEqual(HttpHandlerType.WinHttpHandler, channel.HttpHandlerType); } #if SUPPORT_LOAD_BALANCING diff --git a/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs b/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs new file mode 100644 index 000000000..c91c3b6e1 --- /dev/null +++ b/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs @@ -0,0 +1,43 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Grpc.Net.Client.Internal; +using NUnit.Framework; +using OperatingSystem = Grpc.Net.Client.Internal.OperatingSystem; + +namespace Grpc.Net.Client.Tests; + +public class OperatingSystemTests +{ +#if !NET5_0_OR_GREATER + [Test] + [Platform("Win", Reason = "Only runs on Windows where ntdll.dll is present.")] + public void DetectWindowsVersion_Windows_MatchesEnvironment() + { + // It is safe to compare Environment.OSVersion.Version on netfx because tests have no compatibilty setting. + Assert.AreEqual(Environment.OSVersion.Version, NtDll.DetectWindowsVersion()); + } +#endif + + [Test] + public void OSVersion_ModernDotNet_MatchesEnvironment() + { + // It is safe to compare Environment.OSVersion.Version on netfx because tests have no compatibilty setting. + Assert.AreEqual(Environment.OSVersion.Version, OperatingSystem.Instance.OSVersion); + } +} From c41d7e2302d85d55b6282023d4edc770325517c3 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Wed, 16 Aug 2023 13:18:57 +0200 Subject: [PATCH 3/3] Update version to -pre2 on v2.56.x branch (#2246) --- build/version.props | 2 +- src/Grpc.Core.Api/VersionInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/version.props b/build/version.props index c551052c1..dc18136f4 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ - 2.56.0-pre1 + 2.56.0-pre2 2.0.0.0 diff --git a/src/Grpc.Core.Api/VersionInfo.cs b/src/Grpc.Core.Api/VersionInfo.cs index c1ec1213c..18bc28419 100644 --- a/src/Grpc.Core.Api/VersionInfo.cs +++ b/src/Grpc.Core.Api/VersionInfo.cs @@ -41,5 +41,5 @@ public static class VersionInfo /// /// Current version of gRPC C# /// - public const string CurrentVersion = "2.56.0-pre1"; + public const string CurrentVersion = "2.56.0-pre2"; }