// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.DataProtocol.Contracts; using Microsoft.SqlTools.DataProtocol.Contracts.ServerCapabilities; using Microsoft.SqlTools.Hosting.Channels; using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Contracts.Internal; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Utility; namespace Microsoft.SqlTools.Hosting { public class ServiceHost : IServiceHost { private const int DefaultShutdownTimeoutSeconds = 120; #region Fields private int? shutdownTimeoutSeconds; internal readonly List> initCallbacks; internal readonly List> shutdownCallbacks; internal IJsonRpcHost jsonRpcHost; #endregion #region Construction /// /// Base constructor /// internal ServiceHost() { shutdownCallbacks = new List>(); initCallbacks = new List>(); } /// /// Constructs a new service host that with ability to provide custom protocol channels /// /// Channel to use for JSON RPC input/output public ServiceHost(ChannelBase protocolChannel) : this() { Validate.IsNotNull(nameof(protocolChannel), protocolChannel); jsonRpcHost = new JsonRpcHost(protocolChannel); // Register any request that the service host will handle SetEventHandler(ExitNotification.Type, HandleExitNotification, true); SetAsyncRequestHandler(ShutdownRequest.Type, HandleShutdownRequest, true); SetAsyncRequestHandler(InitializeRequest.Type, HandleInitializeRequest, true); } /// /// Constructs a new service host intended to be used as a JSON RPC server. StdIn is used /// for receiving messages, StdOut is used for sending messages. /// /// Service host as a JSON RPC server over StdI/O public static ServiceHost CreateDefaultServer() { return new ServiceHost(new StdioServerChannel()); } #endregion #region Properties public int ShutdownTimeoutSeconds { get => shutdownTimeoutSeconds ?? DefaultShutdownTimeoutSeconds; set => shutdownTimeoutSeconds = value; } public InitializeResponse InitializeResponse { get; set; } #endregion #region IServiceHost Implementations public void RegisterShutdownTask(Func shutdownCallback) { Validate.IsNotNull(nameof(shutdownCallback), shutdownCallback); shutdownCallbacks.Add(shutdownCallback); } public void RegisterInitializeTask(Func initializeCallback) { Validate.IsNotNull(nameof(initializeCallback), initializeCallback); initCallbacks.Add(initializeCallback); } #endregion #region IJsonRpcHost Implementation public void SendEvent( EventType eventType, TParams eventParams) { jsonRpcHost.SendEvent(eventType, eventParams); } public Task SendRequest( RequestType requestType, TParams requestParams) { return jsonRpcHost.SendRequest(requestType, requestParams); } public void SetAsyncEventHandler( EventType eventType, Func eventHandler, bool overrideExisting) { jsonRpcHost.SetAsyncEventHandler(eventType, eventHandler, overrideExisting); } public void SetEventHandler( EventType eventType, Action eventHandler, bool overrideExisting) { jsonRpcHost.SetEventHandler(eventType, eventHandler, overrideExisting); } public void SetAsyncRequestHandler( RequestType requestType, Func, Task> requestHandler, bool overrideExisting) { jsonRpcHost.SetAsyncRequestHandler(requestType, requestHandler, overrideExisting); } public void SetRequestHandler( RequestType requestType, Action> requestHandler, bool overrideExisting) { jsonRpcHost.SetRequestHandler(requestType, requestHandler, overrideExisting); } public void Start() { // Start the host jsonRpcHost.Start(); } public void Stop() { jsonRpcHost.Stop(); } public void WaitForExit() { jsonRpcHost.WaitForExit(); } #endregion #region Request Handlers internal async Task HandleInitializeRequest(InitializeParameters initParams, RequestContext requestContext) { Logger.Write(TraceEventType.Information, "Service host received intialize request"); // Call all initialize methods provided by the service components IEnumerable initializeTasks = initCallbacks.Select(t => t(initParams, requestContext)); // Respond to initialize once all tasks are completed await Task.WhenAll(initializeTasks); if (InitializeResponse == null) { InitializeResponse = new InitializeResponse { Capabilities = new ServerCapabilities() }; } requestContext.SendResult(InitializeResponse); } internal void HandleExitNotification(object exitParams, EventContext eventContext) { // Stop the server channel Stop(); } internal async Task HandleShutdownRequest(object shutdownParams, RequestContext requestContext) { Logger.Write(TraceEventType.Information, "Service host received shutdown request"); // Call all the shutdown methods provided by the service components IEnumerable shutdownTasks = shutdownCallbacks.Select(t => t(shutdownParams, requestContext)); // Shutdown once all tasks are completed, or after the timeout expires, whichever comes first TimeSpan shutdownTimeout = TimeSpan.FromSeconds(ShutdownTimeoutSeconds); await Task.WhenAny(Task.WhenAll(shutdownTasks), Task.Delay(shutdownTimeout)); requestContext.SendResult(null); } #endregion } }