aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/csharp
diff options
context:
space:
mode:
Diffstat (limited to 'src/csharp')
-rw-r--r--src/csharp/Grpc.Auth/AuthInterceptors.cs84
-rw-r--r--src/csharp/Grpc.Auth/Grpc.Auth.csproj42
-rw-r--r--src/csharp/Grpc.Auth/OAuth2Interceptors.cs115
-rw-r--r--src/csharp/Grpc.Core.Tests/ChannelTest.cs33
-rw-r--r--src/csharp/Grpc.Core.Tests/ClientServerTest.cs15
-rw-r--r--src/csharp/Grpc.Core.Tests/CompressionTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj2
-rw-r--r--src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs26
-rw-r--r--src/csharp/Grpc.Core.Tests/MetadataTest.cs120
-rw-r--r--src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/ServerTest.cs5
-rw-r--r--src/csharp/Grpc.Core.Tests/ShutdownTest.cs77
-rw-r--r--src/csharp/Grpc.Core.Tests/TimeoutsTest.cs8
-rw-r--r--src/csharp/Grpc.Core/AsyncClientStreamingCall.cs18
-rw-r--r--src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs2
-rw-r--r--src/csharp/Grpc.Core/AsyncServerStreamingCall.cs2
-rw-r--r--src/csharp/Grpc.Core/Channel.cs50
-rw-r--r--src/csharp/Grpc.Core/ChannelOptions.cs2
-rw-r--r--src/csharp/Grpc.Core/ClientBase.cs21
-rw-r--r--src/csharp/Grpc.Core/ContextPropagationToken.cs1
-rw-r--r--src/csharp/Grpc.Core/GrpcEnvironment.cs37
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCall.cs8
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCallBase.cs6
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCallServer.cs17
-rw-r--r--src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs16
-rw-r--r--src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs7
-rw-r--r--src/csharp/Grpc.Core/Internal/DebugStats.cs14
-rw-r--r--src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs3
-rw-r--r--src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs5
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerCallHandler.cs10
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs4
-rw-r--r--src/csharp/Grpc.Core/Logging/ConsoleLogger.cs14
-rw-r--r--src/csharp/Grpc.Core/Metadata.cs112
-rw-r--r--src/csharp/Grpc.Core/Method.cs29
-rw-r--r--src/csharp/Grpc.Core/Server.cs57
-rw-r--r--src/csharp/Grpc.Examples.MathClient/MathClient.cs20
-rw-r--r--src/csharp/Grpc.Examples.MathServer/MathServer.cs1
-rw-r--r--src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs3
-rw-r--r--src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs3
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropClient.cs52
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs9
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropServer.cs2
-rw-r--r--src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs3
44 files changed, 717 insertions, 362 deletions
diff --git a/src/csharp/Grpc.Auth/AuthInterceptors.cs b/src/csharp/Grpc.Auth/AuthInterceptors.cs
new file mode 100644
index 0000000000..61338f7f0e
--- /dev/null
+++ b/src/csharp/Grpc.Auth/AuthInterceptors.cs
@@ -0,0 +1,84 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading;
+
+using Google.Apis.Auth.OAuth2;
+using Grpc.Core;
+using Grpc.Core.Utils;
+
+namespace Grpc.Auth
+{
+ /// <summary>
+ /// Factory methods to create authorization interceptors.
+ /// </summary>
+ public static class AuthInterceptors
+ {
+ private const string AuthorizationHeader = "Authorization";
+ private const string Schema = "Bearer";
+
+ /// <summary>
+ /// Creates interceptor that will obtain access token from any credential type that implements
+ /// <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>).
+ /// </summary>
+ public static HeaderInterceptor FromCredential(ITokenAccess credential)
+ {
+ return new HeaderInterceptor((method, authUri, metadata) =>
+ {
+ // TODO(jtattermusch): Rethink synchronous wait to obtain the result.
+ var accessToken = credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None)
+ .ConfigureAwait(false).GetAwaiter().GetResult();
+ metadata.Add(CreateBearerTokenHeader(accessToken));
+ });
+ }
+
+ /// <summary>
+ /// Creates OAuth2 interceptor that will use given access token as authorization.
+ /// </summary>
+ /// <param name="accessToken">OAuth2 access token.</param>
+ public static HeaderInterceptor FromAccessToken(string accessToken)
+ {
+ Preconditions.CheckNotNull(accessToken);
+ return new HeaderInterceptor((method, authUri, metadata) =>
+ {
+ metadata.Add(CreateBearerTokenHeader(accessToken));
+ });
+ }
+
+ private static Metadata.Entry CreateBearerTokenHeader(string accessToken)
+ {
+ return new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken);
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
index 930a34b0c3..4fb087d4a3 100644
--- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj
+++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
@@ -3,8 +3,6 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>10.0.0</ProductVersion>
- <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Grpc.Auth</RootNamespace>
@@ -41,57 +39,47 @@
<AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
- <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System" />
+ <Reference Include="System.Net" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Net.Http.WebRequest" />
+ <Reference Include="BouncyCastle.Crypto">
<HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Google.Apis.Auth">
<HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Google.Apis.Auth.PlatformServices">
<HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Google.Apis.Core">
<HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Microsoft.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Microsoft.Threading.Tasks.Extensions">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
</Reference>
- <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
- <Reference Include="System" />
- <Reference Include="System.Net" />
- <Reference Include="System.Net.Http" />
- <Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System.Net.Http.Extensions">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
</Reference>
- <Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System.Net.Http.Primitives">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
</Reference>
- <Reference Include="System.Net.Http.WebRequest" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Grpc.Core\Version.cs">
<Link>Version.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="OAuth2Interceptors.cs" />
+ <Compile Include="AuthInterceptors.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs
deleted file mode 100644
index d628a83246..0000000000
--- a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-#region Copyright notice and license
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Security.Cryptography.X509Certificates;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Google.Apis.Auth.OAuth2;
-using Google.Apis.Util;
-using Grpc.Core;
-using Grpc.Core.Utils;
-
-namespace Grpc.Auth
-{
- public static class OAuth2Interceptors
- {
- /// <summary>
- /// Creates OAuth2 interceptor that will obtain access token from GoogleCredentials.
- /// </summary>
- public static MetadataInterceptorDelegate FromCredential(GoogleCredential googleCredential)
- {
- var interceptor = new OAuth2Interceptor(googleCredential, SystemClock.Default);
- return new MetadataInterceptorDelegate(interceptor.InterceptHeaders);
- }
-
- /// <summary>
- /// Creates OAuth2 interceptor that will use given OAuth2 token.
- /// </summary>
- /// <param name="oauth2Token"></param>
- /// <returns></returns>
- public static MetadataInterceptorDelegate FromAccessToken(string oauth2Token)
- {
- Preconditions.CheckNotNull(oauth2Token);
- return new MetadataInterceptorDelegate((authUri, metadata) =>
- {
- metadata.Add(OAuth2Interceptor.CreateBearerTokenHeader(oauth2Token));
- });
- }
-
- /// <summary>
- /// Injects OAuth2 authorization header into initial metadata (= request headers).
- /// </summary>
- private class OAuth2Interceptor
- {
- private const string AuthorizationHeader = "Authorization";
- private const string Schema = "Bearer";
-
- private ITokenAccess credential;
- private IClock clock;
-
- public OAuth2Interceptor(ITokenAccess credential, IClock clock)
- {
- this.credential = credential;
- this.clock = clock;
- }
-
- /// <summary>
- /// Gets access token and requests refreshing it if is going to expire soon.
- /// </summary>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
- public string GetAccessToken(string authUri, CancellationToken cancellationToken)
- {
- // TODO(jtattermusch): Rethink synchronous wait to obtain the result.
- return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken: cancellationToken).GetAwaiter().GetResult();
- }
-
- public void InterceptHeaders(string authUri, Metadata metadata)
- {
- var accessToken = GetAccessToken(authUri, CancellationToken.None);
- metadata.Add(CreateBearerTokenHeader(accessToken));
- }
-
- public static Metadata.Entry CreateBearerTokenHeader(string accessToken)
- {
- return new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken);
- }
- }
- }
-}
diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
index 2787572924..dfbd92879e 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
@@ -41,12 +41,6 @@ namespace Grpc.Core.Tests
{
public class ChannelTest
{
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public void Constructor_RejectsInvalidParams()
{
@@ -56,36 +50,33 @@ namespace Grpc.Core.Tests
[Test]
public void State_IdleAfterCreation()
{
- using (var channel = new Channel("localhost", Credentials.Insecure))
- {
- Assert.AreEqual(ChannelState.Idle, channel.State);
- }
+ var channel = new Channel("localhost", Credentials.Insecure);
+ Assert.AreEqual(ChannelState.Idle, channel.State);
+ channel.ShutdownAsync().Wait();
}
[Test]
public void WaitForStateChangedAsync_InvalidArgument()
{
- using (var channel = new Channel("localhost", Credentials.Insecure))
- {
- Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure));
- }
+ var channel = new Channel("localhost", Credentials.Insecure);
+ Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure));
+ channel.ShutdownAsync().Wait();
}
[Test]
public void ResolvedTarget()
{
- using (var channel = new Channel("127.0.0.1", Credentials.Insecure))
- {
- Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
- }
+ var channel = new Channel("127.0.0.1", Credentials.Insecure);
+ Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
+ channel.ShutdownAsync().Wait();
}
[Test]
- public void Dispose_IsIdempotent()
+ public void Shutdown_AllowedOnlyOnce()
{
var channel = new Channel("localhost", Credentials.Insecure);
- channel.Dispose();
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
+ Assert.Throws(typeof(InvalidOperationException), () => channel.ShutdownAsync().GetAwaiter().GetResult());
}
}
}
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index e49fdb5268..68279a2007 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -63,16 +63,10 @@ namespace Grpc.Core.Tests
[TearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public async Task UnaryCall()
{
@@ -208,13 +202,6 @@ namespace Grpc.Core.Tests
}
[Test]
- public void UnaryCall_DisposedChannel()
- {
- channel.Dispose();
- Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
- }
-
- [Test]
public void UnaryCallPerformance()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
diff --git a/src/csharp/Grpc.Core.Tests/CompressionTest.cs b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
index 9547683f60..378c81851c 100644
--- a/src/csharp/Grpc.Core.Tests/CompressionTest.cs
+++ b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
@@ -62,16 +62,10 @@ namespace Grpc.Core.Tests
[TearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public void WriteOptions_Unary()
{
diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
index db5f953b0e..2db3f286f7 100644
--- a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
@@ -62,16 +62,10 @@ namespace Grpc.Core.Tests
[TearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public async Task PropagateCancellation()
{
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index d6a8f52570..ad4e94a695 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -64,6 +64,7 @@
<Link>Version.cs</Link>
</Compile>
<Compile Include="ClientBaseTest.cs" />
+ <Compile Include="ShutdownTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ClientServerTest.cs" />
<Compile Include="ServerTest.cs" />
@@ -82,6 +83,7 @@
<Compile Include="ResponseHeadersTest.cs" />
<Compile Include="CompressionTest.cs" />
<Compile Include="ContextPropagationTest.cs" />
+ <Compile Include="MetadataTest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
index 4ed93c7eca..4fdfab5a99 100644
--- a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
+++ b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
@@ -43,34 +43,40 @@ namespace Grpc.Core.Tests
[Test]
public void InitializeAndShutdownGrpcEnvironment()
{
- var env = GrpcEnvironment.GetInstance();
+ var env = GrpcEnvironment.AddRef();
Assert.IsNotNull(env.CompletionQueue);
- GrpcEnvironment.Shutdown();
+ GrpcEnvironment.Release();
}
[Test]
public void SubsequentInvocations()
{
- var env1 = GrpcEnvironment.GetInstance();
- var env2 = GrpcEnvironment.GetInstance();
+ var env1 = GrpcEnvironment.AddRef();
+ var env2 = GrpcEnvironment.AddRef();
Assert.IsTrue(object.ReferenceEquals(env1, env2));
- GrpcEnvironment.Shutdown();
- GrpcEnvironment.Shutdown();
+ GrpcEnvironment.Release();
+ GrpcEnvironment.Release();
}
[Test]
public void InitializeAfterShutdown()
{
- var env1 = GrpcEnvironment.GetInstance();
- GrpcEnvironment.Shutdown();
+ var env1 = GrpcEnvironment.AddRef();
+ GrpcEnvironment.Release();
- var env2 = GrpcEnvironment.GetInstance();
- GrpcEnvironment.Shutdown();
+ var env2 = GrpcEnvironment.AddRef();
+ GrpcEnvironment.Release();
Assert.IsFalse(object.ReferenceEquals(env1, env2));
}
[Test]
+ public void ReleaseWithoutAddRef()
+ {
+ Assert.Throws(typeof(InvalidOperationException), () => GrpcEnvironment.Release());
+ }
+
+ [Test]
public void GetCoreVersionString()
{
var coreVersion = GrpcEnvironment.GetCoreVersionString();
diff --git a/src/csharp/Grpc.Core.Tests/MetadataTest.cs b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
new file mode 100644
index 0000000000..c00f945d6a
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
@@ -0,0 +1,120 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class MetadataTest
+ {
+ [Test]
+ public void AsciiEntry()
+ {
+ var entry = new Metadata.Entry("ABC", "XYZ");
+ Assert.IsFalse(entry.IsBinary);
+ Assert.AreEqual("abc", entry.Key); // key is in lowercase.
+ Assert.AreEqual("XYZ", entry.Value);
+ CollectionAssert.AreEqual(new[] { (byte)'X', (byte)'Y', (byte)'Z' }, entry.ValueBytes);
+
+ Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc-bin", "xyz"));
+
+ Assert.AreEqual("[Entry: key=abc, value=XYZ]", entry.ToString());
+ }
+
+ [Test]
+ public void BinaryEntry()
+ {
+ var bytes = new byte[] { 1, 2, 3 };
+ var entry = new Metadata.Entry("ABC-BIN", bytes);
+ Assert.IsTrue(entry.IsBinary);
+ Assert.AreEqual("abc-bin", entry.Key); // key is in lowercase.
+ Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
+ CollectionAssert.AreEqual(bytes, entry.ValueBytes);
+
+ Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc", bytes));
+
+ Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString());
+ }
+
+ [Test]
+ public void Entry_ConstructionPreconditions()
+ {
+ Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry(null, "xyz"));
+ Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc", (string)null));
+ Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc-bin", (byte[])null));
+ }
+
+ [Test]
+ public void Entry_Immutable()
+ {
+ var origBytes = new byte[] { 1, 2, 3 };
+ var bytes = new byte[] { 1, 2, 3 };
+ var entry = new Metadata.Entry("ABC-BIN", bytes);
+ bytes[0] = 255; // changing the array passed to constructor should have any effect.
+ CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
+
+ entry.ValueBytes[0] = 255;
+ CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
+ }
+
+ [Test]
+ public void Entry_CreateUnsafe_Ascii()
+ {
+ var bytes = new byte[] { (byte)'X', (byte)'y' };
+ var entry = Metadata.Entry.CreateUnsafe("abc", bytes);
+ Assert.IsFalse(entry.IsBinary);
+ Assert.AreEqual("abc", entry.Key);
+ Assert.AreEqual("Xy", entry.Value);
+ CollectionAssert.AreEqual(bytes, entry.ValueBytes);
+ }
+
+ [Test]
+ public void Entry_CreateUnsafe_Binary()
+ {
+ var bytes = new byte[] { 1, 2, 3 };
+ var entry = Metadata.Entry.CreateUnsafe("abc-bin", bytes);
+ Assert.IsTrue(entry.IsBinary);
+ Assert.AreEqual("abc-bin", entry.Key);
+ Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
+ CollectionAssert.AreEqual(bytes, entry.ValueBytes);
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
index 981b8ea3c8..706006702e 100644
--- a/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
@@ -69,16 +69,10 @@ namespace Grpc.Core.Tests
[TearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public void WriteResponseHeaders_NullNotAllowed()
{
diff --git a/src/csharp/Grpc.Core.Tests/ServerTest.cs b/src/csharp/Grpc.Core.Tests/ServerTest.cs
index 485006ebac..e7193c843b 100644
--- a/src/csharp/Grpc.Core.Tests/ServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ServerTest.cs
@@ -51,7 +51,6 @@ namespace Grpc.Core.Tests
};
server.Start();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
[Test]
@@ -67,8 +66,7 @@ namespace Grpc.Core.Tests
Assert.Greater(boundPort.BoundPort, 0);
server.Start();
- server.ShutdownAsync();
- GrpcEnvironment.Shutdown();
+ server.ShutdownAsync().Wait();
}
[Test]
@@ -83,7 +81,6 @@ namespace Grpc.Core.Tests
Assert.Throws(typeof(InvalidOperationException), () => server.Services.Add(ServerServiceDefinition.CreateBuilder("serviceName").Build()));
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
}
}
diff --git a/src/csharp/Grpc.Core.Tests/ShutdownTest.cs b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
new file mode 100644
index 0000000000..a2be7ddd5e
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
@@ -0,0 +1,77 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class ShutdownTest
+ {
+ const string Host = "127.0.0.1";
+
+ MockServiceHelper helper;
+ Server server;
+ Channel channel;
+
+ [SetUp]
+ public void Init()
+ {
+ helper = new MockServiceHelper(Host);
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+ }
+
+ [Test]
+ public async Task AbandonedCall()
+ {
+ helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+ {
+ await requestStream.ToListAsync();
+ });
+
+ var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(1))));
+
+ channel.ShutdownAsync().Wait();
+ server.ShutdownAsync().Wait();
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
index d875d601b9..41f661f62d 100644
--- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
@@ -65,16 +65,10 @@ namespace Grpc.Core.Tests
[TearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
- [TestFixtureTearDown]
- public void CleanupClass()
- {
- GrpcEnvironment.Shutdown();
- }
-
[Test]
public void InfiniteDeadline()
{
diff --git a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
index bf020cd627..fb9b562c77 100644
--- a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
+++ b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
@@ -89,6 +89,24 @@ namespace Grpc.Core
}
/// <summary>
+ /// Gets the call status if the call has already finished.
+ /// Throws InvalidOperationException otherwise.
+ /// </summary>
+ public Status GetStatus()
+ {
+ return getStatusFunc();
+ }
+
+ /// <summary>
+ /// Gets the call trailing metadata if the call has already finished.
+ /// Throws InvalidOperationException otherwise.
+ /// </summary>
+ public Metadata GetTrailers()
+ {
+ return getTrailersFunc();
+ }
+
+ /// <summary>
/// Provides means to cleanup after the call.
/// If the call has already finished normally (request stream has been completed and call result has been received), doesn't do anything.
/// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call.
diff --git a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
index 0979de606f..183c84216a 100644
--- a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
+++ b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
@@ -32,8 +32,6 @@
#endregion
using System;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
namespace Grpc.Core
{
diff --git a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
index 380efcdb0e..ab2049f269 100644
--- a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
+++ b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
@@ -32,8 +32,6 @@
#endregion
using System;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
namespace Grpc.Core
{
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index 64c6adf2bf..2f8519dfa3 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -45,14 +45,19 @@ namespace Grpc.Core
/// <summary>
/// gRPC Channel
/// </summary>
- public class Channel : IDisposable
+ public class Channel
{
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
+ readonly object myLock = new object();
+ readonly AtomicCounter activeCallCounter = new AtomicCounter();
+
readonly string target;
readonly GrpcEnvironment environment;
readonly ChannelSafeHandle handle;
readonly List<ChannelOption> options;
+
+ bool shutdownRequested;
bool disposed;
/// <summary>
@@ -65,7 +70,7 @@ namespace Grpc.Core
public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null)
{
this.target = Preconditions.CheckNotNull(target, "target");
- this.environment = GrpcEnvironment.GetInstance();
+ this.environment = GrpcEnvironment.AddRef();
this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
EnsureUserAgentChannelOption(this.options);
@@ -172,12 +177,26 @@ namespace Grpc.Core
}
/// <summary>
- /// Destroys the underlying channel.
+ /// Waits until there are no more active calls for this channel and then cleans up
+ /// resources used by this channel.
/// </summary>
- public void Dispose()
+ public async Task ShutdownAsync()
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ lock (myLock)
+ {
+ Preconditions.CheckState(!shutdownRequested);
+ shutdownRequested = true;
+ }
+
+ var activeCallCount = activeCallCounter.Count;
+ if (activeCallCount > 0)
+ {
+ Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
+ }
+
+ handle.Dispose();
+
+ await Task.Run(() => GrpcEnvironment.Release());
}
internal ChannelSafeHandle Handle
@@ -196,13 +215,20 @@ namespace Grpc.Core
}
}
- protected virtual void Dispose(bool disposing)
+ internal void AddCallReference(object call)
{
- if (disposing && handle != null && !disposed)
- {
- disposed = true;
- handle.Dispose();
- }
+ activeCallCounter.Increment();
+
+ bool success = false;
+ handle.DangerousAddRef(ref success);
+ Preconditions.CheckState(success);
+ }
+
+ internal void RemoveCallReference(object call)
+ {
+ handle.DangerousRelease();
+
+ activeCallCounter.Decrement();
}
private static void EnsureUserAgentChannelOption(List<ChannelOption> options)
diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs
index 0cb2953f2c..ad54b46ad5 100644
--- a/src/csharp/Grpc.Core/ChannelOptions.cs
+++ b/src/csharp/Grpc.Core/ChannelOptions.cs
@@ -71,7 +71,7 @@ namespace Grpc.Core
/// Creates a channel option with an integer value.
/// </summary>
/// <param name="name">Name.</param>
- /// <param name="stringValue">String value.</param>
+ /// <param name="intValue">Integer value.</param>
public ChannelOption(string name, int intValue)
{
this.type = OptionType.Integer;
diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs
index f240d777b9..903449439b 100644
--- a/src/csharp/Grpc.Core/ClientBase.cs
+++ b/src/csharp/Grpc.Core/ClientBase.cs
@@ -32,15 +32,15 @@
#endregion
using System;
-using System.Collections.Generic;
using System.Text.RegularExpressions;
-
-using Grpc.Core.Internal;
-using Grpc.Core.Utils;
+using System.Threading.Tasks;
namespace Grpc.Core
{
- public delegate void MetadataInterceptorDelegate(string authUri, Metadata metadata);
+ /// <summary>
+ /// Interceptor for call headers.
+ /// </summary>
+ public delegate void HeaderInterceptor(IMethod method, string authUri, Metadata metadata);
/// <summary>
/// Base class for client-side stubs.
@@ -60,10 +60,10 @@ namespace Grpc.Core
}
/// <summary>
- /// Can be used to register a custom header (initial metadata) interceptor.
- /// The delegate each time before a new call on this client is started.
+ /// Can be used to register a custom header (request metadata) interceptor.
+ /// The interceptor is invoked each time a new call on this client is started.
/// </summary>
- public MetadataInterceptorDelegate HeaderInterceptor
+ public HeaderInterceptor HeaderInterceptor
{
get;
set;
@@ -107,7 +107,7 @@ namespace Grpc.Core
options = options.WithHeaders(new Metadata());
}
var authUri = authUriBase != null ? authUriBase + method.ServiceName : null;
- interceptor(authUri, options.Headers);
+ interceptor(method, authUri, options.Headers);
}
return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options);
}
@@ -119,7 +119,8 @@ namespace Grpc.Core
internal static string GetAuthUriBase(string target)
{
var match = ChannelTargetPattern.Match(target);
- if (!match.Success) {
+ if (!match.Success)
+ {
return null;
}
return "https://" + match.Groups[2].Value + "/";
diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs
index 2e4bfc9e47..a5bf1b5a70 100644
--- a/src/csharp/Grpc.Core/ContextPropagationToken.cs
+++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs
@@ -132,7 +132,6 @@ namespace Grpc.Core
bool propagateDeadline;
bool propagateCancellation;
-
/// <summary>
/// Creates new context propagation options.
/// </summary>
diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs
index 30d8c80235..0a44eead74 100644
--- a/src/csharp/Grpc.Core/GrpcEnvironment.cs
+++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs
@@ -58,6 +58,7 @@ namespace Grpc.Core
static object staticLock = new object();
static GrpcEnvironment instance;
+ static int refCount;
static ILogger logger = new ConsoleLogger();
@@ -67,13 +68,14 @@ namespace Grpc.Core
bool isClosed;
/// <summary>
- /// Returns an instance of initialized gRPC environment.
- /// Subsequent invocations return the same instance unless Shutdown has been called first.
+ /// Returns a reference-counted instance of initialized gRPC environment.
+ /// Subsequent invocations return the same instance unless reference count has dropped to zero previously.
/// </summary>
- internal static GrpcEnvironment GetInstance()
+ internal static GrpcEnvironment AddRef()
{
lock (staticLock)
{
+ refCount++;
if (instance == null)
{
instance = new GrpcEnvironment();
@@ -83,14 +85,16 @@ namespace Grpc.Core
}
/// <summary>
- /// Shuts down the gRPC environment if it was initialized before.
- /// Blocks until the environment has been fully shutdown.
+ /// Decrements the reference count for currently active environment and shuts down the gRPC environment if reference count drops to zero.
+ /// (and blocks until the environment has been fully shutdown).
/// </summary>
- public static void Shutdown()
+ internal static void Release()
{
lock (staticLock)
{
- if (instance != null)
+ Preconditions.CheckState(refCount > 0);
+ refCount--;
+ if (refCount == 0)
{
instance.Close();
instance = null;
@@ -125,12 +129,10 @@ namespace Grpc.Core
private GrpcEnvironment()
{
NativeLogRedirector.Redirect();
- grpcsharp_init();
+ GrpcNativeInit();
completionRegistry = new CompletionRegistry(this);
threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE);
threadPool.Start();
- // TODO: use proper logging here
- Logger.Info("gRPC initialized.");
}
/// <summary>
@@ -175,6 +177,17 @@ namespace Grpc.Core
return Marshal.PtrToStringAnsi(ptr);
}
+
+ internal static void GrpcNativeInit()
+ {
+ grpcsharp_init();
+ }
+
+ internal static void GrpcNativeShutdown()
+ {
+ grpcsharp_shutdown();
+ }
+
/// <summary>
/// Shuts down this environment.
/// </summary>
@@ -185,12 +198,10 @@ namespace Grpc.Core
throw new InvalidOperationException("Close has already been called");
}
threadPool.Stop();
- grpcsharp_shutdown();
+ GrpcNativeShutdown();
isClosed = true;
debugStats.CheckOK();
-
- Logger.Info("gRPC shutdown.");
}
}
}
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 2c3e3d75ea..bb9ba5b8dd 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -311,9 +311,9 @@ namespace Grpc.Core.Internal
}
}
- protected override void OnReleaseResources()
+ protected override void OnAfterReleaseResources()
{
- details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
+ details.Channel.RemoveCallReference(this);
}
private void Initialize(CompletionQueueSafeHandle cq)
@@ -323,7 +323,9 @@ namespace Grpc.Core.Internal
var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry,
parentCall, ContextPropagationToken.DefaultMask, cq,
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value));
- details.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
+
+ details.Channel.AddCallReference(this);
+
InitializeInternal(call);
RegisterCancellationCallback();
}
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 6ca4bbdafc..1808294f43 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -189,15 +189,15 @@ namespace Grpc.Core.Internal
private void ReleaseResources()
{
- OnReleaseResources();
if (call != null)
{
call.Dispose();
}
disposed = true;
+ OnAfterReleaseResources();
}
- protected virtual void OnReleaseResources()
+ protected virtual void OnAfterReleaseResources()
{
}
@@ -212,7 +212,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(sendCompletionDelegate == null, "Only one write can be pending at a time");
}
- protected void CheckReadingAllowed()
+ protected virtual void CheckReadingAllowed()
{
Preconditions.CheckState(started);
Preconditions.CheckState(!disposed);
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index 3710a65d6b..6278c0191e 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -50,16 +50,19 @@ namespace Grpc.Core.Internal
readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>();
readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
readonly GrpcEnvironment environment;
+ readonly Server server;
- public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment) : base(serializer, deserializer)
+ public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment, Server server) : base(serializer, deserializer)
{
this.environment = Preconditions.CheckNotNull(environment);
+ this.server = Preconditions.CheckNotNull(server);
}
public void Initialize(CallSafeHandle call)
{
call.SetCompletionRegistry(environment.CompletionRegistry);
- environment.DebugStats.ActiveServerCalls.Increment();
+
+ server.AddCallReference(this);
InitializeInternal(call);
}
@@ -168,9 +171,15 @@ namespace Grpc.Core.Internal
}
}
- protected override void OnReleaseResources()
+ protected override void CheckReadingAllowed()
+ {
+ base.CheckReadingAllowed();
+ Preconditions.CheckArgument(!cancelRequested);
+ }
+
+ protected override void OnAfterReleaseResources()
{
- environment.DebugStats.ActiveServerCalls.Decrement();
+ server.RemoveCallReference(this);
}
/// <summary>
diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
index 6a2add54db..3a96414bea 100644
--- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
@@ -134,7 +134,7 @@ namespace Grpc.Core.Internal
}
// Gets data of server_rpc_new completion.
- public ServerRpcNew GetServerRpcNew()
+ public ServerRpcNew GetServerRpcNew(Server server)
{
var call = grpcsharp_batch_context_server_rpc_new_call(this);
@@ -145,7 +145,7 @@ namespace Grpc.Core.Internal
IntPtr metadataArrayPtr = grpcsharp_batch_context_server_rpc_new_request_metadata(this);
var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);
- return new ServerRpcNew(call, method, host, deadline, metadata);
+ return new ServerRpcNew(server, call, method, host, deadline, metadata);
}
// Gets data of receive_close_on_server completion.
@@ -198,14 +198,16 @@ namespace Grpc.Core.Internal
/// </summary>
internal struct ServerRpcNew
{
+ readonly Server server;
readonly CallSafeHandle call;
readonly string method;
readonly string host;
readonly Timespec deadline;
readonly Metadata requestMetadata;
- public ServerRpcNew(CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata)
+ public ServerRpcNew(Server server, CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata)
{
+ this.server = server;
this.call = call;
this.method = method;
this.host = host;
@@ -213,6 +215,14 @@ namespace Grpc.Core.Internal
this.requestMetadata = requestMetadata;
}
+ public Server Server
+ {
+ get
+ {
+ return this.server;
+ }
+ }
+
public CallSafeHandle Call
{
get
diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
index 7f03bf4ea5..8cef566c14 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
@@ -68,11 +68,17 @@ namespace Grpc.Core.Internal
public static ChannelSafeHandle CreateInsecure(string target, ChannelArgsSafeHandle channelArgs)
{
+ // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
+ // Doing so would make object finalizer crash if we end up abandoning the handle.
+ GrpcEnvironment.GrpcNativeInit();
return grpcsharp_insecure_channel_create(target, channelArgs);
}
public static ChannelSafeHandle CreateSecure(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs)
{
+ // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
+ // Doing so would make object finalizer crash if we end up abandoning the handle.
+ GrpcEnvironment.GrpcNativeInit();
return grpcsharp_secure_channel_create(credentials, target, channelArgs);
}
@@ -107,6 +113,7 @@ namespace Grpc.Core.Internal
protected override bool ReleaseHandle()
{
grpcsharp_channel_destroy(handle);
+ GrpcEnvironment.GrpcNativeShutdown();
return true;
}
}
diff --git a/src/csharp/Grpc.Core/Internal/DebugStats.cs b/src/csharp/Grpc.Core/Internal/DebugStats.cs
index 8793450ff3..1bea1adf9e 100644
--- a/src/csharp/Grpc.Core/Internal/DebugStats.cs
+++ b/src/csharp/Grpc.Core/Internal/DebugStats.cs
@@ -38,10 +38,6 @@ namespace Grpc.Core.Internal
{
internal class DebugStats
{
- public readonly AtomicCounter ActiveClientCalls = new AtomicCounter();
-
- public readonly AtomicCounter ActiveServerCalls = new AtomicCounter();
-
public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter();
/// <summary>
@@ -49,16 +45,6 @@ namespace Grpc.Core.Internal
/// </summary>
public void CheckOK()
{
- var remainingClientCalls = ActiveClientCalls.Count;
- if (remainingClientCalls != 0)
- {
- DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls));
- }
- var remainingServerCalls = ActiveServerCalls.Count;
- if (remainingServerCalls != 0)
- {
- DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls));
- }
var pendingBatchCompletions = PendingBatchCompletions.Count;
if (pendingBatchCompletions != 0)
{
diff --git a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
index cb4c7c821e..4b7124ee74 100644
--- a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
+++ b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
@@ -83,8 +83,6 @@ namespace Grpc.Core.Internal
lock (myLock)
{
cq.Shutdown();
-
- Logger.Info("Waiting for GRPC threads to finish.");
foreach (var thread in threads)
{
thread.Join();
@@ -136,7 +134,6 @@ namespace Grpc.Core.Internal
}
}
while (ev.type != GRPCCompletionType.Shutdown);
- Logger.Info("Completion queue has shutdown successfully, thread {0} exiting.", Thread.CurrentThread.Name);
}
}
}
diff --git a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
index 427c16fac6..83994f6762 100644
--- a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
@@ -70,7 +70,8 @@ namespace Grpc.Core.Internal
var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count));
for (int i = 0; i < metadata.Count; i++)
{
- grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, metadata[i].ValueBytes, new UIntPtr((ulong)metadata[i].ValueBytes.Length));
+ var valueBytes = metadata[i].GetSerializedValueUnsafe();
+ grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, valueBytes, new UIntPtr((ulong)valueBytes.Length));
}
return metadataArray;
}
@@ -94,7 +95,7 @@ namespace Grpc.Core.Internal
string key = Marshal.PtrToStringAnsi(grpcsharp_metadata_array_get_key(metadataArray, index));
var bytes = new byte[grpcsharp_metadata_array_get_value_length(metadataArray, index).ToUInt64()];
Marshal.Copy(grpcsharp_metadata_array_get_value(metadataArray, index), bytes, 0, bytes.Length);
- metadata.Add(new Metadata.Entry(key, bytes));
+ metadata.Add(Metadata.Entry.CreateUnsafe(key, bytes));
}
return metadata;
}
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 688f9f6fec..59f4c5727c 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -67,7 +67,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer,
- environment);
+ environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync();
@@ -123,7 +123,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer,
- environment);
+ environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync();
@@ -179,7 +179,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer,
- environment);
+ environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync();
@@ -239,7 +239,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer,
- environment);
+ environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync();
@@ -278,7 +278,7 @@ namespace Grpc.Core.Internal
{
// We don't care about the payload type here.
var asyncCall = new AsyncCallServer<byte[], byte[]>(
- (payload) => payload, (payload) => payload, environment);
+ (payload) => payload, (payload) => payload, environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync();
diff --git a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
index f9b44b1acf..5ee7ac14e8 100644
--- a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
@@ -74,6 +74,9 @@ namespace Grpc.Core.Internal
public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, ChannelArgsSafeHandle args)
{
+ // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
+ // Doing so would make object finalizer crash if we end up abandoning the handle.
+ GrpcEnvironment.GrpcNativeInit();
return grpcsharp_server_create(cq, args);
}
@@ -109,6 +112,7 @@ namespace Grpc.Core.Internal
protected override bool ReleaseHandle()
{
grpcsharp_server_destroy(handle);
+ GrpcEnvironment.GrpcNativeShutdown();
return true;
}
diff --git a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
index 382481d871..35561d25d8 100644
--- a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
+++ b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
@@ -51,7 +51,19 @@ namespace Grpc.Core.Logging
private ConsoleLogger(Type forType)
{
this.forType = forType;
- this.forTypeString = forType != null ? forType.FullName + " " : "";
+ if (forType != null)
+ {
+ var namespaceStr = forType.Namespace ?? "";
+ if (namespaceStr.Length > 0)
+ {
+ namespaceStr += ".";
+ }
+ this.forTypeString = namespaceStr + forType.Name + " ";
+ }
+ else
+ {
+ this.forTypeString = "";
+ }
}
/// <summary>
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 9db2abf46e..a589b50caa 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -46,6 +46,11 @@ namespace Grpc.Core
public sealed class Metadata : IList<Metadata.Entry>
{
/// <summary>
+ /// All binary headers should have this suffix.
+ /// </summary>
+ public const string BinaryHeaderSuffix = "-bin";
+
+ /// <summary>
/// An read-only instance of metadata containing no entries.
/// </summary>
public static readonly Metadata Empty = new Metadata().Freeze();
@@ -181,23 +186,49 @@ namespace Grpc.Core
private static readonly Encoding Encoding = Encoding.ASCII;
readonly string key;
- string value;
- byte[] valueBytes;
+ readonly string value;
+ readonly byte[] valueBytes;
+
+ private Entry(string key, string value, byte[] valueBytes)
+ {
+ this.key = key;
+ this.value = value;
+ this.valueBytes = valueBytes;
+ }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Grpc.Core.Metadata+Entry"/> struct with a binary value.
+ /// </summary>
+ /// <param name="key">Metadata key, needs to have suffix indicating a binary valued metadata entry.</param>
+ /// <param name="valueBytes">Value bytes.</param>
public Entry(string key, byte[] valueBytes)
{
- this.key = Preconditions.CheckNotNull(key, "key");
+ this.key = NormalizeKey(key);
+ Preconditions.CheckArgument(this.key.EndsWith(BinaryHeaderSuffix),
+ "Key for binary valued metadata entry needs to have suffix indicating binary value.");
this.value = null;
- this.valueBytes = Preconditions.CheckNotNull(valueBytes, "valueBytes");
+ Preconditions.CheckNotNull(valueBytes, "valueBytes");
+ this.valueBytes = new byte[valueBytes.Length];
+ Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length); // defensive copy to guarantee immutability
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Grpc.Core.Metadata+Entry"/> struct holding an ASCII value.
+ /// </summary>
+ /// <param name="key">Metadata key, must not use suffix indicating a binary valued metadata entry.</param>
+ /// <param name="value">Value string. Only ASCII characters are allowed.</param>
public Entry(string key, string value)
{
- this.key = Preconditions.CheckNotNull(key, "key");
+ this.key = NormalizeKey(key);
+ Preconditions.CheckArgument(!this.key.EndsWith(BinaryHeaderSuffix),
+ "Key for ASCII valued metadata entry cannot have suffix indicating binary value.");
this.value = Preconditions.CheckNotNull(value, "value");
this.valueBytes = null;
}
+ /// <summary>
+ /// Gets the metadata entry key.
+ /// </summary>
public string Key
{
get
@@ -206,33 +237,86 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the binary value of this metadata entry.
+ /// </summary>
public byte[] ValueBytes
{
get
{
if (valueBytes == null)
{
- valueBytes = Encoding.GetBytes(value);
+ return Encoding.GetBytes(value);
}
- return valueBytes;
+
+ // defensive copy to guarantee immutability
+ var bytes = new byte[valueBytes.Length];
+ Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length);
+ return bytes;
}
}
+ /// <summary>
+ /// Gets the string value of this metadata entry.
+ /// </summary>
public string Value
{
get
{
- if (value == null)
- {
- value = Encoding.GetString(valueBytes);
- }
- return value;
+ Preconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");
+ return value ?? Encoding.GetString(valueBytes);
}
}
-
+
+ /// <summary>
+ /// Returns <c>true</c> if this entry is a binary-value entry.
+ /// </summary>
+ public bool IsBinary
+ {
+ get
+ {
+ return value == null;
+ }
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Metadata+Entry"/>.
+ /// </summary>
public override string ToString()
{
- return string.Format("[Entry: key={0}, value={1}]", Key, Value);
+ if (IsBinary)
+ {
+ return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes);
+ }
+
+ return string.Format("[Entry: key={0}, value={1}]", key, value);
+ }
+
+ /// <summary>
+ /// Gets the serialized value for this entry. For binary metadata entries, this leaks
+ /// the internal <c>valueBytes</c> byte array and caller must not change contents of it.
+ /// </summary>
+ internal byte[] GetSerializedValueUnsafe()
+ {
+ return valueBytes ?? Encoding.GetBytes(value);
+ }
+
+ /// <summary>
+ /// Creates a binary value or ascii value metadata entry from data received from the native layer.
+ /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
+ /// </summary>
+ internal static Entry CreateUnsafe(string key, byte[] valueBytes)
+ {
+ if (key.EndsWith(BinaryHeaderSuffix))
+ {
+ return new Entry(key, null, valueBytes);
+ }
+ return new Entry(key, Encoding.GetString(valueBytes), null);
+ }
+
+ private static string NormalizeKey(string key)
+ {
+ return Preconditions.CheckNotNull(key, "key").ToLower();
}
}
}
diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs
index 4c208b4a26..4c53285893 100644
--- a/src/csharp/Grpc.Core/Method.cs
+++ b/src/csharp/Grpc.Core/Method.cs
@@ -55,9 +55,36 @@ namespace Grpc.Core
}
/// <summary>
+ /// A non-generic representation of a remote method.
+ /// </summary>
+ public interface IMethod
+ {
+ /// <summary>
+ /// Gets the type of the method.
+ /// </summary>
+ MethodType Type { get; }
+
+ /// <summary>
+ /// Gets the name of the service to which this method belongs.
+ /// </summary>
+ string ServiceName { get; }
+
+ /// <summary>
+ /// Gets the unqualified name of the method.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets the fully qualified name of the method. On the server side, methods are dispatched
+ /// based on this name.
+ /// </summary>
+ string FullName { get; }
+ }
+
+ /// <summary>
/// A description of a remote method.
/// </summary>
- public class Method<TRequest, TResponse>
+ public class Method<TRequest, TResponse> : IMethod
{
readonly MethodType type;
readonly string serviceName;
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index c76f126026..28f1686e20 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -50,6 +50,8 @@ namespace Grpc.Core
{
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>();
+ readonly AtomicCounter activeCallCounter = new AtomicCounter();
+
readonly ServiceDefinitionCollection serviceDefinitions;
readonly ServerPortCollection ports;
readonly GrpcEnvironment environment;
@@ -73,7 +75,7 @@ namespace Grpc.Core
{
this.serviceDefinitions = new ServiceDefinitionCollection(this);
this.ports = new ServerPortCollection(this);
- this.environment = GrpcEnvironment.GetInstance();
+ this.environment = GrpcEnvironment.AddRef();
this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options))
{
@@ -106,6 +108,17 @@ namespace Grpc.Core
}
/// <summary>
+ /// To allow awaiting termination of the server.
+ /// </summary>
+ public Task ShutdownTask
+ {
+ get
+ {
+ return shutdownTcs.Task;
+ }
+ }
+
+ /// <summary>
/// Starts the server.
/// </summary>
public void Start()
@@ -136,18 +149,9 @@ namespace Grpc.Core
handle.ShutdownAndNotify(HandleServerShutdown, environment);
await shutdownTcs.Task;
- handle.Dispose();
- }
+ DisposeHandle();
- /// <summary>
- /// To allow awaiting termination of the server.
- /// </summary>
- public Task ShutdownTask
- {
- get
- {
- return shutdownTcs.Task;
- }
+ await Task.Run(() => GrpcEnvironment.Release());
}
/// <summary>
@@ -166,7 +170,22 @@ namespace Grpc.Core
handle.ShutdownAndNotify(HandleServerShutdown, environment);
handle.CancelAllCalls();
await shutdownTcs.Task;
- handle.Dispose();
+ DisposeHandle();
+ }
+
+ internal void AddCallReference(object call)
+ {
+ activeCallCounter.Increment();
+
+ bool success = false;
+ handle.DangerousAddRef(ref success);
+ Preconditions.CheckState(success);
+ }
+
+ internal void RemoveCallReference(object call)
+ {
+ handle.DangerousRelease();
+ activeCallCounter.Decrement();
}
/// <summary>
@@ -227,6 +246,16 @@ namespace Grpc.Core
}
}
+ private void DisposeHandle()
+ {
+ var activeCallCount = activeCallCounter.Count;
+ if (activeCallCount > 0)
+ {
+ Logger.Warning("Server shutdown has finished but there are still {0} active calls for that server.", activeCallCount);
+ }
+ handle.Dispose();
+ }
+
/// <summary>
/// Selects corresponding handler for given call and handles the call.
/// </summary>
@@ -254,7 +283,7 @@ namespace Grpc.Core
{
if (success)
{
- ServerRpcNew newRpc = ctx.GetServerRpcNew();
+ ServerRpcNew newRpc = ctx.GetServerRpcNew(this);
// after server shutdown, the callback returns with null call
if (!newRpc.Call.IsInvalid)
diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
index f9839d99f1..abd95cb905 100644
--- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs
+++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
@@ -39,23 +39,21 @@ namespace math
{
public static void Main(string[] args)
{
- using (Channel channel = new Channel("127.0.0.1", 23456, Credentials.Insecure))
- {
- Math.IMathClient client = new Math.MathClient(channel);
- MathExamples.DivExample(client);
+ var channel = new Channel("127.0.0.1", 23456, Credentials.Insecure);
+ Math.IMathClient client = new Math.MathClient(channel);
+ MathExamples.DivExample(client);
- MathExamples.DivAsyncExample(client).Wait();
+ MathExamples.DivAsyncExample(client).Wait();
- MathExamples.FibExample(client).Wait();
+ MathExamples.FibExample(client).Wait();
- MathExamples.SumExample(client).Wait();
+ MathExamples.SumExample(client).Wait();
- MathExamples.DivManyExample(client).Wait();
+ MathExamples.DivManyExample(client).Wait();
- MathExamples.DependendRequestsExample(client).Wait();
- }
+ MathExamples.DependendRequestsExample(client).Wait();
- GrpcEnvironment.Shutdown();
+ channel.ShutdownAsync().Wait();
}
}
}
diff --git a/src/csharp/Grpc.Examples.MathServer/MathServer.cs b/src/csharp/Grpc.Examples.MathServer/MathServer.cs
index 5f7e717b0c..26bef646ec 100644
--- a/src/csharp/Grpc.Examples.MathServer/MathServer.cs
+++ b/src/csharp/Grpc.Examples.MathServer/MathServer.cs
@@ -56,7 +56,6 @@ namespace math
Console.ReadKey();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
}
}
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index fdef950f09..36c1c947bd 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -68,9 +68,8 @@ namespace math.Tests
[TestFixtureTearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
[Test]
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
index 024377e216..80c35fb197 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
@@ -71,10 +71,9 @@ namespace Grpc.HealthCheck.Tests
[TestFixtureTearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
[Test]
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 385ca92086..24c22273fb 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -37,13 +37,15 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Google.Apis.Auth.OAuth2;
using Google.ProtocolBuffers;
+
using grpc.testing;
using Grpc.Auth;
using Grpc.Core;
using Grpc.Core.Utils;
+
using NUnit.Framework;
-using Google.Apis.Auth.OAuth2;
namespace Grpc.IntegrationTesting
{
@@ -118,12 +120,10 @@ namespace Grpc.IntegrationTesting
};
}
- using (Channel channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions))
- {
- TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
- await RunTestCaseAsync(options.testCase, client);
- }
- GrpcEnvironment.Shutdown();
+ var channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions);
+ TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
+ await RunTestCaseAsync(options.testCase, client);
+ channel.ShutdownAsync().Wait();
}
private async Task RunTestCaseAsync(string testCase, TestService.TestServiceClient client)
@@ -169,6 +169,9 @@ namespace Grpc.IntegrationTesting
case "cancel_after_first_response":
await RunCancelAfterFirstResponseAsync(client);
break;
+ case "timeout_on_sleeping_server":
+ await RunTimeoutOnSleepingServerAsync(client);
+ break;
case "benchmark_empty_unary":
RunBenchmarkEmptyUnary(client);
break;
@@ -308,7 +311,7 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("running service_account_creds");
var credential = await GoogleCredential.GetApplicationDefaultAsync();
credential = credential.CreateScoped(new[] { AuthScope });
- client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+ client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
var request = SimpleRequest.CreateBuilder()
.SetResponseType(PayloadType.COMPRESSABLE)
@@ -332,7 +335,7 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("running compute_engine_creds");
var credential = await GoogleCredential.GetApplicationDefaultAsync();
Assert.IsFalse(credential.IsCreateScopedRequired);
- client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+ client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
var request = SimpleRequest.CreateBuilder()
.SetResponseType(PayloadType.COMPRESSABLE)
@@ -357,7 +360,7 @@ namespace Grpc.IntegrationTesting
var credential = await GoogleCredential.GetApplicationDefaultAsync();
// check this a credential with scope support, but don't add the scope.
Assert.IsTrue(credential.IsCreateScopedRequired);
- client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+ client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
var request = SimpleRequest.CreateBuilder()
.SetResponseType(PayloadType.COMPRESSABLE)
@@ -381,7 +384,7 @@ namespace Grpc.IntegrationTesting
ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
string oauth2Token = await credential.GetAccessTokenForRequestAsync();
- client.HeaderInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token);
+ client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token);
var request = SimpleRequest.CreateBuilder()
.SetFillUsername(true)
@@ -401,7 +404,7 @@ namespace Grpc.IntegrationTesting
ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
string oauth2Token = await credential.GetAccessTokenForRequestAsync();
- var headerInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token);
+ var headerInterceptor = AuthInterceptors.FromAccessToken(oauth2Token);
var request = SimpleRequest.CreateBuilder()
.SetFillUsername(true)
@@ -409,7 +412,7 @@ namespace Grpc.IntegrationTesting
.Build();
var headers = new Metadata();
- headerInterceptor("", headers);
+ headerInterceptor(null, "", headers);
var response = client.UnaryCall(request, headers: headers);
Assert.AreEqual(AuthScopeResponse, response.OauthScope);
@@ -458,6 +461,29 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!");
}
+ public static async Task RunTimeoutOnSleepingServerAsync(TestService.ITestServiceClient client)
+ {
+ Console.WriteLine("running timeout_on_sleeping_server");
+
+ var deadline = DateTime.UtcNow.AddMilliseconds(1);
+ using (var call = client.FullDuplexCall(deadline: deadline))
+ {
+ try
+ {
+ await call.RequestStream.WriteAsync(StreamingOutputCallRequest.CreateBuilder()
+ .SetPayload(CreateZerosPayload(27182)).Build());
+ }
+ catch (InvalidOperationException)
+ {
+ // Deadline was reached before write has started. Eat the exception and continue.
+ }
+
+ var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+ Assert.AreEqual(StatusCode.DeadlineExceeded, ex.Status.StatusCode);
+ }
+ Console.WriteLine("Passed!");
+ }
+
// This is not an official interop test, but it's useful.
public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client)
{
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 6fa721bc1c..f3158aeb45 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -75,9 +75,8 @@ namespace Grpc.IntegrationTesting
[TestFixtureTearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
[Test]
@@ -127,5 +126,11 @@ namespace Grpc.IntegrationTesting
{
await InteropClient.RunCancelAfterFirstResponseAsync(client);
}
+
+ [Test]
+ public async Task TimeoutOnSleepingServerAsync()
+ {
+ await InteropClient.RunTimeoutOnSleepingServerAsync(client);
+ }
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
index 504fd11857..0cc8b2cde1 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
@@ -107,8 +107,6 @@ namespace Grpc.IntegrationTesting
server.Start();
server.ShutdownTask.Wait();
-
- GrpcEnvironment.Shutdown();
}
private static ServerOptions ParseArguments(string[] args)
diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
index 1c398eb84e..842795374f 100644
--- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
@@ -85,9 +85,8 @@ namespace Grpc.IntegrationTesting
[TestFixtureTearDown]
public void Cleanup()
{
- channel.Dispose();
+ channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
- GrpcEnvironment.Shutdown();
}
[Test]