views:

45

answers:

2

Hi,

UserA and UserB are changing objectA.filedA objectA.filedB respectively and at the same time. Because they are not changing the same field one might think that there are no overlaps. Is that true? or the implementation of pm.makePersistnace() actually override the whole object... good to know...

+1  A: 

Is this what you're envisioning happening?

  1. Alice retrieves the object. { a = 1, b = "Foo" }
  2. Bob retrieves the object. { a = 1, b = "Foo" }
  3. Alice modifies the object, by changing b. { a = 1, b = "Bar" }
  4. Bob modifies the object, by changing a. { a = 2, b = "Foo" }
  5. Alice persists her copy of the object. { a = 1, b = "Bar" }
  6. Bob persists his copy of the object. { a = 2, b = "Foo" }

Bob's copy of the object will overwrite the copy in the datastore, because he's persisting his whole object, not just his set of changed fields. Or, in general, whichever of them persists last has their whole object persisted in the datastore.

You could fix this by running each of their get-set-and-persist operations in a transaction. App Engine transactions don't lock the whole object from being retrieved or modified locally, they just prevent other users from persisting. So:

  1. Alice retrieves the object in a transaction. { a = 1, b = "Foo" }
  2. Bob retrieves the object in a transaction. { a = 1, b = "Foo" }
  3. Alice modifies the object, by changing b. { a = 1, b = "Bar" }
  4. Bob modifies the object, by changing a. { a = 2, b = "Foo" }
  5. Alice tries to persist the object, but can't, because Bob has it open in a transaction. An exception will be thrown, which Alice will catch by ending her transcation and retrying...
  6. Bob persists the object, with no problems, because Alice has finished his transaction { a = 2, b = "Foo" }
  7. Alice retries her transaction by retrieving again. { a = 2, b = "Foo" }
  8. Alice modifies the object, by changing b. { a = 2, b = "Bar" }
  9. Alice persists the object, and it works because nobody else has a transaction open. { a = 2, b = "Bar" }

I'm not absolutely sure which user will get the exception, but as long as they're willing to retry when they see it, they'll both be able to make their changes to the object and persist them, eventually.

This is called Optimistic Locking.

Jason Hall
You might want to reconsider using 'id' and 'name' - they're both reserved terms in App Engine, and aren't modifiable.
Nick Johnson
Excellent recommendation. Renamed to a and b. I'll also rename User A and User B to Alice and Bob, to avoid even more confusion.
Jason Hall
A: 

hi Jason,

Thanks for your answer. Pity that the makePersistence() implementation is to write the WHOLE object to the datastore and not only to the fields that were changed. This fact is actually forcing ANY shared object update in GAE to use a transaction as a rule. Further more - in such cases you must implement the "retry mechanism" as an exception in the transaction may occur.

So... updating any shared object in GAE should ALWAYS have these extras:

  • do it within a transaction
  • implement a retry mechanism

Most of Google's examples in their site are actually not taking that into account. As if they're assuming that most apps won't used shared objects

For example (http://code.google.com/appengine/docs/java/datastore/creatinggettinganddeletingdata.html):

public void updateEmployeeTitle(User user, String newTitle) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try {
        Employee e = pm.getObjectById(Employee.class, user.getEmail());
        if (titleChangeIsAuthorized(e, newTitle) {
            e.setTitle(newTitle);
        } else {
            throw new UnauthorizedTitleChangeException(e, newTitle);
        }
    } finally {
        pm.close();
    }
}

OR:

public void updateEmployeeTitle(Employee e, String newTitle) {
    if (titleChangeIsAuthorized(e, newTitle) {
        e.setTitle(newTitle);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(e);
        } finally {
            pm.close();
        }
    } else {
        throw new UnauthorizedTitleChangeException(e, newTitle);
    }
}
bach
They're also not handling `OverQuotaException`s or `CapabilityDisabledException`s, but that's probably just because it would make the example too complex to be useful. And the truth is, most apps won't ever have these problems because most apps won't be so heavily used that simultaneous edits are a problem. Some apps don't even edit existing entities at all, only create and display them. Your app seems to be one that needs to take it into account, so it's a good thing that you've looked into it.
Jason Hall
Also it's generally not a good idea to answer your own question with discussion about the question. That's what comments are for, either on your question, or on the answer.
Jason Hall
but then code doesn't appear nicely and hard to read
bach