HOWTO Cancel a Request using the RTI Connext DDS Request-Reply API

The RTI Connext DDS Request-Reply API provides a simple mechanism to cancel long-running requests after a client has lost interest. Although you can implement the same cancellation mechanism at the application-level (e.g., adding flags to your data type to indicate the cancellation or creating different topics) the data-centric approach we present in this article provides a simpler more elegant solution.

RTI Connext DDS uses DDS data types for sending and receiving requests and replies. As a result, each specific request gets translated into a DDS instance. To properly manage different requests, you need a mechanism to identify each request at the DDS level. That is, a way to identify the DDS instance each request is associated to. The simplest way of doing this is to define a set of key members in the data type associated to your request. This set of keys will univocally identify your request and let you operate on it whether you want to update it or just cancel it.

To cancel a request you will need to dispose the DDS instance the request is associated to. That is, you will need to call the dispose() method on the request's DataWriter. Upon the reception of the dispose() operation, the Requester will send an invalid sample to inform all the matched Repliers of that it is no longer interested in a reply for the request associated to that instance.

The following code snippet shows the definition of FooRequest, a keyed request type, and the implementation of the cancel_request() method for that specific type.

struct FooRequest{
    ...
    long request_id;//@key
}; 
using namespace connext;
void cancel_request(Requester<FooRequest,FooReply>& requester,
                       const WriteSample<FooRequest>& request)
{
    if ((requester.get_request_datawriter()->dispose(request.data(), DDS_HANDLE_NIL))
         != DDS::RETCODE_OK) {
        throw std::runtime_error("dispose() failed");
    } 
}

On the Replier side, the reception of the invalid sample will trigger on_sample_available/ on_request_available. The instance_state will be DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, so you will need to check for this instance_state to implement any behavior associated to the request cancellation (see code snippet).

using namespace connext;
classFooReplierListener:publicReplierListener<FooRequest,FooReply>
{
    public:
         void on_request_available(connext::Replier<FooRequest,FooReply>& replier)
         {
             connext::Sample<FooRequest> request;
             replier.take_request(request);

             if (request.info().instance_state == DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE) {
                 // Interrupt and cancel the process of request here
                 // ... 
             }

             // Process regular requests here
             // ... 
         } 
} 

Please, note that:

  • If you handle the deletion of resources properly when closing your application, by default instances will be not only be unregistered, but they will also be dispensed. This could potentially trigger the cancellation of requests as a result. If you want to avoid this default behavior, you will need to set datawriter_qos.autodispose_unregistered_instances to false (This Qos policy controls whether the middleware automatically disposes the instance when it is unregistered, which is true by default.)
  • If you use multiple instances with different keys in the application, the memory usage will increase overtime if you set the datareader_qos.max_instances.resource_limits to LENGTH_UNLIMITED. There are different approaches you can follow to avoid this issue.
    • You can set max_instances to a limited value. Note that requests will be dropped/rejected if the applications reaches the limits. You will need to implement mechanisms to handle those scenarios using the on_sample_rejected callback.
    • You can explicitly unregister requests after each dispose to reclaim the resources used by the request.
    • You can limit the number of instances you use. For example, you can reuse the key in a cyclical manner.

Comments

awesome. I got stuck at this. Thanks a lot for detaild guide.