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