/// <summary> /// Instantiates a new <see cref="MessageDispatcher" /> class. /// </summary> /// <param name="requestHandlers">Request handlers.</param> /// <param name="idGenerator">A unique identifier generator.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="requestHandlers" /> /// is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="idGenerator" /> /// is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="inboundRequestProcessingHandler" /> /// is <c>null</c>.</exception> /// /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> /// is <c>null</c>.</exception> internal MessageDispatcher(IRequestHandlers requestHandlers, IIdGenerator idGenerator, InboundRequestProcessingHandler inboundRequestProcessingHandler, IPluginLogger logger) { if (requestHandlers == null) { throw new ArgumentNullException(nameof(requestHandlers)); } if (idGenerator == null) { throw new ArgumentNullException(nameof(idGenerator)); } if (inboundRequestProcessingHandler == null) { throw new ArgumentNullException(nameof(inboundRequestProcessingHandler)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } RequestHandlers = requestHandlers; _idGenerator = idGenerator; _logger = logger; _inboundRequestContexts = new ConcurrentDictionary <string, InboundRequestContext>(); _outboundRequestContexts = new ConcurrentDictionary <string, OutboundRequestContext>(); _inboundRequestProcessingContext = inboundRequestProcessingHandler; }
/// <summary> /// Initializes a new <see cref="InboundRequestContext" /> class. /// </summary> /// <param name="connection">A connection.</param> /// <param name="requestId">A request ID.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <param name="logger">A plugin logger.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="connection" /> /// is <c>null</c>.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="requestId" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="inboundRequestProcessingHandler" /> /// is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> /// is <c>null</c>.</exception> internal InboundRequestContext( IConnection connection, string requestId, CancellationToken cancellationToken, InboundRequestProcessingHandler inboundRequestProcessingHandler, IPluginLogger logger) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (string.IsNullOrEmpty(requestId)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(requestId)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (inboundRequestProcessingHandler == null) { throw new ArgumentNullException(nameof(inboundRequestProcessingHandler)); } _connection = connection; RequestId = requestId; _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); // Capture the cancellation token now because if the cancellation token source // is disposed race conditions may cause an exception acccessing its Token property. _cancellationToken = _cancellationTokenSource.Token; _logger = logger; _inboundRequestProcessingHandler = inboundRequestProcessingHandler; }
private async Task <IPlugin> CreatePluginAsync( string filePath, IEnumerable <string> arguments, IRequestHandlers requestHandlers, ConnectionOptions options, CancellationToken sessionCancellationToken) { var args = string.Join(" ", arguments); #if IS_DESKTOP var startInfo = new ProcessStartInfo(filePath) { Arguments = args, UseShellExecute = false, RedirectStandardError = false, RedirectStandardInput = true, RedirectStandardOutput = true, StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), WindowStyle = ProcessWindowStyle.Hidden, }; #else var startInfo = new ProcessStartInfo { FileName = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? (NuGet.Common.RuntimeEnvironmentHelper.IsWindows ? "dotnet.exe" : "dotnet"), Arguments = $"\"{filePath}\" " + args, UseShellExecute = false, RedirectStandardError = false, RedirectStandardInput = true, RedirectStandardOutput = true, StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), WindowStyle = ProcessWindowStyle.Hidden, }; #endif var pluginProcess = new PluginProcess(startInfo); string pluginId = Plugin.CreateNewId(); using (var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(sessionCancellationToken)) { // Process ID is unavailable until we start the process; however, we want to wire up this event before // attempting to start the process in case the process immediately exits. EventHandler <IPluginProcess> onExited = null; Connection connection = null; onExited = (object eventSender, IPluginProcess exitedProcess) => { exitedProcess.Exited -= onExited; OnPluginProcessExited(eventSender, exitedProcess, pluginId); if (connection?.State == ConnectionState.Handshaking) { combinedCancellationTokenSource.Cancel(); } }; pluginProcess.Exited += onExited; var stopwatch = Stopwatch.StartNew(); pluginProcess.Start(); if (_logger.IsEnabled) { WriteCommonLogMessages(_logger); } var sender = new Sender(pluginProcess.StandardInput); var receiver = new StandardOutputReceiver(pluginProcess); var processingHandler = new InboundRequestProcessingHandler(new HashSet <MessageMethod> { MessageMethod.Handshake, MessageMethod.Log }); var messageDispatcher = new MessageDispatcher(requestHandlers, new RequestIdGenerator(), processingHandler, _logger); connection = new Connection(messageDispatcher, sender, receiver, options, _logger); var plugin = new Plugin( filePath, connection, pluginProcess, isOwnProcess: false, idleTimeout: _pluginIdleTimeout, id: pluginId); if (_logger.IsEnabled) { _logger.Write(new PluginInstanceLogMessage(_logger.Now, plugin.Id, PluginState.Started, pluginProcess.Id)); } try { // Wire up handlers before calling ConnectAsync(...). plugin.Faulted += OnPluginFaulted; plugin.Idle += OnPluginIdle; await connection.ConnectAsync(combinedCancellationTokenSource.Token); // It's critical that this be registered after ConnectAsync(...). // If it's registered before, stuff could be disposed, which would lead to unexpected exceptions. // If the plugin process exited before this event is registered, an exception should be caught below. plugin.Exited += OnPluginExited; } catch (ProtocolException ex) { plugin.Dispose(); throw new ProtocolException( string.Format(CultureInfo.CurrentCulture, Strings.Plugin_Exception, plugin.Name, ex.Message)); } catch (TaskCanceledException ex) { stopwatch.Stop(); plugin.Dispose(); throw new PluginException( string.Format( CultureInfo.CurrentCulture, Strings.Plugin_FailedOnCreation, plugin.Name, stopwatch.Elapsed.TotalSeconds, pluginProcess.ExitCode), ex); } catch (Exception) { plugin.Dispose(); throw; } return(plugin); } }