1. An abstract callback class whose name is formed using the pattern
AMI_class_op. For example, an operation named
foo defined in interface
I results in a class named
AMI_I_foo. The class is generated in the same scope as the interface or class containing the operation. Two methods must be defined by the subclass:
2.
An additional proxy method, having the mapped name of the operation with the suffix
_async. This method returns a boolean indicating whether the request was sent synchronously. The first parameter is a smart pointer to an instance of the callback class described above. The remaining parameters comprise the
in parameters of the operation, in the order of declaration.
interface I {
["ami"] int foo(short s, out long l);
};
class AMI_I_foo : public ... {
public:
virtual void ice_response(Ice::Int, Ice::Long) = 0;
virtual void ice_exception(const Ice::Exception&) = 0;
};
typedef IceUtil::Handle<AMI_I_foo> AMI_I_fooPtr;
bool foo_async(const AMI_I_fooPtr&, Ice::Short);
The overview on page 2036 describes proxy methods and callback objects in greater detail.
1. An abstract callback class whose name is formed using the pattern
AMI_class_op. For example, an operation named
foo defined in interface
I results in a class named
AMI_I_foo. The class is generated in the same scope as the interface or class containing the operation. Three methods must be defined by the subclass:
public void ice_response(<params>);
public void ice_exception(Ice.LocalException ex);
public void ice_exception(Ice.UserException ex);
2.
An additional proxy method, having the mapped name of the operation with the suffix
_async. This method returns a boolean indicating whether the request was sent synchronously. The first parameter is a reference to an instance of the callback class described above. The remaining parameters comprise the
in parameters of the operation, in the order of declaration.
interface I {
["ami"] int foo(short s, out long l);
};
public abstract class AMI_I_foo extends ... {
public abstract void ice_response(int __ret, long l);
public abstract void ice_exception(Ice.LocalException ex);
public abstract void ice_exception(Ice.UserException ex);
}
public boolean foo_async(AMI_I_foo __cb, short s);
public boolean foo_async(AMI_I_foo __cb, short s,
java.util.Map<String, String> __ctx);
The overview on page 2036 describes proxy methods and callback objects in greater detail.
1. An abstract callback class whose name is formed using the pattern
AMI_class_op. For example, an operation named
foo defined in interface
I results in a class named
AMI_I_foo. The class is generated in the same scope as the interface or class containing the operation. Two methods must be defined by the subclass:
2.
An additional proxy method, having the mapped name of the operation with the suffix
_async. This method returns a boolean indicating whether the request was sent synchronously. The first parameter is a reference to an instance of the callback class described above. The remaining parameters comprise the in parameters of the operation, in the order of declaration.
interface I {
["ami"] int foo(short s, out long l);
};
public abstract class AMI_I_foo : ...
{
public abstract void ice_response(int __ret, long l);
public abstract void ice_exception(Ice.Exception ex);
}
bool foo_async(AMI_I_foo __cb, short s);
bool foo_async(AMI_I_foo __cb, short s,
Dictionary<string, string> __ctx);
The overview on page 2036 describes proxy methods and callback objects in greater detail.
For each AMI operation, the Python mapping emits an additional proxy method having the mapped name of the operation with the suffix
_async. This method returns a boolean indicating whether the request was sent synchronously. The first parameter is a reference to a callback object; the remaining parameters comprise the
in parameters of the operation, in the order of declaration.
Unlike the mappings for strongly-typed languages, the Python mapping does not generate a callback class for asynchronous operations. In fact, the callback object’s type is irrelevant; the Ice run time simply requires that it define the
ice_response and
ice_exception methods:
interface I {
["ami"] int foo(short s, out long l);
};
class ...
#
# Operation signatures:
#
# def ice_response(self, _result, l)
# def ice_exception(self, ex)
def foo_async(self, __cb, s)
The overview on page 2036 describes proxy methods and callback objects in greater detail.
module Demo {
sequence<float> Row;
sequence<Row> Grid;
exception RangeError {};
interface Model {
["ami"] Grid interpolate(Grid data, float factor)
throws RangeError;
};
};
Given a two-dimensional grid of floating point values and a factor, the interpolate operation returns a new grid of the same size with the values interpolated in some interesting (but unspecified) way. In the sections below, we present C++, Java, and C# clients that invoke
interpolate using AMI.
class AMI_Model_interpolateI : public Demo::AMI_Model_interpolate
{
public:
virtual void ice_response(const Demo::Grid& result)
{
cout << "received the grid" << endl;
// ... postprocessing ...
}
virtual void ice_exception(const Ice::Exception& ex)
{
try {
ex.ice_throw();
} catch (const Demo::RangeError& e) {
cerr << "interpolate failed: range error" << endl;
} catch (const Ice::LocalException& e) {
cerr << "interpolate failed: " << e << endl;
}
}
};
The implementation of ice_response reports a successful result, and
ice_exception displays a diagnostic if an exception occurs.
The code to invoke interpolate is equally straightforward:
Demo::ModelPrx model = ...;
AMI_Model_interpolatePtr cb = new AMI_Model_interpolateI;
Demo::Grid grid;
initializeGrid(grid);
model‑>interpolate_async(cb, grid, 0.5);
After obtaining a proxy for a Model object, the client instantiates a callback object, initializes a grid and invokes the asynchronous version of
interpolate. When the Ice run time receives the response to this request, it invokes the callback object supplied by the client.
class AMI_Model_interpolateI extends Demo.AMI_Model_interpolate {
public void ice_response(float[][] result)
{
System.out.println("received the grid");
// ... postprocessing ...
}
public void ice_exception(Ice.UserException ex)
{
assert(ex instanceof Demo.RangeError);
System.err.println("interpolate failed: range error");
}
public void ice_exception(Ice.LocalException ex)
{
System.err.println("interpolate failed: " + ex);
}
}
The implementation of ice_response reports a successful result, and the
ice_exception methods display a diagnostic if an exception occurs.
The code to invoke interpolate is equally straightforward:
Demo.ModelPrx model = ...;
AMI_Model_interpolate cb = new AMI_Model_interpolateI();
float[][] grid = ...;
initializeGrid(grid);
model.interpolate_async(cb, grid, 0.5);
After obtaining a proxy for a Model object, the client instantiates a callback object, initializes a grid and invokes the asynchronous version of
interpolate. When the Ice run time receives the response to this request, it invokes the callback object supplied by the client.
using System;
class AMI_Model_interpolateI : Demo.AMI_Model_interpolate {
public override void ice_response(float[][] result)
{
Console.WriteLine("received the grid");
// ... postprocessing ...
}
public override void ice_exception(Ice.Exception ex)
{
Console.Error.WriteLine("interpolate failed: " + ex);
}
}
The implementation of ice_response reports a successful result, and the
ice_exception method displays a diagnostic if an exception occurs.
The code to invoke interpolate is equally straightforward:
Demo.ModelPrx model = ...;
AMI_Model_interpolate cb = new AMI_Model_interpolateI();
float[][] grid = ...;
initializeGrid(grid);
model.interpolate_async(cb, grid, 0.5);
class AMI_Model_interpolateI(object):
def ice_response(self, result):
print "received the grid"
# ... postprocessing ...
def ice_exception(self, ex):
try:
raise ex
except Demo.RangeError, e:
print "interpolate failed: range error"
except Ice.LocalException, e:
print "interpolate failed: " + str(e)
The implementation of ice_response reports a successful result, and the
ice_exception method displays a diagnostic if an exception occurs.
The code to invoke interpolate is equally straightforward:
model = ...
cb = AMI_Model_interpolateI()
grid = ...
initializeGrid(grid)
model.interpolate_async(cb, grid, 0.5)
Support for asynchronous invocations in Ice is enabled by the client thread pool (see
Section 32.10), whose threads are primarily responsible for processing reply messages. It is important to understand the concurrency issues associated with asynchronous invocations:
•
Calls to the callback object are always made by threads from an Ice thread pool, therefore synchronization may be necessary if the application might interact with the callback object at the same time as the reply arrives. Furthermore, since the Ice run time never invokes callback methods from the client’s calling thread, the client can safely make AMI invocations while holding a lock without risk of a deadlock.
•
The number of threads in the client thread pool determines the maximum number of simultaneous callbacks possible for asynchronous invocations. The default size of the client thread pool is one, meaning invocations on callback objects are serialized. If the size of the thread pool is increased, the application may require synchronization, and replies can be dispatched out of order. The client thread pool can also be configured to serialize messages received over a connection so that AMI replies from a connection are dispatched in the order they are received (see
Section 32.10.4).
The Ice run time queues asynchronous requests when necessary to avoid blocking the calling thread, but places no upper limit on the number of queued requests or the amount of memory they can consume. To prevent unbounded memory utilization, Ice provides the infrastructure necessary for an application to implement its own flow-control logic.
•
The ice_sent method in the AMI callback object
The return value of the proxy method determines whether the request was queued. If the proxy method returns true, no flow control is necessary because the request was accepted by the local transport buffer and therefore the Ice run time did not need to queue it. In this situation, the Ice run time does not invoke the
ice_sent method on the callback object; the return value of the proxy method is sufficient notification that the request was sent.
If the proxy method returns false, the Ice run time has queued the request. Now the application must decide how to proceed with subsequent invocations:
To indicate its interest in receiving ice_sent invocations, an AMI callback object must also derive from the C++ class
Ice::AMISentCallback:
namespace Ice {
class AMISentCallback {
public:
virtual ~AMISentCallback();
virtual void ice_sent() = 0;
};
}
We can modify the example on page 2042 to include an
ice_sent callback as shown below:
class AMI_Model_interpolateI :
public Demo::AMI_Model_interpolate,
public Ice::AMISentCallback
{
public:
// ...
virtual void ice_sent()
{
cout << "request sent successfully" << endl;
}
};
To indicate its interest in receiving ice_sent invocations, an AMI callback object must also implement the Java interface
Ice.AMISentCallback:
package Ice;
public interface AMISentCallback {
void ice_sent();
}
We can modify the example on page 2043 to include an
ice_sent callback as shown below:
class AMI_Model_interpolateI
extends Demo.AMI_Model_interpolate
implements Ice.AMISentCallback {
// ...
public void ice_sent()
{
System.out.println("request sent successfully");
}
}
To indicate its interest in receiving ice_sent invocations, an AMI callback object must also implement the C# interface
Ice.AMISentCallback:
namespace Ice {
public interface AMISentCallback
{
void ice_sent();
}
}
We can modify the example on page 2044 to include an
ice_sent callback as shown below:
class AMI_Model_interpolateI :
Demo.AMI_Model_interpolate,
Ice.AMISentCallback {
// ...
public void ice_sent()
{
Console.Out.WriteLine("request sent successfully");
}
}
class AMI_Model_interpolateI :
Demo.AMI_Model_interpolate,
Ice.AMISentCallback {
// ...
public void ice_sent()
{
Console.Out.WriteLine("request sent successfully");
}
}
To indicate its interest in receiving ice_sent invocations, an AMI callback object need only define the
ice_sent method.
We can modify the example on page 2045 to include an
ice_sent callback as shown below:
class AMI_Model_interpolateI(object):
# ...
def ice_sent(self):
print "request sent successfully"
Applications that send batched requests (see Section 32.16) can either flush a batch explicitly or allow the Ice run time to flush automatically. The proxy method
ice_flushBatchRequests performs an immediate flush using the synchronous invocation model and may block the calling thread until the entire message can be sent. Ice also provides an asynchronous version of this method for applications that wish to flush batch requests without the risk of blocking.
The proxy method ice_flushBatchRequests_async initiates an asynchronous flush. Its only argument is a callback object; this object must define an
ice_exception method for receiving a notification if an error occurs before the message is sent.
If the application is interested in flow control (see
page 2046), the return value of
ice_flushBatchRequests_async is a boolean indicating whether the message was sent synchronously. Furthermore, the callback object can define an
ice_sent method that is invoked when an asynchronous flush completes.
The base proxy class ObjectPrx defines the asynchronous flush operation as shown below:
namespace Ice {
class ObjectPrx : ... {
public:
// ...
bool ice_flushBatchRequests_async(
const Ice::AMI_Object_ice_flushBatchRequestsPtr& cb)
};
}
namespace Ice {
class AMI_Object_ice_flushBatchRequests : ... {
public:
virtual void ice_exception(const Ice::Exception& ex) = 0;
};
}
class MyFlushCallbackI :
public Ice::AMI_Object_ice_flushBatchRequests,
public Ice::AMISentCallback
{
public:
virtual void ice_exception(const Ice::Exception& ex);
virtual void ice_sent();
};
The base proxy class ObjectPrx defines the asynchronous flush operation as shown below:
package Ice;
public class ObjectPrx ... {
// ...
boolean ice_flushBatchRequests_async(
AMI_Object_ice_flushBatchRequests cb);
}
package Ice;
public abstract class AMI_Object_ice_flushBatchRequests ...
{
public abstract void ice_exception(LocalException ex);
}
class MyFlushCallbackI
extends Ice.AMI_Object_ice_flushBatchRequests
implements Ice.AMISentCallback
{
public void ice_exception(Ice.LocalException ex) { ... }
public void ice_sent() { ... }
}
The base proxy class ObjectPrx defines the asynchronous flush operation as shown below:
namespace Ice {
public class ObjectPrx : ... {
// ...
bool ice_flushBatchRequests_async(
AMI_Object_ice_flushBatchRequests cb);
}
}
namespace Ice {
public abstract class AMI_Object_ice_flushBatchRequests ... {
public abstract void ice_exception(Ice.Exception ex);
}
}
class MyFlushCallbackI : Ice.AMI_Object_ice_flushBatchRequests,
Ice.AMISentCallback
{
public override void
ice_exception(Ice.LocalException ex) { ... }
public void ice_sent() { ... }
}
def ice_flushBatchRequests_async(self, cb)
The cb argument represents a callback object that must implement an
ice_exception method. As an example, the class below demonstrates how to define a callback class that also receives a notification when the asynchronous flush completes:
class MyFlushCallbackI(object):
def ice_exception(self, ex):
# handle an exception
def ice_sent(self):
# flush has completed
Timeouts for asynchronous invocations behave like those for synchronous invocations: an
Ice::TimeoutException is raised if the response is not received within the given time period. In the case of an asynchronous invocation, however, the exception is reported to the
ice_exception method of the invocation’s callback object. For example, we can handle this exception in C++ as shown below:
class AMI_Model_interpolateI : public Demo::AMI_Model_interpolate
{
public:
// ...
virtual void ice_exception(const Ice::Exception& ex)
{
try {
ex.ice_throw();
} catch (const Demo::RangeError& e) {
cerr << "interpolate failed: range error" << endl;
} catch (const Ice::TimeoutException&) {
cerr << "interpolate failed: timeout" << endl;
} catch (const Ice::LocalException& e) {
cerr << "interpolate failed: " << e << endl;
}
}
};
It is important to remember that all errors encountered by an AMI invocation (except
CommunicatorDestroyedException) are reported back via the
ice_exception callback, even if the error condition is encountered “on the way out”, when the operation is invoked. The reason for this is consistency: if an invocation, such as
foo_async could throw exceptions, you would have to handle exceptions in two places in your code: at the point of call for exceptions that are encountered “on the way out”, and in
ice_exception for error conditions that are detected after the call is initiated.
p1‑>foo_async(cb1);
p2‑>bar_async(cb2);
If bar depends for its correct working on the successful completion of
foo, this code will not work because the
bar invocation will be sent regardless of whether
foo failed or not.
In such cases, where you need to be sure that one call is dispatched only if a preceding call succeeds, you must instead invoke
bar from within
foo’s
ice_response implementation, instead of from the main-line code.
AMI invocations cannot be sent using collocated optimization. If you attempt to invoke an AMI operation using a proxy that is configured to use collocation optimization, the Ice run time will raise
CollocationOptimizationException if the servant happens to be collocated; the request is sent normally if the servant is not collocated.
Section 32.21 provides more information about this optimization and describes how to disable it when necessary.