Deterministic Client/Server Interaction

By on February 27, 2009 1:04 am

The module dojox.rpc.Client was introduced in Dojo Toolkit version 1.2, providing extra request headers on XMLHttpRequests to facilitate implementing deterministic request handling on servers. Browsers typically use two or more TCP connections to send HTTP requests to a server, while subsequent requests are often routed on separate TCP connections. This means there is no guarantee that the request that is sent first will arrive at the server first. With sophisticated web applications, non-determinism can lead to bugs that are very difficult to track down.

If a web application sends multiple requests to a server and assumes that the server will handle them in the order they were sent, this non-determinism can be problematic. Furthermore, messages that arrive out of order can be rare, and therefore problems can be hard to reproduce. On old modem connections, packets were sequenced over a slow PPP connection that made out of order messages almost non-existent. With modern broadband connections, the race becomes more interesting. HTTP pipelining adds another mechanism that can lead to messages arriving out of order and with browsers beginning to support and use pipelining, messages arriving out of order will become even more likely in the future.

In order to ensure that servers handle requests in the order that the client expects, dojox.rpc.Client adds a X-Seq-Id header to every request. The value of the header is simply a number (starting with 1) that increments with each request. The first XHR request will have a X-Seq-Id header with a value of 1, the next a value of 2, and so on.  A server can then utilize these sequences to enforce deterministic message ordering. If a request is received that has a X-Seq-Id number more than one higher than the previous request, the server can wait until it receives the appropriate request and execute it first. In order to simplify synchronization and concurrency issues, I recommend that you actually synchronize the handling of the responses so that every XHR response is fully executed prior to performing the next XHR request in the message sequence. This can provide a much safer approach to concurrent response handling. This technique is only intended to synchronize and sequence between requests in the same message stream, with the same client. Therefore, there is negligible negative impact on parallel processing which is necessary for scalability. Note that request handling for different clients can still be made in parallel.

However, using the X-Seq-Id header alone is not sufficient to provide deterministic request handling in all situations. Another reliability issue with Ajax applications can come from maintaining state information on the server about the state of the user interface in the browser. Imagine that a server tracked what page a user was on, and then used that information to respond to requests corresponding to the OK and Cancel buttons on the forms. Suppose a user opened a page for buying stocks. Next they opened another tab in the browser and opened the page for selling all their stocks. If the server tracks the state of the client by session (using a session cookie), the server will only know of one session, and think the user is on the selling their stocks page. Now if the user switches back to the first tab and clicks OK to indicate they want to buy a stock, the server will receive the OK request, but think the user is on the selling their stocks page, and incorrectly act upon that assumption. This is also a thorny issue that is difficult to detect. Generally, QA does not do extensive (or any) testing on the behavior of a web application when it is open in multiple tabs at once. Such a problem is usually only seen when a user actually does it, and complains about unexpected behavior as a result.

This particular problem can also afflict the X-Seq-Id message ordering scheme. The server must follow a sequence for one given JavaScript environment. If multiple JavaScript environments share a session and the server enforces the sequencing within the scope of the session, it can easily be confused by parallel sequence numbers.

The dojox.rpc.Client solves this problem by including a X-Client-Id header. The X-Client-Id header is a randomly generated ID that is used for requests from a given JavaScript environment instantiation (one per page). A server can then track client state by client id instead of by session cookie. This should be used with the X-Seq-Id to define the scope of the sequence.


To use dojox.rpc.Client, simply load the module and it will add the headers to all XHR requests:


However, this module is so simple, you may want to write your own for your server. You can view the source code . It is a simple AOP-style function replacement that performs before-advice to add the headers on all XHR requests.

Persevere implements this technique on the server side to manage requests deterministically. You can view the Firebug console with one of the online demos like the live “customer” table, or the database explorer to see the HTTP requests Dojo is making and their associated headers.

For complex applications that are dealing with numerous Ajax requests, the extra integrity of deterministic handling can be critical for reliable operation. Using dojox.rpc.Client, or a similar technique can be very beneficial in improving the robustness of your application and ensuring consistent behavior.