A dispatch interceptor is a server-side mechanism that allows you to intercept incoming client requests before they are given to a servant. The interceptor can examine the incoming request and in particular its Current
information.
A dispatch interceptor can dispatch a request to a servant and check whether the dispatch was successful; if not, the interceptor can choose to retry the dispatch. This functionality is useful to automatically retry requests that have failed due to a recoverable error condition, such as a database deadlock exception.
Dispatch Interceptors are currently available in C++11, C++98, C#, Java, Java Compat and Objective-C. They are defined through a native API in each programming language.
On this page:
Dispatch Interceptor Position
A dispatch interceptor is a servant that delegates requests to another servant. This other servant can be the real servant that unmarshals the parameters carried by the request and implements the target operation. It could also be another dispatch interceptor: this way, you can create a chain of dispatch interceptors, with the real servant at the end of this chain.
You register a dispatch interceptor with an object adapter like any other servant: it can be inserted into this object adapter's Active Servant Map, or registered as a default servant, or returned by a servant locator.
Dispatch interceptors are most often registered as default servants.
A dispatch follows these steps:
- The Ice run time reads the incoming request using a thread from its server thread pool (or object adapter thread pool if one is configured, or client thread pool for requests over bidir connections).
- If a dispatcher is configured, the Ice run time gives the request to the dispatcher to allow the dispatcher to execute this request in a different thread.
- The Ice run time locates the target servant (this may call
locate
on a servant locator). - Assuming the target servant is a dispatch interceptor, it calls
ice_dispatch
on the next servant (which could be another dispatch interceptor); eventually a dispatch interceptor callsice_dispatch
on the real servant. - The real servant unmarshals the input parameters, executes the operation and then:
- marshals the return and out parameters,
- throws an exception,
- or does not complete synchronously (if it's implemented using AMD).
- The dispatch interceptor(s) now have the opportunity to execute code "on the way out", after completion of the dispatch by the real servant (with AMD, only the synchronous part of this dispatch has completed).
- For a synchronous dispatch through a servant locator, the Ice run time calls
finished
on the servant locator. - For a synchronous dispatch, the Ice run time sends the reply to the client: the return and out parameters previously marshaled by the real servant, or an exception caught and marshaled by the Ice run time.
A dispatch interceptor can also loop through steps 4 and 6 and dispatch several times the same request before returning the result to Ice. For example:
bool MyDispatchInterceptor::dispatch(Ice::Request& request) { for(;;) { try { TransactionHolder tx(_db); bool sync = findServant(request.getCurrent().id)->ice_dispatch(request); assert(sync); // this example works only for synchronous dispatch tx.commit(); return sync; } catch(const DeadlockException&) { // Run this dispatch again, with the same request // } } }
Dispatch Interceptor API
You create a dispatch interceptor by providing a class that derives from the DispatchInterceptor
abstract class and implements the dispatch
function or method. The job of dispatch
is to pass the request to the servant and to return the value returned by the servant's ice_dispatch
call.
The Ice run time provides basic information about the request to the dispatch
function in the form of a Request
object:
namespace Ice { class Request { public: virtual ~Request(); virtual const Current& getCurrent() = 0; }; }
namespace Ice { class Request { public: virtual ~Request(); virtual const Current& getCurrent() = 0; }; }
namespace Ice { public interface Request { Current getCurrent(); } }
package com.zeroc.Ice; public interface Request { Current getCurrent(); }
package Ice; public interface Request { Current getCurrent(); }
@protocol ICERequest <NSObject> -(ICECurrent*) getCurrent; @end
getCurrent
provides access to the Current
object for the request, which provides access to information about the request, such as the object identity of the target object, the object adapter used to dispatch the request, and the operation name.
Note that Request
, for performance reasons, is not thread-safe. This means that you must not concurrently dispatch from different threads using the same Request
object. (Concurrent dispatch for different requests does not cause any problems.). We also recommend that you do not change thread in your dispatch interceptor as this would defeat the dispatcher mechanism.
namespace Ice { class DispatchInterceptor : public virtual Object { public: virtual bool dispatch(Request&) = 0; }; }
The dispatch
return value is a bool that indicates whether dispatch was executed synchronously (true
) or asynchronously with AMD (false
). You must return the value returned by the servant ice_dispatch
function, or throw an exception.
namespace Ice { class DispatchInterceptor : public virtual Object { public: virtual bool dispatch(Request&) = 0; }; typedef IceInternal::Handle<DispatchInterceptor> DispatchInterceptorPtr; }
The dispatch
return value is a bool that indicates whether dispatch was executed synchronously (true
) or asynchronously with AMD (false
). You must return the value returned by the servant ice_dispatch
function, or throw an exception.
namespace Ice { public abstract class DispatchInterceptor : Ice.ObjectImpl { public abstract System.Threading.Tasks.Task<Ice.OutputStream> dispatch(Request request); } }
The dispatch
return value is a Task
instance if the request was dispatched asynchronously with AMD, or null
if the request was dispatched synchronously. You must return the same value as returned by the servant ice_dispatch
method, or a continuation created from the returned Task
, or throw an exception. You can check for the completion of the Task
and retry a dispatch if for example the task completed with an exception. It is fine to abandon a returned Task
to retry a dispatch.
If the servant implementation uses the async
keyword for the dispatch of a request, ice_dispatch
always returns a Task
object even if the dispatch completed synchronously with a response or exception.
package com.zeroc.Ice; import java.util.concurrent.CompletionStage; public abstract class DispatchInterceptor implements com.zeroc.Ice.Object { public abstract CompletionStage<OutputStream> dispatch(Request request) throws UserException; ... }
The dispatch
return value is a CompletionStage<OutputStream>
if the request was dispatched asynchronously with AMD, or null
if the request was dispatched synchronously. You must return the value returned by the servant ice_dispatch
method, or a dependent ContinuationStage
, or throw an exception. You can check for the completion of the CompletionStage
and retry a dispatch if for example the request completed with an exception. It is fine to abandon a returned CompletionStage
to retry a dispatch.
package Ice; public abstract class DispatchInterceptor extends ObjectImpl { public abstract boolean dispatch(Request request) throws Ice.UserException; }
The dispatch
return value is a boolean that indicates whether dispatch was executed synchronously (true
) or asynchronously with AMD (false
). You must return the value returned by the servant ice_dispatch
function or throw an exception.
@protocol ICEDispatchInterceptor <ICEObject> -(void) dispatch:(id<ICERequest>)request; @end
The Objective-C mapping does not support asynchronous dispatch (AMD), therefore the return type of the dispatch
method is void.
Using a Dispatch Interceptor
Your implementation of the dispatch
function or method must dispatch the request to the actual servant. Here is a very simple example implementation of an interceptor that dispatches the request to the servant passed to the interceptor's constructor:
class InterceptorI : public Ice::DispatchInterceptor { public: InterceptorI(std::shared_ptr<Ice::Object> servant) : _servant(std::move(servant)) { } virtual bool dispatch(Ice::Request& request) override { return _servant->ice_dispatch(request); } std::shared_ptr<Ice::Object> _servant; };
class InterceptorI : public Ice::DispatchInterceptor { public: InterceptorI(const Ice::ObjectPtr& servant) : _servant(servant) { } virtual bool dispatch(Ice::Request& request) { return _servant->ice_dispatch(request); } Ice::ObjectPtr _servant; };
Note that our implementation of dispatch
calls ice_dispatch
on the target servant to dispatch the request. ice_dispatch
does the work of actually invoking the operation. Also note that dispatch
returns whatever is returned by ice_dispatch
.
We can use this interceptor to intercept requests to a servant of any type as follows:
auto servant = std::make_shared<ExampleI>(); auto interceptor = std::make_shared<InterceptorI>(std::move(servant)); // give the servant to the interceptor adapter->add(interceptor, Ice::stringToIdentity("ExampleServant"));
ExampleIPtr servant = new ExampleI; Ice::DispatchInterceptorPtr interceptor = new InterceptorI(servant); adapter->add(interceptor, Ice::stringToIdentity("ExampleServant"));
Dispatch Interceptor with Asynchronous Method Dispatch (AMD)
When the real servant dispatches a request asynchronously, the result (or exception) is typically not available "on the way out" in the dispatching thread. This creates two challenges:
- The dispatch interceptor may want to be notified when the dispatch completes.
- The dispatch interceptor may want to retry the initial dispatch and prevent earlier dispatch attempts from returning a response or exception to the client.
The solution depends on the language mapping:
Using Callbacks (C++11, C++98, Java Compat)
With C++11, ice_dispatch
on servants accepts two optional function parameters:
namespace Ice { class Object { public: virtual bool ice_dispatch(Ice::Request& request, std::function<bool()> response = nullptr, std::function<bool(std::exception_ptr)> error = nullptr); ... }; }
When a servant dispatches a request asynchronously, ice_dispatch
returns false and then response
or error
is called when the dispatch completes. Both response
and error
(that you supply) return a bool
parameter that indicates whether Ice should send the response or exception to the client (when the return value is true), or just ignore this result (when the return value is false). A null response
or error
function is equivalent to a function that returns true.
Occasionally, you may attempt to re-dispatch a request that has already completed–meaning Ice has already sent the response or exception from a previous attempt back to the client. In this case, your dispatch interceptor will receive a ResponseSentException
: it should then rethrow this exception or return true (like for a synchronous dispatch).
With C++98 and Java Compat, ice_dispatch
on servants accepts a comparable DispatchInterceptorAsyncCallback
parameter:
namespace Ice { class DispatchInterceptorAsyncCallback : public virtual IceUtil::Shared { public: virtual ~DispatchInterceptorAsyncCallback(); virtual bool response() = 0; virtual bool exception(const std::exception&) = 0; virtual bool exception() = 0; }; typedef IceUtil::Handle<DispatchInterceptorAsyncCallback> DispatchInterceptorAsyncCallbackPtr; class Object : public virtual IceUtil::Shared { public: virtual bool ice_dispatch(Ice::Request& request, const DispatchInterceptorAsyncCallbackPtr& = 0); ... }; }
package Ice; public interface DispatchInterceptorAsyncCallback { boolean response(); boolean exception(java.lang.Exception ex); } public interface Object { boolean ice_dispatch(Request request, DispatchInterceptorAsyncCallback cb) throws Ice.UserException; boolean ice_dispatch(Request request) throws Ice.UserException; ... }
Using Futures (C# and Java)
In C# and Java, ice_dispatch
returns a non-null Task<Ice.OutputStream>
(C#) or CompletionStage<OutputStream>
(Java) for asynchronous dispatches:
namespace Ice { public interface Object : ICloneable { Task<OutputStream> ice_dispatch(Request request); .... } }
package com.zeroc.Ice; public interface Object { CompletionStage<OutputStream> ice_dispatch(Request request) throws Ice.UserException; ... }
Your dispatch interceptor can be notified of the completion of an asynchronous dispatch by registering an action with this Task
or CompletionStage
(using ContinueWith
in C# or for example whenComplete
in Java).
Your dispatch interceptor can also forward several times the same request to the real servant and only return the Task
or CompletionStage
provided by the "good" dispatch back to Ice; the Task
or CompletionStage
returned by the other dispatches are simply discarded.