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