Merge pull request #23858 from jtattermusch/csharp_fix18100

Fix C# server start when not all ports have been bound.
pull/23820/head
Jan Tattermusch 5 years ago committed by GitHub
commit f154cac6c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 90
      src/csharp/Grpc.Core.Tests/ServerBindFailedTest.cs
  2. 16
      src/csharp/Grpc.Core/Server.cs
  3. 1
      src/csharp/tests.json

@ -0,0 +1,90 @@
#region Copyright notice and license
// Copyright 2020 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 System;
using System.Threading.Tasks;
using System.IO;
using System.Linq;
using Grpc.Core;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
using NUnit.Framework;
namespace Grpc.Core.Tests
{
public class ServerBindFailedTest
{
Method<string, string> UnimplementedMethod = new Method<string, string>(
MethodType.Unary,
"FooService",
"SomeNonExistentMethod",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
// https://github.com/grpc/grpc/issues/18100
[Test]
public async Task Issue18100()
{
var server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) });
// this port will successfully bind
int successfullyBoundPort = server.Ports.Add(new ServerPort("localhost", ServerPort.PickUnused, ServerCredentials.Insecure));
Assert.AreNotEqual(0, successfullyBoundPort);
// use bad ssl server credentials so this port is guaranteed to fail to bind
Assert.AreEqual(0, server.Ports.Add(new ServerPort("localhost", ServerPort.PickUnused, MakeBadSslServerCredentials())));
try
{
server.Start();
}
catch (IOException ex)
{
// eat the expected "Failed to bind port" exception.
Console.Error.WriteLine($"Ignoring expected exception when starting the server: {ex}");
}
// Create a channel to the port that has been bound successfully
var channel = new Channel("localhost", successfullyBoundPort, ChannelCredentials.Insecure);
var callDeadline = DateTime.UtcNow.AddSeconds(5); // set deadline to make sure we fail quickly if the server doesn't respond
// call a method that's not implemented on the server.
var call = Calls.AsyncUnaryCall(new CallInvocationDetails<string, string>(channel, UnimplementedMethod, new CallOptions(deadline: callDeadline)), "someRequest");
try
{
await call;
Assert.Fail("the call should have failed.");
}
catch (RpcException)
{
// We called a nonexistent method. A healthy server should immediately respond with StatusCode.Unimplemented
Assert.AreEqual(StatusCode.Unimplemented, call.GetStatus().StatusCode);
}
await channel.ShutdownAsync();
await server.ShutdownAsync();
}
private static SslServerCredentials MakeBadSslServerCredentials()
{
var serverCert = new[] { new KeyCertificatePair("this is a bad certificate chain", "this is a bad private key") };
return new SslServerCredentials(serverCert, "this is a bad root set", forceClientAuth: false);
}
}
}

@ -141,7 +141,9 @@ namespace Grpc.Core
/// <summary>
/// Starts the server.
/// Throws <c>IOException</c> if not successful.
/// Throws <c>IOException</c> if not all ports have been bound successfully (see <c>Ports.Add</c> method).
/// Even if some of that ports haven't been bound, the server will still serve normally on all ports that have been
/// bound successfully (and the user is expected to shutdown the server by invoking <c>ShutdownAsync</c> or <c>KillAsync</c>).
/// </summary>
public void Start()
{
@ -151,7 +153,6 @@ namespace Grpc.Core
GrpcPreconditions.CheckState(!shutdownRequested);
startRequested = true;
CheckPortsBoundSuccessfully();
handle.Start();
for (int i = 0; i < requestCallTokensPerCq; i++)
@ -161,6 +162,13 @@ namespace Grpc.Core
AllowOneRpc(cq);
}
}
// Throw if some ports weren't bound successfully.
// Even when that happens, some server ports might have been
// bound successfully, so we let server initialization
// proceed as usual and we only throw at the very end of the
// Start() method.
CheckPortsBoundSuccessfully();
}
}
@ -443,7 +451,7 @@ namespace Grpc.Core
/// <summary>
/// Adds a new port on which server should listen.
/// Only call this before Start().
/// <returns>The port on which server will be listening.</returns>
/// <returns>The port on which server will be listening. Return value of zero means that binding the port has failed.</returns>
/// </summary>
public int Add(ServerPort serverPort)
{
@ -452,7 +460,7 @@ namespace Grpc.Core
/// <summary>
/// Adds a new port on which server should listen.
/// <returns>The port on which server will be listening.</returns>
/// <returns>The port on which server will be listening. Return value of zero means that binding the port has failed.</returns>
/// </summary>
/// <param name="host">the host</param>
/// <param name="port">the port. If zero, an unused port is chosen automatically.</param>

@ -41,6 +41,7 @@
"Grpc.Core.Tests.PInvokeTest",
"Grpc.Core.Tests.ResponseHeadersTest",
"Grpc.Core.Tests.SanityTest",
"Grpc.Core.Tests.ServerBindFailedTest",
"Grpc.Core.Tests.ServerTest",
"Grpc.Core.Tests.ShutdownHookClientTest",
"Grpc.Core.Tests.ShutdownHookPendingCallTest",

Loading…
Cancel
Save