示例#1
0
        /// <summary>
        ///   Instantiate the PluginHost in a new AppDomain. The newly created AppDomain doesn't
        ///   load the PluginHost.exe assembly nor any other assembly than those on which the plugin
        ///   depends.
        /// </summary>
        /// <param name="args">The PluginHost start parameters</param>
        /// <param name="cts">The cancellation token to release the wait handle on the main thread</param>
        public static IDisposable Create(
            PluginHostParameters args,
            CancellationTokenSource cts)
        {
            Process pluginMgrProcess;

            if (args.AttachDebugger || File.Exists(Path.Combine(args.HomePath, "debugger")))
            {
                Debugger.Launch();
            }

            try
            {
                pluginMgrProcess = Process.GetProcessById(args.ManagerProcessId);
            }
            catch (Exception)
            {
                throw new PluginHostException(PluginHostConst.ExitParentExited);
            }

            return(Create(
                       args.PackageRootFolder,
                       args.PluginAndDependenciesAssembliesPath,
                       args.PluginHostTypeAssemblyName,
                       args.PluginHostTypeQualifiedName,
                       args.PackageName,
                       args.HomePath,
                       args.SessionGuid,
                       args.ChannelName,
                       pluginMgrProcess,
                       args.IsDevelopment,
                       cts));
        }
示例#2
0
        public async Task StartPlugin(PluginInstance pluginInstance)
        {
            var pluginPackage = pluginInstance.Package;
            var packageName   = pluginPackage.Id;

            try
            {
                using (await pluginInstance.Lock.LockAsync())
                {
                    if (pluginInstance.Status != PluginStatus.Stopped)
                    {
                        return;
                    }

                    if (CanPluginStartOrPause(pluginInstance) == false)
                    {
                        throw new InvalidOperationException("A plugin with the same Package name is already running");
                    }

                    OnPluginStarting(pluginInstance);
                }

                var cmdLineParams = new PluginHostParameters
                {
                    PackageName    = packageName,
                    HomePath       = pluginPackage.GetHomeDir().FullPath,
                    SessionString  = pluginInstance.Guid.ToString(),
                    SMAChannelName = IpcServerChannelName,
                    SMAProcessId   = System.Diagnostics.Process.GetCurrentProcess().Id,
                    IsDeveloment   = pluginInstance.Metadata.IsDevelopment,
                };

                var processArgs = Parser.Default.FormatCommandLine(cmdLineParams);

                var pluginStartInfo = new ProcessStartInfo(
                    SMAFileSystem.PluginHostExeFile.FullPath,
                    processArgs)
                {
                    UseShellExecute = false,
                };

                pluginInstance.Process = System.Diagnostics.Process.Start(pluginStartInfo);

                if (pluginInstance.Process == null)
                {
                    LogTo.Warning($"Failed to start process for {pluginInstance.Denomination} {packageName}");
                    return;
                }

                pluginInstance.Process.EnableRaisingEvents = true;
                pluginInstance.Process.Exited += async(o, e) =>
                {
                    using (await pluginInstance.Lock.LockAsync())
                        OnPluginStopped(pluginInstance);
                };

                if (await pluginInstance.ConnectedEvent.WaitAsync(PluginConnectTimeout))
                {
                    return;
                }

                if (pluginInstance.Status == PluginStatus.Stopped)
                {
                    LogTo.Error($"{pluginInstance.Denomination.CapitalizeFirst()} {packageName} stopped unexpectedly.");
                    pluginInstance.ConnectedEvent.Set();
                    return;
                }
            }
            catch (Exception ex)
            {
                LogTo.Error(ex, $"An error occured while starting {pluginInstance.Denomination} {packageName}");
                return;
            }

            try
            {
                LogTo.Warning(
                    $"{pluginInstance.Denomination.CapitalizeFirst()} {packageName} failed to connect under {PluginConnectTimeout}ms. Attempting to kill process");

                pluginInstance.Process.Refresh();

                if (pluginInstance.Process.HasExited)
                {
                    LogTo.Warning($"{pluginInstance.Denomination.CapitalizeFirst()} {packageName} has already exited");
                    return;
                }

                pluginInstance.Process.Kill();
            }
            catch (RemotingException ex)
            {
                LogTo.Warning(ex, $"StartPlugin '{pluginInstance.Denomination}' {packageName} failed.");
            }
            catch (Exception ex)
            {
                LogTo.Error(ex, $"An error occured while starting {pluginInstance.Denomination} {packageName}");
            }
            finally
            {
                using (await pluginInstance.Lock.LockAsync())
                    OnPluginStopped(pluginInstance);
            }
        }
        /// <summary>
        /// Start plugin <paramref name="pluginInstance"/>
        /// </summary>
        /// <param name="pluginInstance">The plugin to start</param>
        /// <param name="attachDebugger">Whether to attach the debugger to the plugin host on start</param>
        /// <returns>Success of operation</returns>
        public async Task <bool> StartPlugin(TPluginInstance pluginInstance, bool attachDebugger = false)
        {
            var pluginPackage = pluginInstance.Package;
            var packageName   = pluginPackage.Id;

            try
            {
                // Make sure the plugin is stopped and can be started
                using (await pluginInstance.Lock.LockAsync())
                {
                    if (pluginInstance.Status != PluginStatus.Stopped)
                    {
                        return(true);
                    }

                    if (CanPluginStartOrPause(pluginInstance) == false)
                    {
                        throw new InvalidOperationException("A plugin with the same Package name is already running");
                    }

                    OnPluginStarting(pluginInstance);
                }

                // Determine the plugin and its dependencies assemblies' information. This includes the assembly which contains the PluginHost type
                var packageRootFolder = Locations.PluginPackageDir.FullPathWin;
                var pluginAndDependenciesAssembliesPath = new List <string>();
                var pluginHostTypeAssemblyName          = GetPluginHostTypeAssemblyName(pluginInstance);
                var pluginHostTypeMinAssemblyVersion    = GetPluginHostTypeAssemblyMinimumVersion(pluginInstance);

                if (pluginInstance.IsDevelopment == false)
                {
                    using (await PMLock.ReaderLockAsync())
                    {
                        var pluginAndDependenciesPackageFilePaths = new List <FilePath>();
                        var pluginPkg = PackageManager.FindInstalledPluginById(packageName);

                        if (pluginPkg == null)
                        {
                            throw new InvalidOperationException($"Cannot find requested plugin package {packageName}");
                        }

                        PackageManager.GetInstalledPluginAssembliesFilePath(
                            pluginPkg.Identity,
                            out var tmpPluginAssemblies,
                            out var tmpDependenciesAssemblies);

                        pluginAndDependenciesPackageFilePaths.AddRange(tmpPluginAssemblies);
                        pluginAndDependenciesPackageFilePaths.AddRange(tmpDependenciesAssemblies);

                        var pluginHostTypeAssemblyPath =
                            pluginAndDependenciesPackageFilePaths.FirstOrDefault(a => a.FileNameWithoutExtension == pluginHostTypeAssemblyName);

                        if (pluginHostTypeAssemblyPath == null)
                        {
                            OnPluginStartFailed(
                                pluginInstance,
                                PluginStartFailure.InteropAssemblyNotFound,
                                $"{pluginInstance} failed to start: Unable to find the PluginHost type's \"{pluginHostTypeAssemblyName}\" dependency assembly's package");

                            return(false);
                        }

                        // Make sure the assembly version is equal or higher to the required minimum version
                        if (pluginHostTypeMinAssemblyVersion != null)
                        {
                            var pluginHostTypeAssemblyInfo = FileVersionInfo.GetVersionInfo(pluginHostTypeAssemblyPath.FullPath);

                            if (NuGetVersion.TryParse(pluginHostTypeAssemblyInfo.ProductVersion, out var pluginHostTypeAssemblyVersion) == false)
                            {
                                OnPluginStartFailed(
                                    pluginInstance,
                                    PluginStartFailure.InteropAssemblyNotFound,
                                    $"{pluginInstance} failed to start: Invalid interop version '{pluginHostTypeAssemblyInfo.ProductVersion}'");

                                return(false);
                            }

                            if (pluginHostTypeAssemblyVersion < pluginHostTypeMinAssemblyVersion)
                            {
                                OnPluginStartFailed(
                                    pluginInstance,
                                    PluginStartFailure.InteropAssemblyNotFound,
                                    $"{pluginInstance} failed to start: Outdated interop version '{pluginHostTypeAssemblyInfo.ProductVersion}'. Either update the plugin, downgrade SMA, or ask the plugin developer to publish a new version to fix the issue.");

                                return(false);
                            }
                        }

                        foreach (var pkgFilePath in pluginAndDependenciesPackageFilePaths)
                        {
                            string pkgRelativeFilePath = pkgFilePath.FullPathWin.After(packageRootFolder);

                            if (string.IsNullOrWhiteSpace(pkgRelativeFilePath))
                            {
                                LogTo.Warning(
                                    $"Package {pkgFilePath} isn't located underneath the package folder {packageRootFolder}. Skipping, this might cause issues with the plugin");
                                continue;
                            }

                            pluginAndDependenciesAssembliesPath.Add(pkgRelativeFilePath);
                        }
                    }
                }

                // Build command line
                var cmdLineParams = new PluginHostParameters
                {
                    PackageRootFolder = packageRootFolder,
                    PluginAndDependenciesAssembliesPath = string.Join(";", pluginAndDependenciesAssembliesPath),
                    PluginHostTypeAssemblyName          = pluginHostTypeAssemblyName,
                    PluginHostTypeQualifiedName         = GetPluginHostTypeQualifiedName(pluginInstance),
                    PackageName      = packageName,
                    HomePath         = pluginPackage.HomeDir.FullPath,
                    SessionString    = pluginInstance.Guid.ToString(),
                    ChannelName      = IpcServerChannelName,
                    ManagerProcessId = Process.GetCurrentProcess().Id,
                    IsDevelopment    = pluginInstance.IsDevelopment,
                    AttachDebugger   = attachDebugger
                };

                // Build process parameters
                var processArgs = Parser.Default.FormatCommandLine(cmdLineParams);

                pluginInstance.Process = new Process
                {
                    StartInfo = new ProcessStartInfo(
                        Locations.PluginHostExeFile.FullPath,
                        processArgs)
                    {
                        UseShellExecute        = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError  = true,
                    },
                    EnableRaisingEvents = true,
                };

                // Setup error output logging
                {
                    var           pluginStr        = pluginInstance.ToString(); // Avoids keeping a pointer to PluginInstance around
                    StringBuilder pluginErrBuilder = new StringBuilder();

                    void LogPluginErrorOutput()
                    {
                        if (string.IsNullOrWhiteSpace(pluginErrBuilder.ToString()))
                        {
                            return;
                        }

                        lock (pluginErrBuilder)
                        {
                            LogTo.Warning($"{pluginStr} standard error output:\n--------------------------------------------\n{pluginErrBuilder.ToString().Trim()}\n--------------------------------------------");
                            pluginErrBuilder.Clear();
                        }
                    }

                    DelayedTask logTask = new DelayedTask(LogPluginErrorOutput, 200);

                    void AggregatePluginErrorOutput(object _, DataReceivedEventArgs e)
                    {
                        lock (pluginErrBuilder)
                        {
                            pluginErrBuilder.AppendLine(e.Data);
                            logTask.Trigger(750);
                        }
                    }

                    pluginInstance.Process.ErrorDataReceived += AggregatePluginErrorOutput;
                }

                // Start plugin
                if (pluginInstance.Process.Start() == false)
                {
                    OnPluginStartFailed(
                        pluginInstance,
                        PluginStartFailure.ProcessDidNotStart,
                        $"{pluginInstance} failed to start: Failed to start process");

                    return(false);
                }

                OnPluginStarted(pluginInstance);

                pluginInstance.Process.EnableRaisingEvents = true;
                pluginInstance.Process.BeginErrorReadLine();
                pluginInstance.Process.Exited += (o, e) =>
                {
                    UISynchronizationContext.Post(_ =>
                    {
                        using (pluginInstance.Lock.Lock())
                            OnPluginStopped(pluginInstance);
                    }, null);
                };

                var connected = await pluginInstance.ConnectedEvent.WaitAsync(PluginConnectTimeout);

                if (connected && pluginInstance.Status == PluginStatus.Connected)
                {
                    return(true);
                }

                if (pluginInstance.Status == PluginStatus.Stopped)
                {
                    OnPluginStartFailed(
                        pluginInstance,
                        connected ? PluginStartFailure.ProcessDidNotConnect : PluginStartFailure.Unknown,
                        $"{pluginInstance} failed to start: process stopped unexpectedly.");

                    pluginInstance.ConnectedEvent.Set();

                    return(false);
                }
            }
            catch (Exception ex)
            {
                LogTo.Error(ex, $"{pluginInstance} failed to start: An unknown exception occured during startup");
                return(false);
            }

            try
            {
                LogTo.Warning(
                    $"{pluginInstance.ToString().CapitalizeFirst()} failed to connect under {PluginConnectTimeout}ms. Attempting to kill process");

                pluginInstance.Process.Refresh();

                if (pluginInstance.Process.HasExited)
                {
                    LogTo.Warning($"{pluginInstance.ToString().CapitalizeFirst()} has already exited");
                    return(false);
                }

                pluginInstance.Process.Kill();
            }
            catch (RemotingException ex)
            {
                LogTo.Warning(ex, $"StartPlugin '{pluginInstance} failed.");
            }
            catch (Exception ex)
            {
                LogTo.Error(ex, $"An error occured while starting {pluginInstance}");
            }
            finally
            {
                try
                {
                    using (await pluginInstance.Lock.LockAsync())
                        OnPluginStopped(pluginInstance);
                }
                catch (Exception ex)
                {
                    LogTo.Error(ex, "Exception thrown while calling OnPluginStopped");
                }
            }

            return(false);
        }