Alexandre Martins On Agile Software Development

5Mar/1013

RESTful Web Services: Preventing Race Conditions

One of the core premisses of RESTful web services is that HTTP should be seen as an application protocol rather than just a transport protocol. It comprises a whole bunch of semantics that allows us to build robust distributed systems. And for some cases, when multiple consumers manipulate the same resource, therefore changing its state, the solution should be robust enough to prevent the system to get into a race condition.

But how HTTP could prevent that?

HTTP provides a simple but powerful mechanism for aligning resource states by making use of entity tag or ETag and conditional request headers. An ETag is anything that uniquely identifies an entity, such as the ID associated with a persisted resource, a checksum of the entity headers and body, etc. If this resource changes—that is, when one or more of its headers, or its entity body, changes—then the entity tag changes accordingly, reflecting this new resource state.

When a response contains an ETag associated to a resource state and you want to continue working with this same resource, it's recommended to use this tag in subsequent requests (called conditional requests), otherwise the resource state might eventually become out of sync with service one, returning something like a 409 Conflict.

Conditional requests happens when the current ETag is supplied to a conditional request header, such as If-Match or If-None-Match, when user is requesting to update a resource for example. The service will then check the precondition, by comparing the current resource ETag with the one provided in the request. If it's satisfied than the server proceeds and process the request, otherwise it concludes that the resource has changed and responds with a 412 Precondition Failed.

Example

Given an online shop for home goods, where two people— Admin1 and Admin2 —are responsible for administrating its contents. In our scenario both administrators are trying to change the state of the same product (the Weber BBQ), around the same time. Admin1 wants to lower the product price down to $300.00 and Admin2 wants to change its state to "Not Available". Firstly, both administrators GET the current product state independently of one another by doing the following request:

GET /product/1 HTTP/1.1
Host: myshop.com

Returning the following resource (product) as response. Note that the service's response contains an ETag header.

HTTP/1.1 200 OK
Content-Length: 265
Content-Type: application/xml
ETag: "686897696a7c876b7e"

<product>
  <name>Weber Family BBQ</name>
  <description>Great for parties and cooks a neat roast too.</description>
  <price>$399.00</price>
  <status>In Stock</status>
</product>

When Admin1 does a conditional PUT, including an If-Match header with the ETag value from the previous GET.

PUT /product/1 HTTP/1.1
Host: myshop.com
If-Match: "686897696a7c876b7e"

<product>
  <name>Weber Family BBQ</name>
  <description>Great for parties and cooks a neat roast too.</description>
  <price>$300.00</price>
  <status>In Stock</status>
</product>

And as the product state hasn't changed since the last request, then the request is thus successful! Notice that the response returns an updated ETag value, reflecting the new product state.

HTTP/1.1 204 No Content
ETag: "616898r96a8cy86b8eee11"

Little time after Admin1 has updated the product, Admin2 does another PUT to the same product, including the same If-Match header with the ETag value from the GET request.

PUT /product/1 HTTP/1.1
Host: myshop.com
If-Match: "686897696a7c876b7e"

<product>
  <name>Weber Family BBQ</name>
  <description>Great for parties and cooks a neat roast too.</description>
  <price>$399.00</price>
  <status>Not Available</status>
</product>

The service then determines that someone is trying to change the same product, using an out-of-date resource representation (ETags are different!), and responds with a 412 Precondition Failed code. No race conditions whatsoever!

HTTP/1.1 412 Precondition Failed

Conclusion

Although ETags and conditional request headers make up a powerful mechanism for dealing with concurrency, one thing to keep in mind is that, depending on the amount of computation performed by the server to generate an ETag, response times might increase considerably. So use it only if you need it!

Special thanks to Jim Webber for helping me with this post. For more information on RESTful Web Services, check out his latest book (written together with Savas Parastatidis and Ian Robinson)— REST in Practice: Hypermedia and Systems Architecture.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Reddit
  • Twitter
Tagged as: Leave a comment
Comments (13) Trackbacks (1)
  1. Great article!

    Just to point out another way to implement this: using Last-Modified and If-Unmodified-Since headers.

    When the server returns a resource it adds the date that this resource was modified in the Last-Modified header. Then, the client sends a If-Unmodified-Since header with this same date when trying to update it. If the resource was modified since then, the operation fails (returning 412 like you suggested).

    This can be used when an etag is not simple to generate, for example.

    I was wondering… depending on the implementation, the 412 response could send the same content as GET (with Last-Modified and/or ETag), this would save one extra GET to fetch the most recent resource again.

  2. Nice article.
    I suggest return 400 Bad Request if client doesn’t send If-Match or If-None-Match while modifying resources. What do you think?

    Cheers,

    Emerson

  3. Cool! That’s a very clever solution and design :)

  4. @Emerson Macedo

    Yeah definitely that would be an option. You could specify it as part of the contract to use the service. But even if you don’t do it, client will eventually find out what’s wrong when getting a 409.

  5. Very good writeup. I’m used to doing etags with RSS feeds but this is one of those things that makes me wonder why I didn’t realize it sooner. Such a simple solution to this problem, I love it.

  6. Very nice design, nice written article. Thanks

    Tomas

  7. Hey Alex,
    Very good post! I had never seen this ETag feature of HTTP before, very useful indeed!
    Very good example, clear, simple and straight to the point!
    I’ll definetely remember this post if I need to solve http race conditions one day.
    Cheers ;)

  8. Great post. Many CPUs implement the same mechanism at a low level, called compare-and-swap, or CAS ( http://en.wikipedia.org/wiki/Compare-and-swap ).

  9. Awesome man, front page of Proggit!

  10. Great example on 412 + ETag usage.

    The only negative point is that when sharing unmodified-check responsibility with the client, a non conforming one might break your application.

    The following code should use the received ETag as in the example:

    product = entry.at(uri)
    product.update new_representation

    But most up-to-date rest-called client API’s will simply ignore it, and execute it anyways, breaking our implementation. Thats why I still on a crusade against http apis, looking for REST client ones.

    ETag and PUT is also a good solution when you are unsure your last request went through. Suppose the first PUT attempt got connected, you sent the request, but got no response back from the server.

    Great post!

  11. @Igor Sobreira

    The only thing to keep in mind when using timestamp-based checking is that it’s only limited to the next second. So, if the resources in your application are likely to change more often than once a second, then timestamp-based conditional headers are not the right choice.

  12. Hooray, optimistic locking is re-invented yet again..

  13. This thing is opened in my screen for the last 10 days, I finally got some time to read through, and it was worth.

    This is one of these thing written that we never pay attention:

    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

    Section 24: If-Match

    The If-Match request-header field is used with a method to make it conditional. A client that has one or more entities previously obtained from the resource can verify that one of those entities is current by including a list of their associated entity tags in the If-Match header field. Entity tags are defined in section 3.11. The purpose of this feature is to allow efficient updates of cached information with a minimum amount of transaction overhead. It is also used, on updating requests, to prevent inadvertent modification of the wrong version of a resource. As a special case, the value “*” matches any current entity of the resource.

    Well done Nunes


Leave a comment


Comment moderation is enabled. Your comment may take some time to appear.