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)
            };
#else
            var startInfo = new ProcessStartInfo
            {
                FileName               = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"),
                Arguments              = $"\"{filePath}\" " + args,
                UseShellExecute        = false,
                RedirectStandardError  = false,
                RedirectStandardInput  = true,
                RedirectStandardOutput = true,
                StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
            };
#endif
            var process = new Process();

            string pluginId = Plugin.CreateNewId();

            // 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 onExited = null;

            onExited = (object eventSender, EventArgs e) =>
            {
                process.Exited -= onExited;

                int?processId = GetProcessIdOrNull(process);

                OnPluginProcessExited(eventSender, e, pluginId, processId);
            };

            process.Exited += onExited;

            var pluginProcess = new PluginProcess(process);

            process.StartInfo = startInfo;

            process.Start();

            if (_logger.IsEnabled)
            {
                WriteCommonLogMessages(_logger);
            }

            var sender            = new Sender(process.StandardInput);
            var receiver          = new StandardOutputReceiver(pluginProcess);
            var messageDispatcher = new MessageDispatcher(requestHandlers, new RequestIdGenerator(), _logger);
            var connection        = new Connection(messageDispatcher, sender, receiver, options, _logger);

            var plugin = new Plugin(
                filePath,
                connection,
                pluginProcess,
                isOwnProcess: false,
                idleTimeout: _pluginIdleTimeout,
                id: pluginId);

            if (_logger.IsEnabled)
            {
                int?processId = GetProcessIdOrNull(process);

                _logger.Write(new PluginInstanceLogMessage(_logger.Now, plugin.Id, PluginState.Started, processId));
            }

            try
            {
                // Wire up handlers before calling ConnectAsync(...).
                RegisterEventHandlers(plugin);

                await connection.ConnectAsync(sessionCancellationToken);

                process.EnableRaisingEvents = true;
            }
            catch (ProtocolException ex)
            {
                throw new ProtocolException(
                          string.Format(CultureInfo.CurrentCulture, Strings.Plugin_Exception, plugin.Name, ex.Message));
            }
            catch (Exception)
            {
                plugin.Dispose();

                throw;
            }

            return(plugin);
        }
Exemple #2
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"),
                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 messageDispatcher = new MessageDispatcher(requestHandlers, new RequestIdGenerator(), _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);
            }
        }