Dispatch Interceptors
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, Objective-C and Swift. They are defined through a native API in each programming language.
On this page:
Dispatch Interceptor Position
Once the object adapter finds the servant identified as the target of a request, it logically calls dispatch
on this servant, and dispatch
selects the servant's method that corresponds to the operation name carried by the request and later unmarshals the request parameters for this operation.
In all programming languages except Swift, it is the servant itself that provides this dispatch
method, and this method is called ice_dispatch
to avoid conflicts with your own methods. In Swift, dispatch
is provided by a separate request dispatcher object (typically a struct) that adopts protocol Disp
and holds the actual servant. We will refer to the object that provides the dispatch
method as the request dispatcher–a synonym for servants in all programming languages except Swift.
A dispatch interceptor is simply a request dispatcher that you insert in front of another request dispatcher. This other request dispatcher can be the real request dispatcher that unmarshals the parameters carried by the request and executes the operation. It could also be another dispatch interceptor: this way, you can create a chain of dispatch interceptors, with the real request dispatcher at the end of this chain.
You register a dispatch interceptor with an object adapter like any other request dispatcher: 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 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. (Do not confuse dispatchers with request dispatchers).
- The Ice run time locates the target request dispatcher (this may call
locate
on a servant locator). In all programming languages except Swift, this request dispatcher is the servant. - Assuming the target request dispatcher is a dispatch interceptor, it calls
dispatch
on the next request dispatcher (which could be another dispatch interceptor); eventually a dispatch interceptor callsdispatch
on the real request dispatcher. - The real servant eventually 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 request dispatcher (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
In programming languages other than Swift, 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
public typealias Request = ... // no public API
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.
Swift
In Swift, Request should be considered opaque, and you not call any of its methods.
The native class DispatchInterceptor
provides an abstract method or function dispatch
that you need to implement. In Swift, the base protocol for dispatch interceptors is Disp
.
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.
public protocol Disp { func dispatch(request: Request, current: Current) throws -> Promise<OutputStream>? }
Using a Dispatch Interceptor
Your implementation of the dispatch
function or method must dispatch the request to the actual request dispatcher. Here is a very simple example implementation of an interceptor that dispatches the request to the request dispatcher 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; };
struct InterceptorI: Disp { private let target: Disp init(_ target: Disp) { self.target = target } func dispatch(request: Request, current: Current) throws -> Promise<OutputStream>? { try return target.dispatch(request: request, current: current) } }
Note that our implementation of dispatch
calls ice_dispatch
on the target request dispatcher 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"));
let servant = ExampleI() let interceptor = InterceptorI(ExampleDisp(servant)) adapter.add(servant: interceptor, id: 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 or Promises (C#, Java and Swift)
In C#, Java and Swift, ice_dispatch
(dispatch
in Swift) returns a non-null Task<Ice.OutputStream>
(C#), CompletionStage<OutputStream>
(Java) or Promise<OutputStream>
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; ... }
public protocol Disp { func dispatch(request: Request, current: Current) throws -> Promise<OutputStream>? }
Your dispatch interceptor can be notified of the completion of an asynchronous dispatch by registering an action with this Task
, CompletionStage
or Promise
(using ContinueWith
in C#, whenComplete
in Java and then
, map
or done
in Swift).
Your dispatch interceptor can also forward several times the same request to the real request dispatcher and only return the Task,
CompletionStage
or Promise
provided by the "good" dispatch back to Ice; the Task
, CompletionStage
or Promise
returned by the other dispatches are simply discarded.