Developing IceBox Services
The IceBox Service
Interface
Writing an IceBox service requires implementing the IceBox Service interface or abstract base class.
Your service needs to implement only two methods, start
and stop
. These methods are invoked by the IceBox server: start
is called after the service is loaded, and stop
is called when the IceBox server is shutting down.
The start
method is the service's opportunity to initialize itself; this typically includes creating an object adapter and servants. The name
and args
parameters supply information from the service's configuration, and the communicator
parameter is a communicator created by the IceBox server for use by the service. Depending on the service configuration, this communicator instance may be shared by other services in the same IceBox server, therefore care should be taken to ensure that items such as object adapters are given unique names.
The stop
method must reclaim any resources used by the service. Generally, a service deactivates its object adapter, and may also need to invoke waitForDeactivate
on the object adapter in order to ensure that all pending requests have been completed before the clean up process can proceed. The server (not the service) is responsible for destroying the communicator instance that was passed to start
.
Whether the service's implementation of stop
should explicitly destroy its object adapter depends on other factors. For example, the adapter should be destroyed if the service uses a shared communicator, especially if the service could eventually be restarted. In other circumstances, the service can allow its adapter to be destroyed as part of the communicator's destruction.
IceBox Service Example
The example we present here is taken from the IceBox/greeter
demo program.
The class definition for our service is quite straightforward:
#include <IceBox/IceBox.h>
namespace Service
{
class GreeterService final : public IceBox::Service
{
public:
void start(
const std::string& name,
const Ice::CommunicatorPtr& communicator,
const Ice::StringSeq& args) final;
void stop() final;
};
}
The implementation is equally straightforward:
void
Service::GreeterService::start(
[[maybe_unused]] const string& name,
const Ice::CommunicatorPtr& communicator,
[[maybe_unused]] const Ice::StringSeq& args)
{
assert(!_adapter);
_adapter = communicator->createObjectAdapterWithEndpoints(
"GreeterAdapter",
"tcp -p 4061");
_adapter->add(
make_shared<GreeterServer::Chatbot>("Syd"),
Ice::Identity{"greeter"});
_adapter->activate();
cout << "Listening on port 4061..." << endl;
}
void
Service::GreeterService::stop()
{
cout << "Shutting down..." << endl;
assert(_adapter);
_adapter->destroy();
_adapter = nullptr;
}
The start
method creates an object adapter “GreeterAdapter”, activates a single servant of type Chatbot
(not shown), and activates the object adapter. The stop
method simply destroys the object adapter.
C++ Service Entry Point
The last piece of the puzzle is the entry point function, which the IceBox server calls to create an instance of the IceBox service:
extern "C"
{
ICE_DECLSPEC_EXPORT IceBox::Service*
create(const Ice::CommunicatorPtr&)
{
return new Service::GreeterService;
}
}
In this example, the create
function returns a new instance of the GreeterService
service. The name of the function is not important, but it must have the signature shown above. In particular, the function must have C linkage, accept a single const Ice::CommunicatorPtr&
parameter and return an IceBox::Service*
.
Configuring IceBox Services provides more information on entry points and describes how to configure your service into an IceBox server.
IceBox Service Failures
A service implementation can indicate a failure by throwing any exception.
When a service implementation throws an exception from its entry point, or from its start
or stop
methods, IceBox logs a message and exits.