private ProcessInfo StartProcess(string dirName, params string[] requestedExtensionIDs) { string[] foundExtensionIDs, activeExtensionIDs; try { using (var loader = new SafeExtensionLoader(_extensionsBasePath, dirName, "", null)) foundExtensionIDs = loader.AvailableExtensions.Select(e => e.ExtensionID).ToArray(); } catch (FileNotFoundException ex) { _logger.Debug("Directory not found: " + Path.Combine(_extensionsBasePath, dirName)); _logger.Error("Unable to start extension process - the extension subdirectory \"" + dirName + "\" does not exist or does not contain a valid set of extension assemblies."); return(null); } if (requestedExtensionIDs == null || requestedExtensionIDs.Length == 0) { activeExtensionIDs = foundExtensionIDs; } else { activeExtensionIDs = requestedExtensionIDs.Where(e => foundExtensionIDs.Contains(e)).ToArray(); } var info = new ProcessInfo { ID = Guid.NewGuid(), DirectoryName = dirName, RequestedExtensionIDs = requestedExtensionIDs, ActiveExtensionIDs = activeExtensionIDs, Timeout = DateTime.UtcNow.Add(_monitorInterval).AddSeconds(ExtensionStartupSeconds) // we add 5 seconds to give the process time to start }; _logger.Info("Starting new process for extensions in /" + dirName + " - " + info.ID + " (" + (activeExtensionIDs.Length == 0 ? "all" : activeExtensionIDs.Concat(", ")) + ")"); var cmdargs = string.Format( "-subdir \"{0}\" -basedir \"{1}\" -pid={2} -guid \"{3}\"{4}", dirName, _extensionsBasePath, Process.GetCurrentProcess().Id, info.ID, activeExtensionIDs.Concat(s => " " + s) ); var psi = new ProcessStartInfo(_launcherExePath, cmdargs) { ErrorDialog = false, CreateNoWindow = true, RedirectStandardError = true, RedirectStandardOutput = true, WorkingDirectory = Path.Combine(_extensionsBasePath, dirName), UseShellExecute = false }; info.Process = Process.Start(psi); return(info); }
static void Main(string[] args) { ConfigurationItemFactory.Default.Targets.RegisterDefinition("ServiceManager", typeof(ServiceManagerTarget)); string subdir = null, runDebugMethodOnExtension = null; var baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Extensions"); Environment.CurrentDirectory = ConfigurationManager.AppSettings["DataDirectory"] ?? AppDomain.CurrentDomain.BaseDirectory; var extensionIDs = new HashSet <string>(); Process process = null; Guid guid = Guid.Empty; Logger logger = null; var options = new OptionSet { { "guid=", "Specifies a GUID that the extension can use to identify itself to the parent process", v => { Guid id; if (!Guid.TryParse(v, out id)) { throw new OptionException("The specified id was not a valid GUID", "guid"); } guid = id; } }, { "basedir=", "Specifies the base plugins directory (can be relative or absolute)", v => baseDir = Path.IsPathRooted(v) ? v : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, v) }, { "subdir=", "Specifies the extension subdirectory name", v => subdir = v }, { "debug=", "Specifies an extension ID to run the debug method on", v => runDebugMethodOnExtension = v }, { "pid=", "Parent process ID - if specified, this process will close when the parent process closes", v => { int pid; if (!int.TryParse(v, out pid)) { throw new OptionException("The parent process ID must be a 32 bit integer", "pid"); } try { process = Process.GetProcessById(pid); } catch (Exception ex) { throw new OptionException(ex.Message, "pid"); } if (process == null) { throw new OptionException("There is no process with ID [" + pid + "]", "pid"); } } }, { "<>", v => extensionIDs.Add(v) } }; CancellationTokenSource src = new CancellationTokenSource(); try { options.Parse(args); if (subdir == null) { Console.Write("Enter plugin directory name (not the full path): "); subdir = Console.ReadLine(); if (string.IsNullOrWhiteSpace(subdir)) { Console.WriteLine("No plugin directory specified."); Exit(null, src, ExtensionRunnerExitCode.InvalidArguments); } } GlobalDiagnosticsContext.Set("ExeBaseDir", new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName); GlobalDiagnosticsContext.Set("SubDirName", subdir); GlobalDiagnosticsContext.Set("ParentProcess", process == null ? "" : process.Id.ToString()); logger = LogManager.GetCurrentClassLogger(); logger.Info(new [] { "ExtensionRunner Started:", " => Command Line: "+ Environment.CommandLine, " => Subdirectory: "+ subdir, " => Base Directory: "+ baseDir, " => Specified Extensions: "+ extensionIDs.Concat(", "), " => GUID: "+ guid, " => Parent Process ID: "+ (process == null ? "(none)" : process.Id.ToString()) }.Concat(Environment.NewLine)); AppDomain.CurrentDomain.UnhandledException += (s, e) => logger.FatalException("UNTRAPPED SERVICE EXCEPTION", (Exception)e.ExceptionObject); TaskScheduler.UnobservedTaskException += (s, e) => logger.FatalException("UNTRAPPED TASK EXCEPTION:", e.Exception); if (process != null) { Task.Factory.StartNew(() => { while (!src.IsCancellationRequested) { process.Refresh(); if (process.HasExited) { logger.Warn("Detected parent process shutdown."); Exit(logger, src, ExtensionRunnerExitCode.ParentExited); return; } Thread.Sleep(250); } }); } // Read list of available extensions Dictionary <string, ExtensionInfo> extInfos; using (var loader = new SafeExtensionLoader(baseDir, subdir, process == null ? "" : process.Id.ToString(), src)) extInfos = loader.AvailableExtensions.ToDictionary(x => x.ExtensionID, x => x.Clone()); if (extensionIDs.Count == 0) { extensionIDs = new HashSet <string>(extInfos.Select(x => x.Key)); // use all available extensions } else { extensionIDs = new HashSet <string>(extensionIDs.Where(x => extInfos.ContainsKey(x))); // eliminate invalid any extension IDs } logger.Info("Active extensions: " + (extensionIDs.Any() ? extensionIDs.Concat(", ") : "(none)")); logger.Info("Inactive extensions: " + (!extensionIDs.Any() ? extInfos.Where(x => !extensionIDs.Contains(x.Key)).Concat(", ") : "(none)")); var extLoaders = new List <SafeExtensionLoader>(); var extTasks = new List <Task>(); try { foreach (var id in extensionIDs) { logger.Debug("Starting appdomain for extension: {0}", id); var loader = new SafeExtensionLoader(baseDir, subdir, process == null ? "" : process.Id.ToString(), src); var extID = id; extTasks.Add(Task.Factory.StartNew(() => loader.RunExtension(guid, runDebugMethodOnExtension == extID, extID))); } Task.WaitAll(extTasks.ToArray(), src.Token); } finally { foreach (var extLoader in extLoaders) { extLoader.Dispose(); } } //using(var loader = new SafeExtensionLoader(baseDir, subdir, process == null ? "" : process.Id.ToString(), src)) //{ // var runExtsTask = Task.Factory.StartNew(() => // { // // Verify that all extensions are available and if so, run them // var sb = new StringBuilder(); // sb.AppendLine("[list of all plugins]"); // foreach(var extInfo in loader.AllExtensions) // sb.AppendLine("\t" + extInfo.ExtensionID + ": " + extInfo.Name + " [" + (extensionIDs.Count == 0 || extensionIDs.Contains(extInfo.ExtensionID) ? "ACTIVE" : "INACTIVE") + "]"); // logger.Info(sb.ToString()); // loader.RunExtensions(guid, runDebugMethodOnExtension, extensionIDs.ToArray()); // }, src.Token); // loader.RunMainAppThread(); // Task.WaitAll(new[] { runExtsTask }, src.Token); //} } catch (OptionException ex) { if (logger != null) { logger.Error("Invalid command options: " + ex.Message, options.WriteOptionDescriptions()); } Exit(logger, src, ExtensionRunnerExitCode.Exception); } catch (Exception ex) { if (logger != null) { logger.FatalException("An exception was thrown", ex); } Exit(logger, src, ExtensionRunnerExitCode.Exception); } finally { Exit(logger, src, ExtensionRunnerExitCode.Success); } }
//public string[] ListIncludedExtensionDirectories() //{ // throw new NotImplementedException(); //} //public ExtensionInfo[] ListAvailableExtensions() //{ // throw new NotImplementedException(); //} public ExtensionInfo[] ListExtensionsInDirectory(string name) { using (var pl = new SafeExtensionLoader(_extensionsBaseDir.FullName, name, "", null)) return(pl.AvailableExtensions); }