#region Copyright notice and license
// Copyright 2015 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Logging;
using Grpc.Core.Utils;
namespace Grpc.Core
{
///
/// gRPC server. A single server can server arbitrary number of services and can listen on more than one ports.
///
public class Server
{
const int DefaultRequestCallTokensPerCq = 2000;
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType();
readonly AtomicCounter activeCallCounter = new AtomicCounter();
readonly ServiceDefinitionCollection serviceDefinitions;
readonly ServerPortCollection ports;
readonly GrpcEnvironment environment;
readonly List options;
readonly ServerSafeHandle handle;
readonly object myLock = new object();
readonly List serviceDefinitionsList = new List();
readonly List serverPortList = new List();
readonly Dictionary callHandlers = new Dictionary();
readonly TaskCompletionSource shutdownTcs = new TaskCompletionSource();
bool startRequested;
volatile bool shutdownRequested;
int requestCallTokensPerCq = DefaultRequestCallTokensPerCq;
///
/// Creates a new server.
///
public Server() : this(null)
{
}
///
/// Creates a new server.
///
/// Channel options.
public Server(IEnumerable options)
{
this.serviceDefinitions = new ServiceDefinitionCollection(this);
this.ports = new ServerPortCollection(this);
this.environment = GrpcEnvironment.AddRef();
this.options = options != null ? new List(options) : new List();
using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options))
{
this.handle = ServerSafeHandle.NewServer(channelArgs);
}
foreach (var cq in environment.CompletionQueues)
{
this.handle.RegisterCompletionQueue(cq);
}
GrpcEnvironment.RegisterServer(this);
}
///
/// Services that will be exported by the server once started. Register a service with this
/// server by adding its definition to this collection.
///
public ServiceDefinitionCollection Services
{
get
{
return serviceDefinitions;
}
}
///
/// Ports on which the server will listen once started. Register a port with this
/// server by adding its definition to this collection.
///
public ServerPortCollection Ports
{
get
{
return ports;
}
}
///
/// To allow awaiting termination of the server.
///
public Task ShutdownTask
{
get
{
return shutdownTcs.Task;
}
}
///
/// Experimental API. Might anytime change without prior notice.
/// Number or calls requested via grpc_server_request_call at any given time for each completion queue.
///
public int RequestCallTokensPerCompletionQueue
{
get
{
return requestCallTokensPerCq;
}
set
{
lock (myLock)
{
GrpcPreconditions.CheckState(!startRequested);
GrpcPreconditions.CheckArgument(value > 0);
requestCallTokensPerCq = value;
}
}
}
///
/// Starts the server.
/// Throws IOException if not successful.
///
public void Start()
{
lock (myLock)
{
GrpcPreconditions.CheckState(!startRequested);
GrpcPreconditions.CheckState(!shutdownRequested);
startRequested = true;
CheckPortsBoundSuccessfully();
handle.Start();
for (int i = 0; i < requestCallTokensPerCq; i++)
{
foreach (var cq in environment.CompletionQueues)
{
AllowOneRpc(cq);
}
}
}
}
///
/// Requests server shutdown and when there are no more calls being serviced,
/// cleans up used resources. The returned task finishes when shutdown procedure
/// is complete.
///
///
/// It is strongly recommended to shutdown all previously created servers before exiting from the process.
///
public Task ShutdownAsync()
{
return ShutdownInternalAsync(false);
}
///
/// Requests server shutdown while cancelling all the in-progress calls.
/// The returned task finishes when shutdown procedure is complete.
///
///
/// It is strongly recommended to shutdown all previously created servers before exiting from the process.
///
public Task KillAsync()
{
return ShutdownInternalAsync(true);
}
internal void AddCallReference(object call)
{
activeCallCounter.Increment();
bool success = false;
handle.DangerousAddRef(ref success);
GrpcPreconditions.CheckState(success);
}
internal void RemoveCallReference(object call)
{
handle.DangerousRelease();
activeCallCounter.Decrement();
}
///
/// Shuts down the server.
///
private async Task ShutdownInternalAsync(bool kill)
{
lock (myLock)
{
GrpcPreconditions.CheckState(!shutdownRequested);
shutdownRequested = true;
}
GrpcEnvironment.UnregisterServer(this);
var cq = environment.CompletionQueues.First(); // any cq will do
handle.ShutdownAndNotify(HandleServerShutdown, cq);
if (kill)
{
handle.CancelAllCalls();
}
await ShutdownCompleteOrEnvironmentDeadAsync().ConfigureAwait(false);
DisposeHandle();
await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false);
}
///
/// In case the environment's threadpool becomes dead, the shutdown completion will
/// never be delivered, but we need to release the environment's handle anyway.
///
private async Task ShutdownCompleteOrEnvironmentDeadAsync()
{
while (true)
{
var task = await Task.WhenAny(shutdownTcs.Task, Task.Delay(20)).ConfigureAwait(false);
if (shutdownTcs.Task == task)
{
return;
}
if (!environment.IsAlive)
{
return;
}
}
}
///
/// Adds a service definition.
///
private void AddServiceDefinitionInternal(ServerServiceDefinition serviceDefinition)
{
lock (myLock)
{
GrpcPreconditions.CheckState(!startRequested);
foreach (var entry in serviceDefinition.CallHandlers)
{
callHandlers.Add(entry.Key, entry.Value);
}
serviceDefinitionsList.Add(serviceDefinition);
}
}
///
/// Adds a listening port.
///
private int AddPortInternal(ServerPort serverPort)
{
lock (myLock)
{
GrpcPreconditions.CheckNotNull(serverPort.Credentials, "serverPort");
GrpcPreconditions.CheckState(!startRequested);
var address = string.Format("{0}:{1}", serverPort.Host, serverPort.Port);
int boundPort;
using (var nativeCredentials = serverPort.Credentials.ToNativeCredentials())
{
if (nativeCredentials != null)
{
boundPort = handle.AddSecurePort(address, nativeCredentials);
}
else
{
boundPort = handle.AddInsecurePort(address);
}
}
var newServerPort = new ServerPort(serverPort, boundPort);
this.serverPortList.Add(newServerPort);
return boundPort;
}
}
///
/// Allows one new RPC call to be received by server.
///
private void AllowOneRpc(CompletionQueueSafeHandle cq)
{
if (!shutdownRequested)
{
handle.RequestCall((success, ctx) => HandleNewServerRpc(success, ctx, cq), cq);
}
}
///
/// Checks that all ports have been bound successfully.
///
private void CheckPortsBoundSuccessfully()
{
lock (myLock)
{
var unboundPort = ports.FirstOrDefault(port => port.BoundPort == 0);
if (unboundPort != null)
{
throw new IOException(
string.Format("Failed to bind port \"{0}:{1}\"", unboundPort.Host, unboundPort.Port));
}
}
}
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();
}
///
/// Selects corresponding handler for given call and handles the call.
///
private async Task HandleCallAsync(ServerRpcNew newRpc, CompletionQueueSafeHandle cq, Action continuation)
{
try
{
IServerCallHandler callHandler;
if (!callHandlers.TryGetValue(newRpc.Method, out callHandler))
{
callHandler = UnimplementedMethodCallHandler.Instance;
}
await callHandler.HandleCall(newRpc, cq).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.Warning(e, "Exception while handling RPC.");
}
finally
{
continuation();
}
}
///
/// Handles the native callback.
///
private void HandleNewServerRpc(bool success, RequestCallContextSafeHandle ctx, CompletionQueueSafeHandle cq)
{
bool nextRpcRequested = false;
if (success)
{
var newRpc = ctx.GetServerRpcNew(this);
// after server shutdown, the callback returns with null call
if (!newRpc.Call.IsInvalid)
{
nextRpcRequested = true;
// Start asynchronous handler for the call.
// Don't await, the continuations will run on gRPC thread pool once triggered
// by cq.Next().
#pragma warning disable 4014
HandleCallAsync(newRpc, cq, () => AllowOneRpc(cq));
#pragma warning restore 4014
}
}
if (!nextRpcRequested)
{
AllowOneRpc(cq);
}
}
///
/// Handles native callback.
///
private void HandleServerShutdown(bool success, BatchContextSafeHandle ctx)
{
shutdownTcs.SetResult(null);
}
///
/// Collection of service definitions.
///
public class ServiceDefinitionCollection : IEnumerable
{
readonly Server server;
internal ServiceDefinitionCollection(Server server)
{
this.server = server;
}
///
/// Adds a service definition to the server. This is how you register
/// handlers for a service with the server. Only call this before Start().
///
public void Add(ServerServiceDefinition serviceDefinition)
{
server.AddServiceDefinitionInternal(serviceDefinition);
}
///
/// Gets enumerator for this collection.
///
public IEnumerator GetEnumerator()
{
return server.serviceDefinitionsList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return server.serviceDefinitionsList.GetEnumerator();
}
}
///
/// Collection of server ports.
///
public class ServerPortCollection : IEnumerable
{
readonly Server server;
internal ServerPortCollection(Server server)
{
this.server = server;
}
///
/// Adds a new port on which server should listen.
/// Only call this before Start().
/// The port on which server will be listening.
///
public int Add(ServerPort serverPort)
{
return server.AddPortInternal(serverPort);
}
///
/// Adds a new port on which server should listen.
/// The port on which server will be listening.
///
/// the host
/// the port. If zero, an unused port is chosen automatically.
/// credentials to use to secure this port.
public int Add(string host, int port, ServerCredentials credentials)
{
return Add(new ServerPort(host, port, credentials));
}
///
/// Gets enumerator for this collection.
///
public IEnumerator GetEnumerator()
{
return server.serverPortList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return server.serverPortList.GetEnumerator();
}
}
}
}