//****************************************************************************************************** // ShutdownHandler.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 11/03/2016 - Steven E. Chisholm // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; #if !SQLCLR using System.ComponentModel; #endif using GSF.Collections; using GSF.Diagnostics; namespace GSF.Threading { /// /// The order in which the specified callback should occur when shutting down. /// public enum ShutdownHandlerOrder { /// /// This queue is processed first. Unless there is a compelling reason to execute first, select the Default one. /// First, /// /// This shutdown order occurs after First, but before Last. /// Default, /// /// This queue is processed last. Items such as flushing application logs should go here. /// Last } /// /// This class will monitor the state to the application and raise events when it detects that the application is about to shutdown. /// public static class ShutdownHandler { private static readonly LogPublisher Log; /// /// Gets if this process is shutting down. /// public static bool IsShuttingDown { get; private set; } /// /// Gets if this process has already shut down. /// public static bool HasShutdown { get; private set; } private static readonly List s_onShutdownCallbackFirst; private static readonly List s_onShutdownCallbackDefault; private static readonly List s_onShutdownCallbackLast; private static readonly object s_syncRoot; static ShutdownHandler() { s_syncRoot = new object(); s_onShutdownCallbackFirst = new List(); s_onShutdownCallbackDefault = new List(); s_onShutdownCallbackLast = new List(); Logger.Initialize(); Log = Logger.CreatePublisher(typeof(ShutdownHandler), MessageClass.Component); if (AppDomain.CurrentDomain.IsDefaultAppDomain()) AppDomain.CurrentDomain.ProcessExit += InitiateSafeShutdown; else AppDomain.CurrentDomain.DomainUnload += InitiateSafeShutdown; } /// /// Initializes the shutdown handler. This is recommended to put in main loop of the program, but it is not critical. /// public static void Initialize() { //This is handled through the static constructor. } /// /// Attempts Registers a callback that will be called /// when the application is shutdown. /// /// the callback when the shutdown occurs /// the order that the callback will occur. /// public static bool TryRegisterCallback(Action callback, ShutdownHandlerOrder shutdownOrder = ShutdownHandlerOrder.Default) { List list; switch (shutdownOrder) { case ShutdownHandlerOrder.First: list = s_onShutdownCallbackFirst; break; case ShutdownHandlerOrder.Default: list = s_onShutdownCallbackDefault; break; case ShutdownHandlerOrder.Last: list = s_onShutdownCallbackLast; break; default: throw new ArgumentOutOfRangeException(nameof(shutdownOrder), shutdownOrder, null); } if (IsShuttingDown) return false; lock (s_syncRoot) { if (IsShuttingDown) return false; list.RemoveWhere(x => !x.IsAlive); list.Add(new WeakAction(callback)); return true; } } private static void InitiateSafeShutdown(object sender, EventArgs e) { List shutdownList = new List(); lock (s_syncRoot) { if (IsShuttingDown) return; IsShuttingDown = true; shutdownList.AddRange(s_onShutdownCallbackFirst); shutdownList.AddRange(s_onShutdownCallbackDefault); shutdownList.AddRange(s_onShutdownCallbackLast); } Log.Publish(MessageLevel.Info, MessageFlags.SystemHealth, "Shutting Down", $"Sending shutdown notification to {shutdownList.Count} objects"); foreach (WeakAction weakAction in shutdownList) { try { weakAction.TryInvoke(); } catch (Exception ex) { Log.Publish(MessageLevel.Warning, "Application Shutdown Generated an Error", null, null, ex); } } Logger.Shutdown(); HasShutdown = true; } /// /// Requests that certain components initiate a safe shutdown. /// /// /// This method should only be called when the main thread exits. Calling this outside /// of the application exiting could result in unpredictable behavior. /// #if !SQLCLR [EditorBrowsable(EditorBrowsableState.Advanced)] #endif public static void InitiateSafeShutdown() { InitiateSafeShutdown(null, null); } } }