Table of Contents Previous Next
Logo
The Ice Run Time in Detail : 28.4 Object Adapters
Copyright © 2003-2008 ZeroC, Inc.

28.4 Object Adapters

 
A communicator contains one or more object adapters. An object adapter sits at the boundary between the Ice run time and the server application code and has a number of responsibilities:
• It maps Ice objects to servants for incoming requests and dispatches the requests to the application code in each servant (that is, an object adapter implements an up‑call interface that connects the Ice run time and the application code in the server).
• It assists in life cycle operations so Ice objects and servants can be created and existing destroyed without race conditions.
• It provides one or more transport endpoints. Clients access the Ice objects provided by the adapter via those endpoints. (It is also possible to create an object adapter without endpoints. In this case the adapter is used for bidirectional callbacks—see Section 33.7.)
Each object adapter has one or more servants that incarnate Ice objects, as well as one or more transport endpoints. If an object adapter has more than one endpoint, all servants registered with that adapter respond to incoming requests on any of the endpoints. In other words, if an object adapter has multiple transport endpoints, those endpoints represent alternative communication paths to the same set of objects (for example, via different transports).
Each object adapter belongs to exactly one communicator (but a single communicator can have many object adapters). Each object adapter has a name that distinguishes it from all other object adapters in the same communicator.
Each object adapter can optionally have its own thread pool, enabled via the <adaptername>.ThreadPool.Size property (see Section 28.9.3). If so, client invocations for that adapter are dispatched in a thread taken from the adapter’s thread pool instead of using a thread from the communicator’s server thread pool.

28.4.1 The Active Servant Map

Each object adapter maintains a data structure known as the active servant map. The active servant map (or ASM, for short) is a lookup table that maps object identities to servants: for C++, the lookup value is a smart pointer to the corresponding servant’s location in memory; for Java and C#, the lookup value is a reference to the servant. When a client sends an operation invocation to the server, the request is targeted at a specific transport endpoint. Implicitly, the transport endpoint identifies the object adapter that is the target of the request (because no two object adapters can be bound to the same endpoint). The proxy via which the client sends its request contains the object identity for the corresponding object, and the client-side run time sends this object identity over the wire with the invocation. In turn, the object adapter uses that object identity to look in its ASM for the correct servant to dispatch the call to, as shown in Figure 28.1.
Figure 28.1. Binding a request to the correct servant.
The process of associating a request via a proxy to the correct servant is known as binding. The scenario depicted in Figure 28.1 shows direct binding, in which the transport endpoint is embedded in the proxy. Ice also supports an indirect binding mode, in which the correct transport endpoints are provided by the IceGrid service (see Chapter 35 for details).
If a client request contains an object identity for which there is no entry in the adapter’s ASM, the adapter returns an ObjectNotExistException to the client (unless you use a servant locator—see Section 28.7).

28.4.2 Servants

As mentioned in Section 2.2.2, servants are the physical manifestation of an Ice object, that is, they are entities that are implemented in a concrete programming language and instantiated in the server’s address space. Servants provide the server-side behavior for operation invocations sent by clients.
The same servant can be registered with one or more object adapters.

28.4.3 Object Adapter Interface

Object adapters are local interfaces:
module Ice {
    local interface ObjectAdapter {
        string getName();
        Communicator getCommunicator();

        // ...
    };
};
The operations behave as follows:
• The getName operation returns the name of the adapter as passed to one of the communicator operations createObjectAdapter,
createObjectAdapterWithEndpoints, or createObjectAdapterWithRouter.
• The getCommunicator operation returns the communicator that was used to create the adapter.
Note that there are other operations in the ObjectAdapter interface; we will explore these throughout the remainder of this chapter.

28.4.4 Servant Activation and Deactivation

The term servant activation refers to making the presence of a servant for a particular Ice object known to the Ice run time. Activating a servant adds an entry to the active servant map shown in Figure 28.1. Another way of looking at servant activation is to think of it as creating a link between the identity of an Ice object and the corresponding programming-language servant that handles requests for that Ice object. Once the Ice run time has knowledge of this link, it can dispatch incoming requests to the correct servant. Without this link, that is, without a corresponding entry in the ASM, an incoming request for the identity results in an ObjectNotExistException. While a servant is activated, it is said to incarnate the corresponding Ice object.
The inverse operation is known as servant deactivation. Deactivating a servant removes an entry for a particular identity from the ASM. Thereafter, incoming requests for that identity are no longer dispatched to the servant and result in an ObjectNotExistException.
The object adapter offers a number of operations to manage servant activation and deactivation:
module Ice {
    local interface ObjectAdapter {
        // ...

        Object* add(Object servant, Identity id);
        Object* addWithUUID(Object servant);
        Object  remove(Identity id);
        Object  find(Identity id);
        Object  findByProxy(Object* proxy);

        // ...
    };
};
The operations behave as follows:
• add
The add operation adds a servant with the given identity to the ASM. Requests are dispatched to that servant as soon as add is called. The return value is the proxy for the Ice object incarnated by that servant. The proxy embeds the identity passed to add.
You cannot call add with the same identity more than once: attempts to add an already existing identity to the ASM result in an AlreadyRegisteredException. (It does not make sense to add two servants with the same identity because that would make it ambiguous as to which servant should handle incoming requests for that identity.)
Note that it is possible to activate the same servant multiple times with different identities. In that case, the same single servant incarnates multiple Ice objects. We explore the ramifications of this in more detail in Section 28.8.2.
• addWithUUID
The addWithUUID operation behaves the same way as the add operation but does not require you to supply an identity for the servant. Instead, addWithUUID generates a UUID (see [14]) as the identity for the corresponding Ice object. You can retrieve the generated identity by calling the ice_getIdentity operation on the returned proxy. addWithUUID is useful to create identities for temporary objects, such as short-lived session objects. (You can also use addWithUUID for persistent objects that do not have a natural identity, as we have done for the file system application.)
• remove
The remove operation breaks the association between an identity and its servant by removing the corresponding entry from the ASM; it returns a smart pointer to the removed servant.
Once the servant is deactivated, new incoming requests for the removed identity result in an ObjectNotExistException. Requests that are executing inside the servant at the time remove is called are allowed to complete normally. Once the last request for the servant is complete, the object adapter drops its reference (or smart pointer, for C++) to the servant. At that point, the servant becomes available for garbage collection (or is destroyed, for C++), provided that you do not hold references or smart pointers to the servant elsewhere. The net effect is that a deactivated servant is destroyed once it becomes idle.
Deactivating an object adapter (see Section 28.4.5) implicitly calls remove on its active servants.
• find
The find operation performs a lookup in the ASM and returns the servant for the specified object identity. If no servant with that identity is registered, the operation returns null. Note that find does not consult any servant locators.
• findByProxy
The findByProxy operation performs a lookup in the ASM and returns the servant with the object identity and facet that are embedded in the proxy. If no such servant is registered, the operation returns null. Note that findByProxy does not consult any servant locators.

28.4.5 Adapter States

An object adapter has a number of processing states:
• holding
In this state, any incoming requests for the adapter are held, that is, not dispatched to servants.
For TCP/IP (and other stream-oriented protocols), the server-side run time stops reading from the corresponding transport endpoint while the adapter is in the holding state. In addition, it also does not accept incoming connection requests from clients. This means that if a client sends a request to an adapter that is in the holding state, the client eventually receives a TimeoutException or ConnectTimeoutException (unless the adapter is placed into the active state before the timer expires).
For UDP, client requests that arrive at an adapter that is in the holding state are thrown away.
Immediately after creation of an adapter, the adapter is in the holding state. This means that requests are not dispatched until you place the adapter into the active state.
• active
In this state, the adapter accepts incoming requests and dispatches them to servants. A newly-created adapter is initially in the holding state. The adapter begins dispatching requests as soon as you place it into the active state.
You can transition between the active and the holding state as many times as you wish.
• inactive
In this state, the adapter has conceptually been destroyed (or is in the process of being destroyed). Deactivating an adapter destroys all transport endpoints that are associated with the adapter. Requests that are executing at the time the adapter is placed into the inactive state are allowed to complete, but no new requests are accepted. (New requests are rejected with an exception). Once an adapter has been deactivated, you can reactivate it again. Any attempt to use a deactivated object adapter results in an ObjectAdapterDeactivatedException.
The ObjectAdapter interface offers operations that allow you to change the adapter state, as well as to wait for a state change to be complete:
module Ice {
    local interface ObjectAdapter {
        // ...

        void activate();
        void hold();
        void waitForHold();
        void deactivate();
        void waitForDeactivate();
        void isDeactivated();
        void destroy();

        // ...
    };
};
The operations behave as follows:
• activate
The activate operation places the adapter into the active state. Activating an adapter that is already active has no effect. The Ice run time starts dispatching requests to servants for the adapter as soon as activate is called.
• hold
The hold operation places the adapter into the holding state. Requests that arrive after calling hold are held as detailed on page 741. Requests that are in progress at the time hold is called are allowed to complete normally. Note that hold returns immediately without waiting for currently executing requests to complete.
• waitForHold
The waitForHold operation suspends the calling thread until the adapter has completed its transition to the holding state, that is, until all currently executing requests have finished. You can call waitForHold from multiple threads, and you can call waitForHold while the adapter is in the active state. If you call waitForHold on an adapter that is already in the holding state, waitForHold returns immediately.
• deactivate
The deactivate operation initiates deactivation of the adapter: requests that arrive after calling deactivate are rejected, but currently executing requests are allowed to complete. Once all requests have completed, the adapter enters the inactivate state. Note that deactivate returns immediately without waiting for the currently executing requests to complete. Any attempt to use a deactivated object adapter results in an ObjectAdapterDeactivatedException.
• waitForDeactivate
The waitForDeactivate operation suspends the calling thread until the adapter has completed its transition to the inactive state, that is, until all currently executing requests have completed. You can call waitForDeactivate from multiple threads, and you can call waitForDeactivate while the adapter is in the active or holding state. Calling waitForDeactivate on an adapter that is in the inactive state does nothing and returns immediately.
• isDeactivated
The isDeactivated operation returns true if deactivate has been invoked on the adapter. A return value of true does not necessarily indicate that the adapter has fully transitioned to the inactive state, only that it has begun this transition. Applications that need to know when deactivation is completed can use waitForDeactivate.
• destroy
The destroy operation deactivates the adapter and releases all of its resources. Internally, destroy invokes deactivate followed by waitForDeactivate, therefore the operation blocks until all currently executing requests have completed. Furthermore, any servants associated with the adapter are destroyed, all transport endpoints are closed, and the adapter’s name becomes available for reuse.
Destroying a communicator implicitly destroys all of its object adapters. Invoking destroy on an adapter is only necessary when you need to ensure that its resources are released prior to the destruction of its communicator.
Placing an adapter into the holding state is useful, for example, if you need to make state changes in the server that require the server (or a group of servants) to be idle. For example, you could place the implementation of your servants into a dynamic library and upgrade the implementation by loading a newer version of the library at run time without having to shut down the server.
Similarly, waiting for an adapter to complete its transition to the inactive state is useful if your server needs to perform some final clean‑up work that cannot be carried out until all executing requests have completed.
Note that you can create an object adapter with the same name as a previous object adapter, but only once destroy on the previous adapter has completed.

28.4.6 Endpoints

An object adapter maintains two sets of transport endpoints. One set identifies the network interfaces on which the adapter listens for new connections, and the other set is embedded in proxies created by the adapter and used by clients to communicate with it. We will refer to these sets of endpoints as the physical endpoints and the published endpoints, respectively. In most cases these sets are identical, but there are situations when they must be configured independently.

Physical Endpoints

An object adapter’s physical endpoints identify the network interfaces on which it receives requests from clients. These endpoints are configured via the name.Endpoints property, or they can be specified explicitly when the adapter is created using the operation createObjectAdapterWithEndpoints (see Section 28.2). The endpoint syntax is described in detail in Appendix D; however, an endpoint generally consists of a transport protocol followed by an optional host name and port.
If a host name is specified, the object adapter listens only on the network interface associated with that host name. If no host name is specified but the property Ice.Default.Host is defined, the object adapter uses the property’s value as the host name. Finally, if a host name is not specified, and the property Ice.Default.Host is undefined, the object adapter listens on all available network interfaces, including the loopback interface. You may also force the object adapter to listen on all interfaces by using one of the host names 0.0.0.0 or *. The adapter does not expand the list of interfaces when it is initialized. Instead, if no host is specified, or you use h * or  0.0.0.0, the adapter binds to INADDR_ANY to listen for incoming requests.
If you want an adapter to accept requests on certain network interfaces, you must specify a separate endpoint for each interface. For example, the following property configures a single endpoint for the adapter named MyAdapter:
MyAdapter.Endpoints=tcp ‑h 10.0.1.1 ‑p 9999
This endpoint causes the adapter to accept requests on the network interface associated with the IP address 10.0.1.1 at port 9999. Note however that this adapter configuration does not accept requests on the loopback interface (the one associated with address 127.0.0.1). If both addresses must be supported, then both must be specified explicitly, as shown below:
MyAdapter.Endpoints=\
    tcp ‑h 10.0.1.1 ‑p 9999:tcp ‑h 127.0.0.1 ‑p 9999
If these are the only two network interfaces available on the host, then a simpler configuration omits the host name altogether, causing the object adapter to listen on both interfaces automatically:
MyAdapter.Endpoints=tcp ‑p 9999
If you want to make your configuration more explicit, you can use one of the special host names:
MyAdapter.Endpoints=tcp ‑h * ‑p 9999
Another advantage to this configuration is that it ensures the object adapter always listens on all interfaces, even if a definition for Ice.Default.Host is later added to your configuration. Without an explicit host name, the addition of Ice.Default.Host could potentially change the interfaces on which the adapter is listening.
Careful consideration must also be given to the selection of a port for an endpoint. If no port is specified, the adapter uses a port that is selected (essentially at random) by the operating system, meaning the adapter will likely be using a different port each time the server is restarted. Whether that behavior is desirable depends on the application, but in many applications a client has a proxy containing the adapter’s endpoint and expects that proxy to remain valid indefinitely. Therefore, an endpoint generally should contain a fixed port to ensure that the adapter is always listening at the same port.
However, there are certain situations where a fixed port is not required. For example, an adapter whose servants are transient does not need a fixed port, because the proxies for those objects are not expected to remain valid past the lifetime of the server process. Similarly, a server using indirect binding via IceGrid (see Chapter 35) does not need a fixed port because its port is never published.

Published Endpoints

An object adapter publishes its endpoints in the proxies it creates, but it is not always appropriate to publish the adapter’s physical endpoints in a proxy. For example, suppose a server is running on a host in a private network, protected from the public network by a firewall that can forward network traffic to the server. The adapter’s physical endpoints must use the private network’s address scheme, but a client in the public network would be unable to use those endpoints if they were published in a proxy. In this scenario, the adapter must publish endpoints in its proxies that direct the client to the firewall instead.
The published endpoints are configured using the adapter property name.PublishedEndpoints. If this property is not defined, the adapter publishes its physical endpoints by default, with one exception: endpoints for the loopback address (127.0.0.1) are not published unless the loopback interface is the only interface, or 127.0.0.1 (or loopback) is explicitly listed as an endpoint with the h option. Otherwise, to force the inclusion of loopback endpoints when they would normally be excluded, you must define name.PublishedEndpoints explicitly.
As an example, the properties below configure the adapter named MyAdapter with physical and published endpoints:
MyAdapter.Endpoints=tcp ‑h 10.0.1.1 ‑p 9999
MyAdapter.PublishedEndpoints=tcp ‑h corpfw ‑p 25000
This example assumes that clients connecting to host corpfw at port 25000 are forwarded to the adapter’s endpoint in the private network.
Another use case of published endpoints is for replicated servers. Suppose we have two instances of a stateless server running on separate hosts in order to distribute the load between them. We can supply the client with a bootstrap proxy containing the endpoints of both servers, and the Ice run time in the client will select one of the servers at random when a connection is established. However, should the client invoke an operation on a server that returns a proxy for another object, that proxy would normally contain only the endpoint of the server that created it. Invocations on the new proxy are always directed at the same server, reducing the opportunity for load balancing.
We can alleviate this situation by configuring the adapters to publish the endpoints of both servers. For example, here is a configuration for the server on host Sun1:
MyAdapter.Endpoints=tcp ‑h Sun1 ‑p 9999
MyAdapter.PublishedEndpoints=tcp ‑h Sun1 ‑p 9999:tcp ‑h Sun2 ‑p 9999
Similarly, the configuration for host Sun2 retains the same published endpoints:
MyAdapter.Endpoints=tcp ‑h Sun2 ‑p 9999
MyAdapter.PublishedEndpoints=tcp ‑h Sun1 ‑p 9999:tcp ‑h Sun2 ‑p 9999

Refreshing Endpoints

The list of interfaces of a host may change over time, for example, if a laptop moves in and out of range of a wireless network. The object adapter provides an operation to refresh its list of interfaces:
local interface ObjectAdapter {
    void refreshPublishedEndpoints();
    // ...
};
Calling refreshPublishedEndpoints causes the object adapter to update its internal list of available network interfaces and to re-read the value of the name.PublishedEndpoints property. This allows you to react to changing network interfaces while an object adapter is in use. (Your application code must determine when it is necessary to call this operation.)
Note that refreshPublishedEndpoints takes effect only for object adapters that specify published endpoints without a host or set the published endpoints to h * or -h 0.0.0.0.

Timeouts

As a defense against hostile clients, we recommend that you specify a timeout for you physical object adapter endpoints. The timeout value you select affects tasks that the Ice run time normally does not expect to block for any significant amount of time, such as writing a reply message to a socket or waiting for SSL negotiation to complete. If you do not specify a timeout, the Ice run time waits indefinitely in these situations. As a result, malicious or misbehaving clients could consume excessive resources such as file descriptors.
Specifying a timeout in an object adapter endpoint is done exactly as in a proxy endpoint using the t option:
MyAdapter.Endpoints=tcp ‑p 9999 ‑t 5000
In this example, we specify a timeout of five seconds.

Routers

If an object adapter is configured with a router, the adapter’s published endpoints are augmented to reflect the router. See Chapter 39 for more information on configuring an adapter with a router.

28.4.7 Creating Proxies

Although the servant activation operations described in Section 28.4.4 return proxies, the life cycle of proxies is completely independent from servants (see Chapter 31). The ObjectAdapter interface provides several operations for creating a proxy, regardless of whether a servant is currently activated for the object’s identity:
module Ice {
    local interface ObjectAdapter {
        // ...

        Object* createProxy(Identity id);
        Object* createDirectProxy(Identity id);
        Object* createIndirectProxy(Identity id);

        // ...
    };
};
These operations are described below:
• createProxy
The createProxy operation returns a new proxy for the object with the given identity. The adapter’s configuration determines whether the return value is a direct proxy or an indirect proxy (see Section 2.2.2). If the adapter is configured with an adapter id (see Section 28.17.5), the operation returns an indirect proxy that refers to the adapter id. If the adapter is also configured with a replica group id, the operation returns an indirect proxy that refers to the replica group id. Otherwise, if an adapter id is not defined, createProxy returns a direct proxy containing the adapter’s published endpoints (see Section 28.4.6).
• createDirectProxy
The createDirectProxy operation returns a direct proxy containing the adapter’s published endpoints (see Section 28.4.6).
• createIndirectProxy
The createIndirectProxy operation returns an indirect proxy. If the adapter is configured with an adapter id, the returned proxy refers to that adapter id. Otherwise, the proxy refers only to the object’s identity (see page 15).
In contrast to createProxy, createIndirectProxy does not use the replica group id. Therefore, the returned proxy always refers to a specific replica.

Configuring Proxies

After using one of the operations discussed above to create a proxy, you will receive a proxy that is configured by default for twoway invocations. If you require the proxy to have a different configuration, you can use the proxy factory methods described in Section 28.10.2. As an example, the code below demonstrates how to configure the proxy for oneway invocations:
// C++
Ice::ObjectAdapterPtr adapter = ...;
Ice::Identity id = ...;
Ice::ObjectPrx proxy = adapter->createProxy(id)->ice_oneway();
You can also instruct the object adapter to use a different default proxy configuration by setting the property name.ProxyOptions. For example, the following property causes the object adapter to return proxies that are configured for oneway invocations by default:
MyAdapter.ProxyOptions=-o
See Appendix C for more information on this property.

28.4.8 Using Multiple Object Adapters

A typical server rarely needs to use more than one object adapter. If you are considering using multiple object adapters, we suggest that you check whether any of the items in the list below apply to your situation:
• You need fine-grained control over which objects are accessible. For example, you could have an object adapter with only secure endpoints to restrict access to some administrative objects, and another object adapter with non-secure endpoints for other objects. Because an object adapter is associated with one or more transport endpoints, you can firewall a particular port, so objects associated with the corresponding endpoint cannot be reached unless the firewall rules are satisfied.
• You need control over the number of threads in the pools for different sets of objects in your application. For example, you may not need concurrency on the objects connected to a particular object adapter, and multiple object adapters, each with its own thread pool, can be useful to solve deadlocks. See Section 28.9.5 for more information on dealing with deadlocks.
• You want to be able to temporarily disable processing new requests for a set of objects. This can be accomplished by placing an object adapter in the holding state.
• You want to set up different request routing when using an Ice router with Glacier2.
If none of the preceding items apply, chances are that you do not need more than one object adapter.
Table of Contents Previous Next
Logo