On 30/11/12 12:29 PM, Martyn Taylor wrote:
On 11/30/2012 12:07 AM, Richard Su wrote:
Hi,
There is a race condition when two clients attempt to update the same
resource at the same time. Both clients sees the same initial state.
Client A changes it to state A and then Client B changes it to state
B. Client B should know that the resource was changed to state A
before deciding whether to change the state to B.
This race condition is present in both the UI and API.
If we want to prevent this type of race condition, there are two ways
to solve it: one leaves it optional and the other forces the
prevention. The optional case, uses headers, and leaves the decision
to prevent such race conditions to the client. The forced case uses
the updated_at field as a means to check if a resource has been
modified.
It is not clear to me if it should be forced since the UI currently
allows it whether by design or oversight. Your opinion on this matter
is appreciated.
* Optional Case
Use the ETag header, containing a MD5 hash, to indicate the state of
the resource. As far as I can tell we have this header already,
perhaps it is auto generated.
Is this something that is managed by us or by the HTTP server?
Looking at the HTTP Spec this does seem to be the way to go. But I am
unsure on how this is implemented. For example, we do not want the
MD5 Checksum to be done after the resource has been represented in a
particular format, since the client is stuck using that format or it
has to refresh its entire view (Since the checksum will change from
XML to JSON).
Shouldn't matter how you generate the checksum, as long as the hash is
calculated consistently. The client doesn't do any modifications of the
hash (or shouldn't). As another option, you could use a timestamp for ETag.
In a PUT, clients will include a If-Match header as part of the
request, setting the value to the ETag hash it received when it last
received the resource's state. Before processing a PUT, the server
will compare ETag to If-Match and allow the request to complete if
the values match. If the values do not match the server returns
status code 412 - Precondition Failed. A time based alternative to
ETag and If-Match would be Last-Modified and If-Unmodified-Since.
This seems more natural to me, but I wonder about caching.
Last-modified is used by Caches between the client and the origin
server. Is it normal for a cache to update this tag?
Also, is this something we are implementing or is it provided by the
HTTP Server? If it is the latter then we may have timesync problems
when clustering.
Since the lower-level stack is unaware of how to calculate ETags (By
default rails does MD5 hash of the body, I think), you will have to
process headers in the controllers.
This does not address the issue for the UI.
(This idea is from
http://blog.m.artins.net/restful-web-services-preventing-race-conditions/)
* Forced Case
Most of the resources available through the api have a updated_at
field. One exception is deployables, and this will need to be
rectified. The updated_at field can be used as a timestamp to flag an
error if a resource has changed since the client last requested a
view of it.
For the API, in a PUT, clients will be required to return back the
updated_at value. If the server sees updated_at has not changed, the
request is allowed to complete. If the value has changed, the server
returns status 409 - Conflict.
I dislike this approach, particularly when the problem as been solved
at the protocol level.
For the UI, we can embed updated_at as hidden form data and perform
the same comparisons. If a change is detected, the server will
display an error message.
Another argument for the UI consuming the API.
+1. Not sure how attainable this is for you guys, you may have to stick
to keeping a time stamp in some hidden field.
-d
The "Optional" or "Protocol" approach
gets my vote. But I need to find out more on how this thing works.
ETags does seem to be designed for this specific purpose so I would
start there.
Thanks
Martyn