26.16 The main Program
The main entry point to the Ice run time is represented by the local interface Ice::Communicator. You must initialize the Ice run time by calling
Ice::initialize before you can do anything else in your program.
Ice::initialize returns a reference to an instance of an
Ice:::Communicator:
require 'Ice'
status = 0
ic = nil
begin
ic = Ice::initialize(ARGV)
# ...
rescue => ex
puts ex
status = 1
end
# ...
Ice::initialize accepts the argument list that is passed to the program by the operating system. The function scans the argument list for any command-line options that are relevant to the Ice run time; any such options are removed from the argument list 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.
Before leaving your program, you must call
Communicator::destroy. The
destroy operation is responsible for finalizing the Ice run time. In particular,
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 program to terminate without calling
destroy first; doing so has undefined behavior.
require 'Ice'
status = 0
ic = nil
begin
ic = Ice::initialize(ARGV)
# ...
rescue => ex
puts ex
status = 1
end
if ic
begin
ic.destroy()
rescue => ex
puts ex
status = 1
end
end
exit(status)
Note that the code places the call to Ice::initialize into a
begin 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.
26.16.1 The Ice::Application Class
The preceding program structure is so common that Ice offers a class, Ice::Application, that encapsulates all the correct initialization and finalization activities. The synopsis of the class is as follows (with some detail omitted for now):
module Ice
class Application
def main(args, configFile=nil, initData=nil)
def run(args)
def Application.appName()
def Application.communicator()
end
end
The intent of this class is that you specialize Ice::Application and implement the abstract
run method in your derived class. Whatever code you would normally place in your main program goes into
run instead. Using
Ice::Application, our program looks as follows:
require 'Ice'
class Client < Ice::Application
def run(args)
# Client code here...
return 0
end
end
app = Client.new()
status = app.main(ARGV)
exit(status)
If you prefer, you can also reopen Ice::Application and define
run directly:
require 'Ice'
class Ice::Application
def run(args)
# Client code here...
return 0
end
end
app = Ice::Application.new()
status = app.main(ARGV)
exit(status)
You also can call main with an optional file name or an
InitializationData structure (see
Section 32.3 and
Section 30.9). If you pass a configuration file name to
main, the settings in this file are overridden by settings in a file identified by the
ICE_CONFIG environment variable (if defined). Property settings supplied on the command line take precedence over all other settings.
The Application.main function does the following:
2.
It initializes (by calling Ice::initialize) and finalizes (by calling
Communicator.destroy) a communicator. You can get access to the communicator for your program by calling the static
communicator accessor.
4.
It provides the name of your application via the static appName member function. The return value from this call is the first element of the argument vector passed to
Application.main, so you can get at this name from anywhere in your code by calling
Ice::Application::appName (which is usually required for error messages).
Using Ice::Application ensures that your program properly finalizes the Ice run time, whether your program 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.
A program typically needs 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 Ruby’s signal handling capabilities, allowing you to cleanly shut down on receipt of a signal:
class Application
def Application.destroyOnInterrupt()
def Application.ignoreInterrupt()
def Application.callbackOnInterrupt()
def Application.holdInterrupt()
def Application.releaseInterrupt()
def Application.interrupted()
def interruptCallback(sig):
# Default implementation does nothing.
end
# ...
end
This function configures Ice::Application to invoke
interruptCallback when a signal occurs, thereby giving the subclass responsibility for handling the signal.
This method 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.
By default, Ice::Application behaves as if
destroyOnInterrupt was invoked, therefore our program requires no change to ensure that the program terminates cleanly on receipt of a signal. (You can disable the signal-handling functionality of
Ice::Application by passing the constant
NoSignalHandling to the constructor. In that case, signals retain their default behavior, that is, terminate the process.) However, we add a diagnostic to report the occurrence of a signal, so our program now looks like:
require 'Ice'
class MyApplication < Ice::Application
def run(args)
# Client code here...
if Ice::Application::interrupted()
print Ice::Application::appName() + ": terminating"
end
return 0
end
end
app = MyApplication.new()
status = app.main(ARGV)
exit(status)
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 the trace level for diagnostic output. The
main method of
Ice::Application accepts an optional second parameter allowing you to specify the name of a configuration file that will be processed during initialization. We discuss Ice properties in more detail in
Chapter 30.
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 communicator).