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:
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 <adapter‑name>.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.
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.
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).
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.
module Ice {
local interface ObjectAdapter {
string getName();
Communicator getCommunicator();
// ...
};
};
•
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.
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.
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 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.)
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.)
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.
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.
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.
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).
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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:
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:
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.
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.
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:
Similarly, the configuration for host Sun2 retains the same published 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.
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.
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.
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);
// ...
};
};
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).
The createDirectProxy operation returns a direct proxy containing the adapter’s published endpoints (see
Section 28.4.6).
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.
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:
See Appendix C for more information on this property.
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.