Table of Contents Previous Next
Logo
Deprecated AMI Mapping : K.3 Using AMI
Copyright © 2003-2010 ZeroC, Inc.

K.3 Using AMI

In this section, we describe the Ice implementation of AMI and how to use it. We begin by discussing a way to (partially) simulate AMI using oneway invocations. This is not a technique that we recommend, but it is an informative exercise that highlights the benefits of AMI and illustrates how it works. Next, we explain the AMI mapping and illustrate its use with examples.

Simulating AMI using Oneways

As we discussed at the beginning of the chapter, synchronous invocations are not appropriate for certain types of applications. For example, an application with a graphical user interface typically must avoid blocking the window system’s event dispatch thread because blocking makes the application unresponsive to user commands. In this situation, making a synchronous remote invocation is asking for trouble.
The application could attempt to avoid this situation using oneway invocations (see Section 32.14), which by definition cannot return a value or have any out parameters. Since the Ice run time does not expect a reply, the invocation blocks only as long as it takes to establish a connection (if necessary), marshal the request, and copy the message into the local transport buffer. However, these network activities may still block. Furthermore, the use of oneway invocations may require unacceptable changes to the interface definitions. For example, a twoway invocation that returns results or raises user exceptions must be converted into at least two operations: one for the client to invoke with oneway semantics that contains only in parameters, and one (or more) for the server to invoke to notify the client of the results.
To illustrate these changes, suppose that we have the following Slice defini­tion:
interface I {
  int op(string s, out long l);
};
In its current form, the operation op is not suitable for a oneway invocation because it has an out parameter and a non-void return type. In order to accommo­date a oneway invocation of op, we can change the Slice definitions as shown below:
interface ICallback {
  void opResults(int result, long l);
};

interface I {
  void op(ICallback* cb, string s);
};
We made several modifications to the original definition:
• We added interface ICallback, containing an operation opResults whose arguments represent the results of the original twoway operation. The server invokes this operation to notify the client of the completion of the operation.
• We modified I::op to be compliant with oneway semantics: it now has a void return type, and takes only in parameters.
• We added a parameter to I::op that allows the client to supply a proxy for its callback object.
As you can see, we have made significant changes to our interface definitions to accommodate the implementation requirements of the client. One ramification of these changes is that the client must now also be a server, because it must create an instance of ICallback and register it with an object adapter in order to receive notifications of completed operations.
A more severe ramification, however, is the impact these changes have on the type system, and therefore on the server. Whether a client invokes an operation synchronously or asynchronously should be irrelevant to the server; this is an arti­fact of behavior that should have no impact on the type system. By changing the type system as shown above, we have tightly coupled the server to the client, and eliminated the ability for op to be invoked synchronously.
To make matters even worse, consider what would happen if op could raise user exceptions. In this case, ICallback would have to be expanded with addi­tional operations that allow the server to notify the client of the occurrence of each exception. Since exceptions cannot be used as parameter or member types in Slice, this quickly becomes a difficult endeavor, and the results are likely to be equally difficult to use.
At this point, you will hopefully agree that this technique is flawed in many ways, so why do we bother describing it in such detail? The reason is that the Ice implementation of AMI uses a strategy similar to the one described above, with several important differences:
1. No changes to the type system are required in order to use AMI. The on-the-wire representation of the data is identical, therefore synchronous and asyn­chronous clients and servers can coexist in the same system, using the same operations.
2. The AMI solution accommodates exceptions in a reasonable way.
3. Using AMI does not require the client to also be a server.
4. Ice guarantees that AMI requests never block the calling thread.

Overview

AMI operations have the same semantics in all of the language mappings that support asynchronous invocations. This section provides a language-independent introduction to the AMI model.

Proxy Method

Annotating a Slice operation with the AMI metadata tag does not prevent an application from invoking that operation using the traditional synchronous model. Rather, the presence of the metadata extends the proxy with an asynchronous version of the operation, so that invocations can be made using either model.
The asynchronous operation never blocks the calling thread. If the message cannot be accepted into the local transport buffer without blocking, the Ice run time queues the request and immediately returns control to the calling thread.
The parameters of the asynchronous operation are modified similar to the example from page 2034: the first argument is a callback object (described below), followed by any in parameters in the order of declaration. The opera­tion’s return value and out parameters, if any, are passed to the callback object when the response is received.
The asynchronous operation only raises CommunicatorDestroyedException directly; all other exceptions are reported to the callback object. See page 2053 for more information on error handling.
Finally, the return value of the asynchronous operation is a boolean that indi­cates whether the Ice run time was able to send the request synchronously; that is, whether the entire message was immediately accepted by the local transport buffer. An application can use this value to implement flow control (see page 2046).

Callback Object

The asynchronous operation requires the application to supply a callback object as the first argument. This object is an instance of an application-defined class; in strongly-typed languages this class must inherit from a superclass generated by the Slice compiler. In contrast to the example on page 2034, the callback object is a purely local object that is invoked by the Ice run time in the client, and not by the remote server.
The Ice run time always invokes methods of the callback object from a thread in an Ice thread pool, and never from the thread that is invoking the asynchronous operation. Exceptions raised by a callback object are ignored but may cause the Ice run time to log a warning message (see the description of Ice.Warn.AMICallback in Appendix D).
The callback class must define two methods:
• ice_response
The Ice run time invokes ice_response to supply the results of a successful twoway invocation; this method is not invoked for oneway invoca­tions. The arguments to ice_response consist of the return value (if the operation returns a non-void type) followed by any out parameters in the order of declaration.
• ice_exception
This method is called if an error occurs during the invocation. As explained on page 2053, the only exception that can be raised to the thread invoking the asynchronous operation is CommunicatorDestroyedException; all other errors, including user exceptions, are passed to the callback object via its ice_exception method. In the case of a oneway invocation, ice_exception is only invoked if an error occurs before the request is sent.
For an asynchronous invocation, the Ice run time calls ice_response or ice_exception, but not both. It is possible for one of these methods to be called before control returns to the thread that is invoking the operation.
A callback object may optionally define a third method:
• ice_sent
The ice_sent method is invoked when the entire message has been passed to the local transport buffer. The Ice run time does not invoke ice_sent if the asynchronous operation returned true to indicate that the message was sent synchronously. An application must make no assumptions about the order of invocations on a callback object; ice_sent can be called before, after, or concurrently with ice_response or ice_exception. Refer to page 2046 for more information about the purpose of this method.

Table of Contents Previous Next
Logo