private bool reinitialize() { // If we are already initialized and the settings file hasn’t changed, we don’t need to do anything. var firstRunEver = _server == null; if (_server != null && File.GetLastWriteTimeUtc(_settingsPath) <= _settingsLastChangedTime) { return(false); } // This may load *and re-write* the settings file... var newSettings = PropellerUtil.LoadSettings(_settingsPath, firstRunEver ? new ConsoleLogger() : _log, firstRunEver); // ... so remember the file date/time stamp *after* the writing _settingsLastChangedTime = File.GetLastWriteTimeUtc(_settingsPath); _log = PropellerUtil.GetLogger(true, newSettings.LogFile, newSettings.LogVerbosity); _log.Info(firstRunEver ? "Initializing Propeller" : "Reinitializing Propeller"); // If either port number or the bind-to address have changed, stop and restart the server’s listener. var startListening = false; if (_server == null || CurrentSettings == null || !CurrentSettings.ServerOptions.Endpoints.Values.SequenceEqual(newSettings.ServerOptions.Endpoints.Values)) { var removed = CurrentSettings == null ? new HttpEndpoint[0] : CurrentSettings.ServerOptions.Endpoints.Values.Except(newSettings.ServerOptions.Endpoints.Values).ToArray(); var added = CurrentSettings == null?newSettings.ServerOptions.Endpoints.Values.ToArray() : newSettings.ServerOptions.Endpoints.Values.Except(CurrentSettings.ServerOptions.Endpoints.Values).ToArray(); if (_server == null || removed.Length > 0 || added.Length > 0) { if (removed.Length > 0) { _log.Info("Disabling {0}".Fmt(removed.Select(ep => "HTTP{0} on port {1}".Fmt(ep.Secure ? "S" : null, ep.Port)).JoinString(", ", lastSeparator: " and "))); } if (added.Length > 0) { _log.Info("Enabling {0}".Fmt(added.Select(ep => "HTTP{0} on port {1}".Fmt(ep.Secure ? "S" : null, ep.Port)).JoinString(", ", lastSeparator: " and "))); } if (_server == null) { _server = new HttpServer { Options = newSettings.ServerOptions, ErrorHandler = errorHandler, ResponseExceptionHandler = responseExceptionHandler } } ; else { _server.StopListening(); } startListening = true; } } CurrentSettings = newSettings; // Create a new instance of all the modules var newAppDomains = new HashSet <AppDomainInfo>(); foreach (var module in newSettings.Modules) { _log.Info("Initializing module: " + module.ModuleName); try { var inf = new AppDomainInfo(_log, newSettings, module, new SettingsSaver(s => { module.Settings = s; _settingsSavedByModule = true; })); newAppDomains.Add(inf); } catch (Exception e) { _log.Error("Failed to initialize module {0}:".Fmt(module.ModuleName)); _log.Exception(e); } } // Switcheroo! lock (_lockObject) { _log.Info("AppDomain Switcheroo"); _inactiveAppDomains.AddRange(_activeAppDomains); _activeAppDomains = newAppDomains; _server.Options = newSettings.ServerOptions; _server.Handler = createResolver().Handle; _server.Log = PropellerUtil.GetLogger(newSettings.HttpAccessLogToConsole, newSettings.HttpAccessLogFile, newSettings.HttpAccessLogVerbosity); if (startListening) { _server.StartListening(); } } // Delete any remaining temp folders no longer in use HashSet <string> tempFoldersInUse; lock (_lockObject) tempFoldersInUse = _activeAppDomains.Concat(_inactiveAppDomains).Select(ad => ad.TempPathUsed).ToHashSet(); foreach (var tempFolder in Directory.EnumerateDirectories(CurrentSettings.TempFolder ?? Path.GetTempPath(), "propeller-tmp-*")) { if (tempFoldersInUse.Contains(tempFolder)) { continue; } try { Directory.Delete(tempFolder, recursive: true); } catch { } } return(true); }
private void checkSettingsChanges() { try { // ① If the server settings have changed, reinitialize everything. if (reinitialize()) { return; } // ② If a module rewrote its settings, save the settings file. if (_settingsSavedByModule) { _log.Debug("A module saved the settings."); try { lock (_lockObject) CurrentSettings.Save(_settingsPath); } catch (Exception e) { _log.Error("Error saving Propeller settings:"); PropellerUtil.LogException(_log, e); } _settingsSavedByModule = false; _settingsLastChangedTime = File.GetLastWriteTimeUtc(_settingsPath); } // ③ If any module wants to reinitialize, do it AppDomainInfo[] actives; lock (_lockObject) actives = _activeAppDomains.ToArray(); foreach (var active in actives) { if (!active.MustReinitialize) // this adds a log message if it returns true { continue; } _log.Info("Module says it must reinitialize: {0} ({1})".Fmt(active.ModuleSettings.ModuleName, active.GetHashCode())); var newAppDomain = new AppDomainInfo(_log, CurrentSettings, active.ModuleSettings, active.Saver); lock (_lockObject) { _inactiveAppDomains.Add(active); _activeAppDomains.Remove(active); _activeAppDomains.Add(newAppDomain); _server.Handler = createResolver().Handle; _log.Info(" --- {0} replaced with {1}, {0} shutting down".Fmt(active.GetHashCode(), newAppDomain.GetHashCode())); active.RunnerProxy.Shutdown(); } } // ④ Try to clean up as many inactive AppDomains as possible AppDomainInfo[] inactives; lock (_lockObject) inactives = _inactiveAppDomains.ToArray(); foreach (var inactive in inactives) { // Ask the runner if it has active connections; if this throws, it’s in a broken state anyway, so unload it by force. bool disposeAllowed; try { disposeAllowed = inactive.HasActiveConnections; } catch { disposeAllowed = true; } if (disposeAllowed) { _log.Info("Disposing inactive module: {0} ({1})".Fmt(inactive.ModuleSettings.ModuleName, inactive.GetHashCode())); lock (_lockObject) _inactiveAppDomains.Remove(inactive); try { inactive.Dispose(); } catch { } } else { _log.Info("Inactive module still has active connections: {0} ({1})".Fmt(inactive.ModuleSettings.ModuleName, inactive.GetHashCode())); } } } catch (Exception e) { PropellerUtil.LogException(_log, e); } }