Why is it important to use the idempotent Slice keyword?

An idempotent operation is an operation that, if invoked twice in succession with identical parameter values, has the same effect as a single invocation. For example, the statement x=1 is idempotent whereas the statement x++ is not.

Ice allows you to optionally mark a Slice operation as idempotent. For example:

Slice
interface Example
{
    idempotent void setVal(int val);
    void incVal();
}

The idempotent keyword informs the Ice run time that a single invocation of the setVal operation has the same effect as two successive invocations of setVal with the same parameter value. Obviously, incVal (which adds one to the current value) is not idempotent.

So, why would the Ice run time care about this? The answer is that Ice provides at-most-once semantics: it guarantees that, if a client makes a single operation invocation, the invocation will either be delivered to the server exactly once or not at all. This may seem self-evident. However, it implies that under no circumstances will a single invocation by a client ever result in two invocations in the server.

The at-most-once guarantee is important: if Ice were not to provide this guarantee, a single invocation of incVal by a client could result in two invocations of the operation in the server, with the net effect that the value would be incremented twice instead of once.

As long as everything works well, the Ice run time does not care if an operation is idempotent or not: call dispatch on the client and server side are exactly the same for idempotent and normal operations. However, when things go wrong, the operation mode (normal or idempotent) becomes important. Consider the following scenario:

  1. A client invokes an operation on an object in a server.
  2. The client-side run time makes a successful write system call on the appropriate socket to send the request.
  3. The client-side run time calls read on the socket to read the server's reply to the request.
  4. The read system call returns an error indicating that the connection was lost.

At this point, the client-side run time is in a difficult situation because it cannot know exactly when the connection was lost:

  • It may have been lost immediately after the write system call returned, that is, after the request data was copied into the client-side transport buffers in the kernel. In that case, the data was never sent to the server, but only buffered in the client's machine.
  • Alternatively, the connection may have been lost some time after the data was physically transmitted to and read by the server. In that case, the operation was actually executed by the server, but the reply from the server to the client was lost.

In other words, in the preceding scenario, the client-side run time has no idea whether or not the operation ended up executing in the server. Either is possible, and there is no way to find out.

In many cases, the Ice run time will automatically retry a failed request before propagating any error back to the application. This also applies to lost connections: the Ice run time will automatically re-establish lost connections and re-send failed requests, but only if it can guarantee that doing so will not violate at-most-once semantics.

In the preceding scenario, the Ice run time cannot safely re-send the failed request because doing so might end up executing the operation in the server a second time.

If you mark an operation as idempotent, the Ice run time relaxes its conservative rules and is more aggressive in trying to recover from transient errors. In particular, for the preceding scenario, if the operation is idempotent, the run time will attempt to re-establish the lost connection and send the request a second time before propagating any error to the application because idempotent tells the run time that is safe to do so.

All this means that, if you use idempotent where appropriate, you have a better chance for the Ice run time to transparently recover from errors that, otherwise, you would have to deal with yourself. So, it pays to use idempotent on operations for which it is appropriate. In general, these are all operations that only read data in the server, but do not modify it, such as get operations, and set operations that do not operate on previous state. (Such operations are also known as stateless or context-free operations.)

Note that life-cycle destroy operations are never idempotent, even though they look like they are. The reason is that, if the client loses connectivity to the server at the wrong moment, the client can draw false conclusions about the existence of an object.

Also note that, even if an operation's signature suggests that it is idempotent as far as the client is concerned, the operation may not be idempotent in the server. This usually is the case if you have an operation that reads data in the server in order to return it to the client, but also updates state in the server as a side-effect, for example, to update a statistics counter. In that case (at least if you care about accurate statistics), you should not mark the operation as idempotent. (This is the same as the difference between logical const-ness and physical const-ness in C++; you must decide which is appropriate and implement the operation accordingly.)

See Also