/// <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;
        }
Beispiel #3
0
        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);
            }
        }