// // Copyright (c) ZeroC, Inc. All rights reserved. // namespace Ice { using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; #if NET45 internal static class SafeNativeMethods { [DllImport("kernel32.dll")] [return: MarshalAsAttribute(UnmanagedType.Bool)] internal static extern bool SetConsoleCtrlHandler(CtrlCEventHandler eh, [MarshalAsAttribute(UnmanagedType.Bool)]bool add); } #endif /// /// The signal policy for Ice.Application signal handling. /// public enum SignalPolicy { /// /// If a signal is received, Ice.Application reacts to the signal /// by calling Communicator.destroy or Communicator.shutdown, /// or by calling a custom shutdown hook installed by the application. /// HandleSignals, /// /// Any signal that is received is not intercepted and takes the default action. /// NoSignalHandling } /// /// Utility base class that makes it easy to correctly initialize and finalize /// the Ice run time, as well as handle signals. Unless the application specifies /// a logger, Application installs a per-process logger that logs to the standard /// error output. /// Applications must create a derived class that implements the run method. /// A program can contain only one instance of this class. /// public abstract class Application { /// /// Called once the communicator has been initialized. The derived class must /// implement run, which is the application's starting method. /// /// The argument vector for the application. Application /// scans the argument vector passed to main for options that are /// specific to the Ice run time and removes them; therefore, the vector passed /// to run is free from Ice-related options and contains only options /// and arguments that are application-specific. /// The run method should return zero for successful termination, and /// non-zero otherwise. Application.main returns the value returned by run. public abstract int run(string[] args); /// /// Override this method to provide a custom application interrupt /// hook. You must call callbackOnInterrupt for this method /// to be called. Note that the interruptCallback can be called /// concurrently with any other thread (including main) in your /// application--take appropriate concurrency precautions. /// /// The cause of the interrupt. public virtual void interruptCallback(int sig) { } /// /// Initializes an instance that handles signals according to the signal policy. /// If not signal policy is provided the default SinalPolicy.HandleSignals /// will be used, which calls Communicator.shutdown if a signal is received. /// /// Determines how to respond to signals. public Application(SignalPolicy signalPolicy = SignalPolicy.HandleSignals) { iceSignalPolicy = signalPolicy; } /// /// The application must call main after it has /// instantiated the derived class. main creates /// a communicator, establishes the specified signal policy, and, /// once run returns, destroys the communicator. /// The method prints an error message for any exception that propagates /// out of run and ensures that the communicator is /// destroyed correctly even if run completes abnormally. /// /// The arguments for the application (as passed to Main(string[]) /// by the operating system. /// The value returned by run. If run terminates with an exception, /// the return value is non-zero. public int main(string[] args) { return main(args, new InitializationData()); } /// /// The application must call main after it has /// instantiated the derived class. main creates /// a communicator, establishes the specified signal policy, and, /// once run returns, destroys the communicator. /// The method prints an error message for any exception that propagates /// out of run and ensures that the communicator is /// destroyed correctly even if run completes abnormally. /// /// The arguments for the application (as passed to Main(string[]) /// by the operating system. /// The configuration file with which to initialize /// Ice properties. /// The value returned by run. If run terminates with an exception, /// the return value is non-zero. public int main(string[] args, string configFile) { if(Util.getProcessLogger() is ConsoleLoggerI) { Util.setProcessLogger(new ConsoleLoggerI(iceAppName)); } InitializationData initData = new InitializationData(); if(configFile != null) { try { initData.properties = Util.createProperties(); initData.properties.load(configFile); } catch(Ice.Exception ex) { Util.getProcessLogger().error(ex.ToString()); return 1; } catch(System.Exception ex) { Util.getProcessLogger().error("unknown exception:\n" + ex); return 1; } } return main(args, initData); } /// /// The application must call main after it has /// instantiated the derived class. main creates /// a communicator, establishes the specified signal policy, and, /// once run returns, destroys the communicator. /// The method prints an error message for any exception that propagates /// out of run and ensures that the communicator is /// destroyed correctly even if run completes abnormally. /// /// The arguments for the application (as passed to Main(string[]) /// by the operating system. /// Additional data used to initialize the communicator. /// The value returned by run. If run terminates with an exception, /// the return value is non-zero. public int main(string[] args, InitializationData initializationData) { if(Util.getProcessLogger() is ConsoleLoggerI) { Util.setProcessLogger(new ConsoleLoggerI(iceAppName)); } if(iceCommunicator != null) { Util.getProcessLogger().error("only one instance of the Application class can be used"); return 1; } // // We parse the properties here to extract Ice.ProgramName. // InitializationData initData; if(initializationData != null) { initData = (InitializationData)initializationData.Clone(); } else { initData = new InitializationData(); } try { initData.properties = Util.createProperties(ref args, initData.properties); } catch(Ice.Exception ex) { Util.getProcessLogger().error(ex.ToString()); return 1; } catch(System.Exception ex) { Util.getProcessLogger().error("unknown exception:\n" + ex); return 1; } iceAppName = initData.properties.getPropertyWithDefault("Ice.ProgramName", iceAppName); iceNohup = initData.properties.getPropertyAsInt("Ice.Nohup") > 0; _application = this; int status; if(iceSignalPolicy == SignalPolicy.HandleSignals) { _signals = new WindowsSignals(); _signals.register(_handler); status = doMain(args, initData); _signals.destroy(); _signals = null; } else { status = doMain(args, initData); } return status; } /// /// Returns the application name (which is also the value of Ice.ProgramName. /// This method is useful mainly for error messages that /// include the application name. Because appName is a static method, it is available from anywhere /// in the program. /// /// The name of the application. public static string appName() { return iceAppName; } /// /// Returns the communicator for the application. Because communicator is a static method, /// it permits access to the communicator from anywhere in the program. Note that, as a consequence, /// you cannot have more than one instance of Application in a program. /// /// The communicator for the application. public static Communicator communicator() { return iceCommunicator; } /// /// Instructs Application to call Communicator.destroy on receipt of a signal. /// This is default signal handling policy established by the default constructor. /// public static void destroyOnInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback == _holdCallback) { iceReleased = true; System.Threading.Monitor.Pulse(iceMutex); } _callback = _destroyCallback; } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Instructs Application to call Communicator.shutdown on receipt of a signal. /// public static void shutdownOnInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback == _holdCallback) { iceReleased = true; System.Threading.Monitor.Pulse(iceMutex); } _callback = _shutdownCallback; } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Instructs Application to ignore signals. /// public static void ignoreInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback == _holdCallback) { iceReleased = true; System.Threading.Monitor.Pulse(iceMutex); } _callback = null; } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Instructs Application to call interruptCallback on receipt of a signal. /// The derived class can intercept signals by overriding interruptCallback. /// public static void callbackOnInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback == _holdCallback) { iceReleased = true; System.Threading.Monitor.Pulse(iceMutex); } _callback = _userCallback; } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Instructs Application to call to hold signals. /// public static void holdInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback != _holdCallback) { _previousCallback = _callback; iceReleased = false; _callback = _holdCallback; } // else, we were already holding signals } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Instructs Application respond to signals. If a signal arrived since the last call /// to holdInterrupt, it is delivered once you call releaseInterrupt. /// public static void releaseInterrupt() { if(iceSignalPolicy == SignalPolicy.HandleSignals) { lock(iceMutex) { if(_callback == _holdCallback) { // // Note that it's very possible no signal is held; // in this case the callback is just replaced and // setting iceReleased to true and signalling this // will do no harm. // iceReleased = true; _callback = _previousCallback; System.Threading.Monitor.Pulse(iceMutex); } // Else nothing to release. } } else { Util.getProcessLogger().warning( "interrupt method called on Application configured to not handle interrupts."); } } /// /// Determines whether the application shut down intentionally or was forced to shut down due to a signal. /// This is useful for logging purposes. /// /// True if a signal caused the communicator to shut down; false otherwise. public static bool interrupted() { lock(iceMutex) { return iceInterrupted; } } protected virtual int doMain(string[] args, InitializationData initData) { int status = 0; try { // // If the process logger is the default logger, we replace it with a // a logger which is using the program name for the prefix. // if(initData.properties.getProperty("Ice.ProgramName").Length > 0 && Util.getProcessLogger() is ConsoleLoggerI) { Util.setProcessLogger(new ConsoleLoggerI(initData.properties.getProperty("Ice.ProgramName"))); } iceCommunicator = Util.initialize(ref args, initData); iceDestroyed = false; // // The default is to destroy when a signal is received. // if(iceSignalPolicy == SignalPolicy.HandleSignals) { destroyOnInterrupt(); } status = run(args); } catch(Ice.Exception ex) { Util.getProcessLogger().error(ex.ToString()); status = 1; } catch(System.Exception ex) { Util.getProcessLogger().error("unknown exception:\n" + ex); status = 1; } // // Don't want any new interrupt. And at this point // (post-run), it would not make sense to release a held // signal to run shutdown or destroy. // if(iceSignalPolicy == SignalPolicy.HandleSignals) { ignoreInterrupt(); } lock(iceMutex) { while(iceCallbackInProgress) { System.Threading.Monitor.Wait(iceMutex); } if(iceDestroyed) { iceCommunicator = null; } else { iceDestroyed = true; // // iceCommunicator != null means that it will be destroyed // next; iceDestroyed == true ensures that any // remaining callback won't do anything // } _application = null; } if(iceCommunicator != null) { try { iceCommunicator.destroy(); } catch(Ice.Exception ex) { Util.getProcessLogger().error(ex.ToString()); status = 1; } catch(System.Exception ex) { Util.getProcessLogger().error("unknown exception:\n" + ex); status = 1; } iceCommunicator = null; } return status; } // // First-level handler. // private static void signalHandler(int sig) { Callback callback; lock(iceMutex) { callback = _callback; } if(callback != null) { try { callback(sig); } catch(System.Exception) { Debug.Assert(false); } } } // // The callbacks to be invoked from the handler. // private static void holdInterruptCallback(int sig) { Callback callback = null; lock(iceMutex) { while(!iceReleased) { System.Threading.Monitor.Wait(iceMutex); } if(iceDestroyed) { // // Being destroyed by main thread // return; } callback = _callback; } if(callback != null) { callback(sig); } } // // The callbacks to be invoked from the handler. // private static void destroyOnInterruptCallback(int sig) { lock(iceMutex) { if(iceDestroyed) { // // Being destroyed by main thread // return; } if(iceNohup && sig == SIGHUP) { return; } Debug.Assert(!iceCallbackInProgress); iceCallbackInProgress = true; iceInterrupted = true; iceDestroyed = true; } try { Debug.Assert(iceCommunicator != null); iceCommunicator.destroy(); } catch(System.Exception ex) { Util.getProcessLogger().error("(while destroying in response to signal " + sig + "):\n" + ex); } lock(iceMutex) { iceCallbackInProgress = false; System.Threading.Monitor.Pulse(iceMutex); } } private static void shutdownOnInterruptCallback(int sig) { lock(iceMutex) { if(iceDestroyed) { // // Being destroyed by main thread // return; } if(iceNohup && sig == SIGHUP) { return; } Debug.Assert(!iceCallbackInProgress); iceCallbackInProgress = true; iceInterrupted = true; } try { Debug.Assert(iceCommunicator != null); iceCommunicator.shutdown(); } catch(System.Exception ex) { Util.getProcessLogger().error("(while shutting down in response to signal " + sig + "):\n" + ex); } lock(iceMutex) { iceCallbackInProgress = false; System.Threading.Monitor.Pulse(iceMutex); } } private static void userCallbackOnInterruptCallback(int sig) { lock(iceMutex) { if(iceDestroyed) { // // Being destroyed by main thread // return; } // For SIGHUP the user callback is always called. It can // decide what to do. Debug.Assert(!iceCallbackInProgress); iceCallbackInProgress = true; iceInterrupted = true; } try { Debug.Assert(_application != null); _application.interruptCallback(sig); } catch(System.Exception ex) { Util.getProcessLogger().error("(while interrupting in response to signal " + sig + "):\n" + ex); } lock(iceMutex) { iceCallbackInProgress = false; System.Threading.Monitor.Pulse(iceMutex); } } protected static object iceMutex = new object(); protected static bool iceCallbackInProgress = false; protected static bool iceDestroyed = false; protected static bool iceInterrupted = false; protected static bool iceReleased = false; protected static bool iceNohup = false; protected static SignalPolicy iceSignalPolicy = SignalPolicy.HandleSignals; private delegate void Callback(int sig); private static readonly Callback _destroyCallback = new Callback(destroyOnInterruptCallback); private static readonly Callback _shutdownCallback = new Callback(shutdownOnInterruptCallback); private static readonly Callback _holdCallback = new Callback(holdInterruptCallback); private static readonly Callback _userCallback = new Callback(userCallbackOnInterruptCallback); private static Callback _callback = null; // Current callback private static Callback _previousCallback; // Remembers prev. callback when signals are held // // We use FriendlyName instead of Process.GetCurrentProcess().ProcessName because the latter // is terribly slow. (It takes around 1 second!) // protected static string iceAppName = AppDomain.CurrentDomain.FriendlyName; protected static Communicator iceCommunicator; private static Application _application; private static int SIGHUP; static Application() { SIGHUP = 5; // CTRL_LOGOFF_EVENT, from wincon.h } private delegate void SignalHandler(int sig); private static readonly SignalHandler _handler = new SignalHandler(signalHandler); private Signals _signals; private interface Signals { void register(SignalHandler handler); void destroy(); } private class WindowsSignals : Signals { #if NET45 public void register(SignalHandler handler) { _handler = handler; _callback = new CtrlCEventHandler(callback); bool rc = SafeNativeMethods.SetConsoleCtrlHandler(_callback, true); Debug.Assert(rc); } public void destroy() { } private CtrlCEventHandler _callback; private bool callback(int sig) { _handler(sig); return true; } #else public void register(SignalHandler handler) { _handler = handler; Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs args) { args.Cancel = true; _handler(0); }; } public void destroy() { } #endif private SignalHandler _handler; } } delegate bool CtrlCEventHandler(int sig); }