// **********************************************************************
//
// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************
using System;
using System.Diagnostics;
namespace Glacier2
{
///
/// 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 {@link #run} method.
///
/// A program can contain only one instance of this class.
///
public abstract class Application : Ice.Application
{
///
/// This exception is raised if the session should be restarted.
///
public class RestartSessionException : Exception
{
}
///
/// 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(Ice.SignalPolicy signalPolicy = Ice.SignalPolicy.HandleSignals) : base(signalPolicy)
{
}
///
/// Creates a new Glacier2 session. A call to createSession always
/// precedes a call to runWithSession. If Ice.LocalException is thrown
/// from this method, the application is terminated.
///
/// The Glacier2 session.
public abstract Glacier2.SessionPrx
createSession();
///
/// Called once the communicator has been initialized and the Glacier2 session
/// has been established. A derived class must implement runWithSession
,
/// 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 runWithSession
is free from Ice-related options and contains
/// only options and arguments that are application-specific.
///
/// The runWithSession method should return zero for successful
/// termination, and non-zero otherwise. Application.main returns the
/// value returned by runWithSession.
///
public abstract int
runWithSession(string[] args);
///
/// Run should not be overridden for Glacier2.Application. Instead
/// runWithSession should be used.
///
public override int
run(string[] args)
{
//
// This shouldn't be called.
//
Debug.Assert(false);
return 0;
}
///
/// Called when the session refresh thread detects that the session has been
/// destroyed. A subclass can override this method to take action after the
/// loss of connectivity with the Glacier2 router. This method is called
/// according to the Ice invocation dipsatch rules (in other words, it
/// uses the same rules as an servant upcall or AMI callback).
///
public virtual void
sessionDestroyed()
{
}
///
/// Called to restart the application's Glacier2 session. This
/// method never returns. The exception produce an application restart
/// when called from the Application main thread.
///
/// throws RestartSessionException This exception is
/// always thrown.
///
public static void
restart()
{
throw new RestartSessionException();
}
///
/// Returns the Glacier2 router proxy.
///
/// The router proxy.
public static Glacier2.RouterPrx
router()
{
return _router;
}
///
/// Returns the Glacier2 session proxy.
///
/// The session proxy.
public static SessionPrx
session()
{
return _session;
}
///
/// Returns the category to be used in the identities of all of the client's
/// callback objects. Clients must use this category for the router to
/// forward callback requests to the intended client.
/// Throws SessionNotExistException if no session exists.
///
/// The category.
public static string
categoryForClient()
{
if(_router == null)
{
throw new SessionNotExistException();
}
return _category;
}
///
/// Create a new Ice identity for callback objects with the given
/// identity name field.
///
/// The identity.
public static Ice.Identity
createCallbackIdentity(string name)
{
return new Ice.Identity(name, categoryForClient());
}
///
/// Adds a servant to the callback object adapter's Active Servant Map with a UUID.
///
/// The servant to add.
/// The proxy for the servant.
public static Ice.ObjectPrx
addWithUUID(Ice.Object servant)
{
return objectAdapter().add(servant, createCallbackIdentity(Guid.NewGuid().ToString()));
}
///
/// Returns an object adapter for callback objects, creating it if necessary.
///
/// The object adapter.
public static Ice.ObjectAdapter
objectAdapter()
{
if(_router == null)
{
throw new SessionNotExistException();
}
lock(iceMutex)
{
if(_adapter == null)
{
_adapter = communicator().createObjectAdapterWithRouter("", _router);
_adapter.activate();
}
}
return _adapter;
}
protected override int
doMain(string[] originArgs, Ice.InitializationData initData)
{
//
// Set the default properties for all Glacier2 applications.
//
initData.properties.setProperty("Ice.RetryIntervals", "-1");
bool restart;
int ret = 0;
do
{
//
// A copy of the initialization data and the string array
// needs to be passed to doMainInternal, as these can be
// changed by the application.
//
Ice.InitializationData id = (Ice.InitializationData)initData.Clone();
id.properties = id.properties.ice_clone_();
string[] args = (string[]) originArgs.Clone();
restart = doMain(args, id, out ret);
}
while(restart);
return ret;
}
private bool
doMain(string[] args, Ice.InitializationData initData, out int status)
{
//
// Reset internal state variables from Ice.Application. The
// remainder are reset at the end of this method.
//
iceCallbackInProgress = false;
iceDestroyed = false;
iceInterrupted = false;
bool restart = false;
bool sessionCreated = false;
status = 0;
try
{
iceCommunicator = Ice.Util.initialize(ref args, initData);
_router = RouterPrxHelper.uncheckedCast(communicator().getDefaultRouter());
if(_router == null)
{
Ice.Util.getProcessLogger().error(iceAppName + ": no Glacier2 router configured");
status = 1;
}
else
{
//
// The default is to destroy when a signal is received.
//
if(iceSignalPolicy == Ice.SignalPolicy.HandleSignals)
{
destroyOnInterrupt();
}
//
// If createSession throws, we're done.
//
try
{
_session = createSession();
sessionCreated = true;
}
catch(Ice.LocalException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
status = 1;
}
if(sessionCreated)
{
int acmTimeout = 0;
try
{
acmTimeout = _router.getACMTimeout();
}
catch(Ice.OperationNotExistException)
{
}
if(acmTimeout <= 0)
{
acmTimeout = (int)_router.getSessionTimeout();
}
if(acmTimeout > 0)
{
Ice.Connection connection = _router.ice_getCachedConnection();
Debug.Assert(connection != null);
connection.setACM(acmTimeout, Ice.Util.None, Ice.ACMHeartbeat.HeartbeatAlways);
connection.setCloseCallback(_ => sessionDestroyed());
}
_category = _router.getCategoryForClient();
status = runWithSession(args);
}
}
}
//
// We want to restart on those exceptions that indicate a
// break down in communications, but not those exceptions that
// indicate a programming logic error (i.e., marshal, protocol
// failure, etc).
//
catch(RestartSessionException)
{
restart = true;
}
catch(Ice.ConnectionRefusedException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
restart = true;
}
catch(Ice.ConnectionLostException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
restart = true;
}
catch(Ice.UnknownLocalException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
restart = true;
}
catch(Ice.RequestFailedException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
restart = true;
}
catch(Ice.TimeoutException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
restart = true;
}
catch(Ice.LocalException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
status = 1;
}
catch(Exception ex)
{
Ice.Util.getProcessLogger().error("unknown exception:\n" + ex.ToString());
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 == Ice.SignalPolicy.HandleSignals)
{
ignoreInterrupt();
}
lock(iceMutex)
{
while(iceCallbackInProgress)
{
System.Threading.Monitor.Wait(iceMutex);
}
if(iceDestroyed)
{
iceCommunicator = null;
}
else
{
iceDestroyed = true;
//
// And iceCommunicator != null, meaning will be
// destroyed next, iceDestroyed = true also ensures that
// any remaining callback won't do anything
//
}
}
if(sessionCreated && _router != null)
{
try
{
_router.destroySession();
}
catch(Ice.ConnectionLostException)
{
//
// Expected if another thread invoked on an object from the session concurrently.
//
}
catch(SessionNotExistException)
{
//
// This can also occur.
//
}
catch(Exception ex)
{
//
// Not expected.
//
Ice.Util.getProcessLogger().error("unexpected exception when destroying the session:\n" +
ex.ToString());
}
_router = null;
}
if(iceCommunicator != null)
{
try
{
iceCommunicator.destroy();
}
catch(Ice.LocalException ex)
{
Ice.Util.getProcessLogger().error(ex.ToString());
status = 1;
}
catch(Exception ex)
{
Ice.Util.getProcessLogger().error("unknown exception:\n" + ex.ToString());
status = 1;
}
iceCommunicator = null;
}
//
// Reset internal state. We cannot reset the Application state
// here, since iceDestroyed must remain true until we re-run
// this method.
//
_adapter = null;
_router = null;
_session = null;
_category = null;
return restart;
}
private static Ice.ObjectAdapter _adapter;
private static RouterPrx _router;
private static SessionPrx _session;
private static string _category;
}
}