Table of Contents Previous Next
Logo
Server-Side Slice-to-C++ Mapping : 8.3 The Server-Side main Function
Copyright © 2003-2007 ZeroC, Inc.

8.3 The Server-Side main Function

The main entry point to the Ice run time is represented by the local interface Ice::Communicator. As for the client side, you must initialize the Ice run time by calling Ice::initialize before you can do anything else in your server. Ice::initialize returns a smart pointer to an instance of an Ice::Communicator:
int
main(int argc, char* argv[])
{
    Ice::CommunicatorPtr ic
        = Ice::initialize(argc, argv);
    // ...
}
Ice::initialize accepts a C++ reference to argc and argv. The function scans the argument vector for any command-line options that are relevant to the Ice run time; any such options are removed from the argument vector so, when Ice::initialize returns, the only options and arguments remaining are those that concern your application. If anything goes wrong during initialization, initialize throws an exception.1
Before leaving your main function, you must call Communicator::destroy. The destroy operation is responsible for finalizing the Ice run time. In particular, destroy waits for any operation invocations that may still be running to complete. In addition, destroy ensures that any outstanding threads are joined with and reclaims a number of operating system resources, such as file descriptors and memory. Never allow your main function to terminate without calling destroy first; doing so has undefined behavior.
The general shape of our server-side main function is therefore as follows:
#include <Ice/Ice.h>

int
main(int argc, char* argv[])
{
    int status = 0;
    Ice::CommunicatorPtr ic;
    try {
        ic = Ice::initialize(argc, argv);

        // Server code here...

    } catch (const Ice::Exception& e) {
        cerr << e << endl;
        status = 1;
    } catch (const std::string& msg) {
        cerr << msg << endl;
        status = 1;
    } catch (const char* msg) {
        cerr << msg << endl;
        status = 1;
    }
    if (ic) {
        try {
            ic>destroy();
        } catch (const std::string& msg) {
            cerr << msg << endl;
            status = 1;
        }
    }
    return status;
}
Note that the code places the call to Ice::initialize in to a try block and takes care to return the correct exit status to the operating system. Also note that an attempt to destroy the communicator is made only if the initialization succeeded.
The catch handlers for const std::string & and const char * are in place as a convenience feature: if we encounter a fatal error condition anywhere in the server code, we can simply throw a string or a string literal containing an error message; this causes the stack to be unwound back to main, at which point the error message is printed and, after destroying the communicator, main terminates with non-zero exit status.

8.3.1 The Ice::Application Class

The preceding structure for the main function is so common that Ice offers a class, Ice::Application, that encapsulates all the correct initialization and finalization activities. The definition of the class is as follows (with some detail omitted for now):
namespace Ice {
    class Application /* ... */ {
    public:
                Application();
        virtual ~Application();

                int main(int argc, char*[] argv);
                int main(int, char*[], const char* config);
                int main(int argc, char*[] argv,
                         const Ice::InitializationData& id);
                int main(const Ice::StringSeq&);
                int main(const Ice::StringSeq&, const char* config);
                int main(const Ice::StringSeq&,
                         const Ice::InitializationData& id);

        virtual int run(int, char*[]) = 0;

        static  const char* appName();
        static  CommunicatorPtr communicator();
        // ...
    };
}
The intent of this class is that you specialize Ice::Application and implement the pure virtual run method in your derived class. Whatever code you would normally place in main goes into the run method instead. Using Ice::Application, our program looks as follows:
#include <Ice/Ice.h>

class MyApplication : virtual public Ice::Application {
public:
    virtual int run(int, char*[]) {

        // Server code here...

        return 0;
    }
};

int
main(int argc, char* argv[])
{
    MyApplication app;
    return app.main(argc, argv);
}
Note that Application::main is overloaded: you can pass a string sequence instead of an argc/argv pair. This is useful if you need to parse application-specific property settings on the command line (see Section 30.8.3). You also can call main with an optional file name or an InitializationData structure (see Section 32.3 and Section 30.8). If you pass a configuration file name, settings on the command line override settings in the configuration file. The Application::main function does the following:
1. It installs an exception handler for Ice::Exception. If your code fails to handle an Ice exception, Application::main prints the exception details on stderr before returning with a non-zero return value.
2. It installs exception handlers for const std::string & and const char *. This allows you to terminate your server in response to a fatal error condition by throwing a std::string or a string literal. Application::main prints the string on stderr before returning a non-zero return value.
3. It initializes (by calling Ice::initialize) and finalizes (by calling Communicator::destroy) a communicator. You can get access to the communicator for your server by calling the static communicator() member.
4. It scans the argument vector for options that are relevant to the Ice run time and removes any such options. The argument vector that is passed to your run method therefore is free of Ice-related options and only contains options and arguments that are specific to your application.
5. It provides the name of your application via the static appName member function. The return value from this call is argv[0], so you can get at argv[0] from anywhere in your code by calling Ice::Application::appName (which is usually required for error messages).
6. It creates an IceUtil::CtrlCHandler that properly destroys the communicator.
Using Ice::Application ensures that your program properly finalizes the Ice run time, whether your server terminates normally or in response to an exception or signal. We recommend that all your programs use this class; doing so makes your life easier. In addition, Ice::Application also provides features for signal handling and configuration that you do not have to implement yourself when you use this class.

Using Ice::Application on the Client Side

You can use Ice::Application for your clients as well: simply implement a class that derives from Ice::Application and place the client code into its run method. The advantage of this approach is the same as for the server side: Ice::Application ensures that the communicator is destroyed correctly even in the presence of exceptions.

Catching Signals

The simple server we developed in Chapter 3 had no way to shut down cleanly: we simply interrupted the server from the command line to force it to exit. Terminating a server in this fashion is unacceptable for many real-life server applications: typically, the server has to perform some cleanup work before terminating, such as flushing database buffers or closing network connections. This is particularly important on receipt of a signal or keyboard interrupt to prevent possible corruption of database files or other persistent data.
To make it easier to deal with signals, Ice::Application encapsulates the platform-independent signal handling capabilities provided by the class IceUtil::CtrlCHandler (see Section 31.11). This allows you to cleanly shut down on receipt of a signal and to use the same source code regardless of the underlying operating system and threading package:
namespace Ice {
    class Application : /* ... */ {
    public:
        // ...
        static void destroyOnInterrupt();
        static void shutdownOnInterrupt();
        static void ignoreInterrupt();
        static void callbackOnInterrupt();
        static void holdInterrupt();
        static void releaseInterrupt();
        static bool interrupted();

        virtual void interruptCallback(int);
    };
}
You can use Ice::Application under both Windows and Unix: for Unix, the member functions control the behavior of your application for SIGINT, SIGHUP, and SIGTERM; for Windows, the member functions control the behavior of your application for CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT.
The functions behave as follows:
• destroyOnInterrupt
This function creates an IceUtil::CtrlCHandler that destroys the communicator when one of the monitored signals is raised. This is the default behavior.
• shutdownOnInterrupt
This function creates an IceUtil::CtrlCHandler that shuts down the communicator when one of the monitored signals is raised.
• ignoreInterrupt
This function causes signals to be ignored.
• ignoreInterrupt
This function causes signals to be ignored.
• callbackOnInterrupt
This function configures Ice::Application to invoke interruptCallback when a signal occurs, thereby giving the subclass responsibility for handling the signal. Note that if the signal handler needs to terminate the program, you must call _exit (instead of exit). This prevents global destructors from running which, depending on the activities of other threads in the program, could cause deadlock or assertion failures.
• releaseInterrupt
This function restores signal delivery to the previous disposition. Any signal that arrives after holdInterrupt was called is delivered when you call releaseInterrupt.
• interrupted
This function returns true if a signal caused the communicator to shut down, false otherwise. This allows us to distinguish intentional shutdown from a forced shutdown that was caused by a signal. This is useful, for example, for logging purposes.
• interruptCallback
A subclass overrides this function to respond to signals. The function may be called concurrently with any other thread and must not raise exceptions.
By default, Ice::Application behaves as if destroyOnInterrupt was invoked, therefore our server main function requires no change to ensure that the program terminates cleanly on receipt of a signal. However, we add a diagnostic to report the occurrence of a signal, so our main function now looks like:
#include <Ice/Ice.h>

class MyApplication : virtual public Ice::Application {
public:
    virtual int run(int, char*[]) {

        // Server code here...

        if (interrupted())
            cerr << appName() << ": terminating" << endl;

        return 0;
    }
};

int
main(int argc, char* argv[])
{
    MyApplication app;
    return app.main(argc, argv);
}
Note that, if your server is interrupted by a signal, the Ice run time waits for all currently executing operations to finish. This means that an operation that updates persistent state cannot be interrupted in the middle of what it was doing and cause partial update problems.
Under Unix, if you handle signals with your own handler (by deriving a subclass from Ice::Application and calling callbackOnInterrupt), the handler is invoked synchronously from a separate thread. This means that the handler can safely call into the Ice run time or make system calls that are not async-signal-safe without fear of deadlock or data corruption. Note that Ice::Application blocks delivery of SIGINT, SIGHUP, and SIGTERM. If your application calls exec, this means that the child process will also ignore these signals; if you need the default behavior of these signals in the exec’d process, you must explicitly reset them to SIG_DFL before calling exec.

Ice::Application and Properties

Apart from the functionality shown in this section, Ice::Application also takes care of initializing the Ice run time with property values. Properties allow you to configure the run time in various ways. For example, you can use properties to control things such as the thread pool size or port number for a server. We discuss Ice properties in more detail in Chapter 30.

Limitations of Ice::Application

Ice::Application is a singleton class that creates a single communicator. If you are using multiple communicators, you cannot use Ice::Application. Instead, you must structure your code as we saw in Chapter 3 (taking care to always destroy the communicators).

8.3.2 The Ice::Service Class

The Ice::Application class described in Section 8.3.1 is very convenient for general use by Ice client and server applications. In some cases, however, an application may need to run at the system level as a Unix daemon or Win32 service. For these situations, Ice includes Ice::Service, a singleton class that is comparable to Ice::Application but also encapsulates the low-level, platform-specific initialization and shutdown procedures common to system services. The Ice::Service class is defined as follows:
namespace Ice {
    class Service {
    public:
        Service();

        virtual bool shutdown();
        virtual void interrupt();

        int main(int&, char*[],
                 const Ice::InitializationData& =
                     Ice::InitializationData());
        int main(Ice::StringSeq&,
                 const Ice::InitializationData& =
                     Ice::InitializationData());

        Ice::CommunicatorPtr communicator() const;

        static Service* instance();

        bool service() const;
        std::string name() const;
        bool checkSystem() const;

        int run(int&, char*[], const Ice::InitializationData&);

        void configureService(const std::string&);

        int installService(bool, const std::string&,
                           const std::string&, const std::string&,
                           const std::vector<std::string>&);
        int uninstallService(bool, const std::string&);
        int startService(const std::string&,
                         const std::vector<std::string>&);
        int stopService(const std::string&);

        void configureDaemon(bool, bool, const std::string&);

        virtual void handleInterrupt(int);

    protected:
        virtual bool start(int, char*[]) = 0;
        virtual void waitForShutdown();
        virtual bool stop();
        virtual Ice::CommunicatorPtr initializeCommunicator(
            int&, char*[], const Ice::InitializationData&);

        virtual void syserror(const std::string&);
        virtual void error(const std::string&);
        virtual void warning(const std::string&);
        virtual void trace(const std::string&);

        void enableInterrupt();
        void disableInterrupt();

        // ...
    };
}
At a minimum, an Ice application that uses the Ice::Service class must define a subclass and override the start member function, which is where the service must perform its startup activities, such as processing command-line arguments, creating an object adapter, and registering servants. The application’s main function must instantiate the subclass and typically invokes its main member function, passing the program’s argument vector as parameters. The example below illustrates a minimal Ice::Service subclass:
#include <Ice/Service.h>

class MyService : public Ice::Service {
protected:
    virtual bool start(int, char*[]);
private:
    Ice::ObjectAdapterPtr _adapter;
};

bool
MyService::start(int argc, char* argv[])
{
    _adapter = communicator()>createObjectAdapter("MyAdapter");
    _adapter>addWithUUID(new MyServantI);
    _adapter>activate();
    return true;
}

int
main(int argc, char* argv[])
{
    MyService svc;
    return svc.main(argc, argv);
}
The Service::main member function performs the following sequence of tasks:
1. Scans the argument vector for reserved options that indicate whether the program should run as a system service and removes these options from the argument vector (argc is adjusted accordingly). Additional reserved options are supported for administrative tasks.
2. Configures the program for running as a system service (if necessary) by invoking configureService or configureDaemon, as appropriate for the platform.
3. Invokes the run member function and returns its result.
Note that, as for Application::main, Service::main is overloaded to accept a string sequence instead of an argc/argv pair. This is useful if you need to parse application-specific property settings on the command line (see Section 30.8.3).
The Service::run member function executes the service in the steps shown below:
1. Installs an IceUtil::CtrlCHandler (see Section 31.11) for proper signal handling.
2. Invokes the initializeCommunicator member function to obtain a communicator. The communicator instance can be accessed using the communicator member function.
3. Invokes the start member function. If start returns false to indicate failure, run destroys the communicator and returns immediately.
4. Invokes the waitForShutdown member function, which should block until shutdown is invoked.
5. Invokes the stop member function. If stop returns true, run considers the application to have terminated successfully.
6. Destroys the communicator.
7. Gracefully terminates the system service (if necessary).
If an unhandled exception is caught by Service::run, a descriptive message is logged, the communicator is destroyed and the service is terminated.

Ice::Service Member Functions

The virtual member functions in Ice::Service represent the points at which a subclass can intercept the service activities. All of the virtual member functions (except start) have default implementations.
• void handleInterrupt(int sig)
Invoked by the CtrlCHandler when a signal occurs. The default implementation ignores the signal if it represents a logoff event and the Ice.Nohup property is set to a value larger than zero, otherwise it invokes the interrupt member function.
• Ice::CommunicatorPtr
initializeCommunicator(int & argc, char * argv[],
                       const Ice::InitializationData & data)
Initializes a communicator. The default implementation invokes Ice::initialize and passes the given arguments.
• void interrupt()
Invoked by the signal handler to indicate a signal was received. The default implementation invokes the shutdown member function.
• bool shutdown()
Causes the service to begin the shutdown process. The default implementation invokes shutdown on the communicator. The subclass must return true if shutdown was started successfully, and false otherwise.
• bool start(int argc, char * argv[])
Allows the subclass to perform its startup activities, such as scanning the provided argument vector for recognized command-line options, creating an object adapter, and registering servants. The subclass must return true if startup was successful, and false otherwise.
• bool stop()
Allows the subclass to clean up prior to termination. The default implementation does nothing but return true. The subclass must return true if the service has stopped successfully, and false otherwise.
• void syserror(const std::string & msg) const
• void error(const std::string & msg) const
• void warning(const std::string & msg) const
• void trace(const std::string & msg) const
• void print(const std::string & msg) const
Convenience functions for logging messages to the communicator’s logger. The syserror member function includes a description of the system’s current error code.
• void waitForShutdown()
Waits indefinitely for the service to shut down. The default implementation invokes waitForShutdown on the communicator.
The non-virtual member functions shown in the class definition are described below:
• bool checkSystem() const
Returns true if the operating system supports Win32 services or Unix daemons. This function returns false on Windows 95/98/ME.
• Ice::CommunicatorPtr communicator() const
Returns the communicator used by the service, as created by initializeCommunicator.
• void configureDaemon(bool chdir, bool close,
                     const std::string & pidFile)
Configures the program to run as a Unix daemon. The chdir parameter determines whether the daemon changes its working directory to the root directory. The close parameter determines whether the daemon closes unnecessary file descriptors (i.e., stdin, stdout, etc.). If a non-empty string is provided in the pidFile parameter, the daemon writes its process ID to the given file.
• void configureService(const std::string & name)
Configures the program to run as a Win32 service with the given name.
• void disableInterrupt()
Disables the signal handling behavior in Ice::Service. When disabled, signals are ignored.
• void enableInterrupt()
Enables the signal handling behavior in Ice::Service. When enabled, the occurrence of a signal causes the handleInterrupt member function to be invoked.
• int installService(bool useEventLogger,
                   const std::string & name,
                   const std::string & display,
                   const std::string & executable,
                   const std::vector<std::string> & args)
Registers the program as a Win32 service with the given name. If the display parameter is non-empty, it is used as the display name for the service, otherwise the value of name is used. If the executable parameter is non-empty, it is used as the pathname of the executable, otherwise the pathname of the current executable is used. The values in args are passed to the service as command-line arguments at startup. This vector should typically include an option to notify the program that it is being started as a service. The useEventLogger parameter indicates whether the service uses the default logger implementation based on the Windows event log; if true, the function installs the necessary registry keys. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.
• static Service * instance()
Returns the singleton Ice::Service instance.
• int main(int & argc, char * argv[],
         const Ice::InitializationData & data =
         Ice::InitializationData())
The primary entry point of the Ice::Service class. The tasks performed by this function are described earlier in this section. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.
• std::string name() const
Returns the name of the service. If the program is running as a Win32 service, the return value is the Win32 service name, otherwise it returns the value of argv[0].
• int run(int & argc, char * argv[],
        const Ice::InitializationData & data)
Alternative entry point for applications that prefer a different style of service configuration. The program must invoke configureService (Win32) or configureDaemon (Unix) in order to run as a service. The tasks performed by this function are described earlier in this section. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.
• bool service() const
Returns true if the program is running as a Win32 service or Unix daemon, or false otherwise.
• int startService(const std::string & name,
                 const std::vector<std::string> & args)
Starts the Win32 service using the Service Control Manager. The values in args are passed to the service as command-line options at startup. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.
• int stopService(const std::string & name)
Stops the Win32 service using the Service Control Manager. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.
• int uninstallService(bool useEventLogger,
                     const std::string & name)
Removes a Win32 service using the Service Control Manager. If useEventLogger is true, the function removes the registry keys created by installService. The function returns EXIT_SUCCESS for success, EXIT_FAILURE for failure.

Unix Daemons

On Unix platforms, Ice::Service recognizes the following command-line options:
• daemon
Indicates that the program should run as a daemon. This involves the creation of a background child process in which Service::main performs its tasks. The parent process does not terminate until the child process has successfully invoked the start member function2. Unless instructed otherwise, Ice::Service changes the current working directory of the child process to the root directory, and closes all unnecessary file descriptors. Note that the file descriptors are not closed until after the communicator is initialized, meaning standard input, standard output, and standard error are available for use during this time. For example, the IceSSL plug-in may need to prompt for a passphrase on standard input, or Ice may print the child’s process id on standard output if the property Ice.PrintProcessId is set.
• pidfile FILE
This option writes the process ID of the service into the specified file. (This option requires daemon.)
• noclose
Prevents Ice::Service from closing unnecessary file descriptors. This can be useful during debugging and diagnosis because it provides access to the output from the daemon’s standard output and standard error.
• nochdir
Prevents Ice::Service from changing the current working directory.
The noclose and nochdir options can only be specified in conjunction with daemon. These options are removed from the argument vector that is passed to the start member function.

Win32 Services

On Win32 platforms3, Ice::Service starts the application as a Windows service if the service option is specified:
• service NAME
Run as a Windows service named NAME. This option is removed from the argument vector that is passed to the start member function.
Before an application can run as a Windows service, however, it must be installed, therefore the Ice::Service class supports several additional command-line options for performing administrative duties:
• install NAME [display DISP] [executable EXEC]
[ARG ...]
Installs the service NAME. If the display option is specified, use DISP as the display name of the service, otherwise use NAME. If the executable option is specified, use EXEC as the service executable pathname, otherwise use the pathname of the executable used to invoke install. Any additional arguments are passed unchanged to the Service::start member function. Note that this command automatically adds the command-line option service NAME to the set of arguments passed to the service at startup, therefore it is not necessary to specify those options explicitly.
• ‑uninstall NAME
Removes the service NAME. If the service is currently active, it must be stopped before it can be uninstalled.
• ‑start NAME [ARG ...]
Starts the service NAME. Any additional arguments are passed unchanged to the Service::start member function.
• ‑stop NAME
Stops the service NAME.
An error occurs if more than one administrative command is specified, or if the service option is specified in conjunction with an administrative command. The program terminates immediately after executing the administrative command.
The Ice::Service class supports the Windows service control codes SERVICE_CONTROL_INTERROGATE and SERVICE_CONTROL_STOP. Upon receipt of SERVICE_CONTROL_STOP, Ice::Service invokes the shutdown member function.

Logging Considerations

For Windows, Ice::Service automatically sends log messages to the Windows application event log. (The install option adds the required registry keys, and the uninstall option removes them.) You can override this behavior by installing a different logger in one of the following ways:
• use a process-wide logger (see Section 32.18.4),
• specify a logger in the InitializationData argument that you pass to main,
• specify a logger by setting the Ice.LoggerPlugin property (see Appendix C).
Note that when preparing to start a Windows service, Ice::Service may encounter errors before the communicator is initialized. If you use a custom logger, these errors are not sent to the custom logger, but rather to the Windows application event log. (The custom logger is used only once initialization of the communicator is complete.) Therefore, even if a failing service is configured to use a different logger implementation, you may find useful diagnostic information in the Windows application event log.
For Unix, Ice::Service uses the default logger (which logs to the standard error output). For daemons, this is not appropriate because the output will be lost. To change this, you can either implement a custom logger (as for Windows), or set the Ice.UseSyslog property, which selects a logger implementation that logs to the syslog facility.
Alternatively, for either Windows or Unix, you can set the Ice.StdErr property to redirect standard error to a file.
If an Ice::Service subclass needs to supply an alternate logger implementation, the subclass can provide it using the InitializationData argument to main, or the subclass can override the initializeCommunicator member function.

Troubleshooting Win32 Service Failures

One failure that commonly occurs when starting a Windows service is caused by missing DLLs, which usually results in an error window stating a particular DLL cannot be found. Fixing this problem can often be a trial-and-error process because the DLL mentioned in the error may depend on other DLLs that are also missing. It is important to understand that a Windows service is launched by the operating system and can be configured to execute as a different user, which means the service’s environment (most importantly its PATH) may not match yours and therefore extra steps are necessary to ensure that the service can locate its required DLLs4.
The simplest approach is to copy all of the necessary DLLs to the directory containing the service executable. If this solution is undesirable, another option is to modify the system PATH to include the directory or directories containing the required DLLs. (Note that modifying the system PATH requires restarting the system.) Finally, you can copy the necessary DLLs to \WINDOWS\system32, although we do not recommend this approach5.
Assuming that DLL issues are resolved, a Windows service can fail to start for a number of other reasons, including
• invalid command-line arguments or configuration properties
• inability to access necessary resources such as file systems and databases, because either the resources do not exist or the service does not have sufficient access rights to them
• networking issues, such as attempting to open a port that is already in use, or DNS lookup failures
Failures encountered by the Ice run time prior to initialization of the communicator are reported to the Windows event log if no other logger implementation is defined, so that should be the first place you look. Typically you will find an entry in the System event log resembling the following message:
The IcePatch2 service terminated with service‑specific error 1.
Error code 1 corresponds to EXIT_FAILURE, the value used by Ice::Service to indicate a failure during startup. Additional diagnostic messages may be available in the Application event log. See page 276 for more information on configuring a logger for a Windows service.
As we mentioned earlier, insufficient access rights can also prevent a Windows service from starting successfully. By default, a Windows service is configured to run under a local system account, in which case the service may not be able to access resources owned by other users. It may be necessary for you to configure a service to run under a different account, which you can do using the Services control panel.

1
Ice::initialize has additional overloads to permit other information to be passed to the Ice run time (see Section 32.3).

2
This behavior avoids the uncertainty often associated with starting a daemon from a shell script, because it ensures that the command invocation does not complete until the daemon is ready to receive requests.

3
Windows services are not supported on Windows 95/98/ME.

4
The command-line utility dumpbin can be used to discover the dependencies of an executable or DLL.

5
Copying DLLs to \WINDOWS\system32 often results in subtle problems later when trying to develop using newer versions of the DLLs. Inevitably you will forget about the DLLs in \WINDOWS\system32 and struggle to determine why your application is misbehaving or failing to start.

Table of Contents Previous Next
Logo