/// <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)); }
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); }