What to do when “Optimistic Locking” doesn't work?

by maximedupre   Last Updated October 05, 2018 18:05 PM - source

I have this following scenario:

  1. User makes GET request to /projects/1 and receives an ETag
  2. User makes PUT request to /projects/1 with ETag from step #1
  3. User makes another PUT request to /projects/1 with ETag from step #1

Normally, the second PUT request would receive a 412 response, since the ETag is now stale - the first PUT request modified the resource, so the ETag doesn't match anymore.

But what if the 2 PUT requests are sent at the same time (or exactly one after the other)? The first PUT request does not have time to process and update the resource before PUT #2 arrives, which causes PUT #2 to overwrite PUT #1. The whole point of "Optimistic Locking" is for that not to happen...

Answers 3

In that case the service is broken. It should utilize transactions or locks or databases that have this functionality out of the box.

Esben Skov Pedersen
Esben Skov Pedersen
October 05, 2018 17:23 PM

The ETag mechanism specifies only the communication protocol for optimistic locking. It's the responsibility of the application to implement the mechanism to detect concurrent updates to enforce the optimistic lock.

In a typical application that uses a database, you'd usually do this by opening a transaction when processing a PUT request. You'd normally read the existing state of the database inside that transaction (to gain a read lock), check your Etag validity, and overwrite the data (in a way that'll cause a write conflict when there's any incompatible concurrent transaction), then commit. If you setup the transaction correctly, then one of the commits should fail because they'll both be trying to update the same data concurrently. You'll then be able to use this transaction failure to either return 412 or retry the request, if it makes sense for the application.

Lie Ryan
Lie Ryan
October 05, 2018 17:27 PM

It's on the application developer to actually check the E-Tag and provide that logic. It's not magic that the web server does for you because it only knows how to calculate E-Tag headers for static content. So let's take your scenario above and break down how the interaction should happen.

GET /projects/1

Server receives the request, determines the E-Tag for this version of the record, returning that with the actual content.

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Since the client now has the E-Tag value, it can include that with the PUT request:

PUT /projects/1
E-Tag: "412"
Content-Type: application/json
{modified: true}

At this point your application has to do the following:

  • Verify that the E-Tag is still correct: "412" == "412" ?
  • If so, make the update and calculate a new E-Tag

Send the success response.

204 No Content
E-Tag: "543"

If another request comes and attempts to perform a PUT similar to the request above, the second time your server code evaluates it, you are responsible to provide the error message.

  • Verify the E-Tag is still correct: "412" != "543"

On failure, send the failure response.

412 Precondition Failed

This is code you actually have to write. The E-Tag can in fact be any text (within the limits defined in the HTTP spec). It doesn't have to be a number. It can be a hash value as well.

Berin Loritsch
Berin Loritsch
October 05, 2018 17:50 PM

Related Questions

design java library to make HTTP request

Updated February 06, 2019 11:05 AM

How to handle deadlocks (with a REST API)?

Updated August 27, 2018 16:05 PM

REST API acceptable design flexibility

Updated February 13, 2019 14:05 PM