Re: Optimistic locking and concurrency within the same application

From: Aristedes Maniatis (ar..aniatis.org)
Date: Mon Nov 01 2010 - 01:07:15 UTC

  • Next message: Mike Kienenberger: "Re: Modified Fields"

    Great. If you like, please make a task in our tracking system that we should improve the UI in the modeler to disable the attribute lock options (or show a warning) if the entity is not set to optimistic locking.

    Thanks

    Ari

    On 1/11/10 3:22 AM, Paulo Andrade wrote:
    > OK, figured out what was going on. There's a checkbox on Cayenne Modeler that toggles optimistic locking for that entity on or off. I only had the attributes marked for locking and didn't see that checkbox.
    >
    > It is now throwing an OL exception as it should.
    >
    > On Oct 30, 2010, at 3:53 PM, Paulo Andrade wrote:
    >
    >> So I went ahead and did a bit of testing:
    >>
    >> I created an app that would execute the following code (the model as an entity named "Counter" with a "value" property marked for locking):
    >>
    >> ----------
    >> ObjectContext context = DataContext.createDataContext();
    >>
    >> Counter counter = context.newObject(Counter.class);
    >> counter.setValue(3);
    >>
    >> context.commitChanges();
    >>
    >> // Create our two peer contexts
    >> ObjectContext context1 = DataContext.createDataContext();
    >> ObjectContext context2 = DataContext.createDataContext();
    >>
    >> // Instantiate object on context1
    >> Counter counter1 = (Counter) context1.localObject(counter.getObjectId(), null);
    >> LOG.debug("Context1 read counter with value "+ counter.getValue());
    >>
    >> // Instantiate object on context2
    >> Counter counter2 = (Counter) context2.localObject(counter.getObjectId(), null);
    >> LOG.debug("Context2 read counter with value "+ counter.getValue());
    >>
    >> // Context1 makes changes
    >> counter1.setValue(counter1.getValue() + 1);
    >> LOG.debug("Counter1 incremented to value " +counter1.getValue());
    >>
    >> // Context2 makes changes
    >> counter2.setValue(counter2.getValue() + 1);
    >> LOG.debug("Counter2 incremented to value " +counter2.getValue());
    >>
    >> // Context1 commit
    >> context1.commitChanges();
    >>
    >> // Context2 commit
    >> context2.commitChanges();
    >> ----------
    >>
    >> Now what I would like is for context2.commitChanges() to fail, since this commit would write the value 4 which is already on the database thus failling to increment the value.
    >>
    >> If we take a look at the query logging you can see that the last commit has a 4 on the where clause (which I feel should be a 3).
    >>
    >> ----------
    >> [INFO] access.QueryLogger +++ Connecting: SUCCESS.
    >> [INFO] access.QueryLogger --- transaction started.
    >> [INFO] access.QueryLogger Detected and installed adapter: org.apache.cayenne.dba.postgres.PostgresAdapter
    >> [INFO] access.QueryLogger SELECT nextval('pk_counter')
    >> [INFO] access.QueryLogger --- will run 1 query.
    >> [INFO] access.QueryLogger INSERT INTO Counter (id, value) VALUES (?, ?)
    >> [INFO] access.QueryLogger [batch bind: 1->id:240, 2->value:3]
    >> [INFO] access.QueryLogger === updated 1 row.
    >> [INFO] access.QueryLogger +++ transaction committed.
    >> [DEBUG] pages.Index Context1 read counter with value 3
    >> [DEBUG] pages.Index Context2 read counter with value 3
    >> [INFO] access.QueryLogger --- will run 1 query.
    >> [INFO] access.QueryLogger --- transaction started.
    >> [INFO] access.QueryLogger SELECT t0.value, t0.id FROM Counter t0 WHERE t0.id = ? [bind: 1->id:240] - prepared in 10 ms.
    >> [INFO] access.QueryLogger === returned 1 row. - took 18 ms.
    >> [INFO] access.QueryLogger +++ transaction committed.
    >> [DEBUG] pages.Index Counter1 incremented to value 4
    >> [INFO] access.QueryLogger --- will run 1 query.
    >> [INFO] access.QueryLogger --- transaction started.
    >> [INFO] access.QueryLogger SELECT t0.value, t0.id FROM Counter t0 WHERE t0.id = ? [bind: 1->id:240]
    >> [INFO] access.QueryLogger === returned 1 row. - took 1 ms.
    >> [INFO] access.QueryLogger +++ transaction committed.
    >> [DEBUG] pages.Index Counter2 incremented to value 4
    >> [INFO] access.QueryLogger --- will run 1 query.
    >> [INFO] access.QueryLogger --- transaction started.
    >> [INFO] access.QueryLogger UPDATE Counter SET value = ? WHERE id = ?
    >> [INFO] access.QueryLogger [batch bind: 1->value:4, 2->id:240]
    >> [INFO] access.QueryLogger === updated 1 row.
    >> [INFO] access.QueryLogger +++ transaction committed.
    >> [INFO] access.QueryLogger --- will run 1 query.
    >> [INFO] access.QueryLogger --- transaction started.
    >> [INFO] access.QueryLogger UPDATE Counter SET value = ? WHERE id = ?
    >> [INFO] access.QueryLogger [batch bind: 1->value:4, 2->id:240]
    >> [INFO] access.QueryLogger === updated 1 row.
    >> [INFO] access.QueryLogger +++ transaction committed.
    >> ----------
    >>
    >> Even more surprisingly is that even with Level 1 - No Cache Sharing the same behavior applies.
    >>
    >> So do you guys feel this is normal behavior? How can I code this in a way that the second commitChanges would fail?
    >>
    >> Best regards,
    >> Paulo Andrade
    >>
    >> On Oct 28, 2010, at 11:50 AM, Paulo Andrade wrote:
    >>
    >>> Hello,
    >>>
    >>> I'm new to Cayenne, and coming form EOF I'm find everything very easy to understand.
    >>>
    >>> Everything is so similar that I'm wondering that what I consider to be flaws in EOF also happen in Cayenne.
    >>>
    >>> For a long description of the problem here's a blog post:
    >>>
    >>> http://terminalapp.net/dr-optimistic-locking/
    >>>
    >>> The summary is this:
    >>>
    >>> EOF stores snapshots in the ObjectStoreCoordinator which are shared by EOEditingContexts (ObjectContexts in Cayenne), these snapshots are updated on fetches (queries in Cayenne) and saves (commits in Cayenne).
    >>>
    >>> So take an Cayenne application with a Level 2 cache (Local VM Caching), two ObjectStores (oc1 and oc2) and a Counter object with and intValue attribute marked for optimistic locking.
    >>>
    >>> What should happen in the following code:
    >>>
    >>> -------------
    >>> Counter oc1Counter, oc2Counter; // assume both exist and refer to the same entity in the DB, each in their own context
    >>>
    >>> int i = oc1Counter.intValue();
    >>> int j = oc2Counter.intValue();
    >>> // both i and j have a value of 3
    >>>
    >>> // now we increment oc1's counter
    >>> oc1Counter.setIntValue( i+1 ); // sets to 4
    >>>
    >>> oc1.commitChanges(); // saves oc1Counter with a value of 4 to disk and updates the snapshot
    >>>
    >>> // now increment oc2's counter
    >>> oc1Counter.setIntValue( j+1 ); // sets to 4 again
    >>>
    >>> oc2.commitChanges(); // **
    >>> ——————
    >>>
    >>> ** now what do you think should happen here?
    >>> In EOF the save succeeds and the previous change is overwritten without me knowing about it. Will Cayenne do the same?
    >>>
    >>> Best regards,
    >>> Paulo Andrade
    >>>
    >>
    >

    -- 
    -------------------------->
    Aristedes Maniatis
    GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A
    



    This archive was generated by hypermail 2.0.0 : Mon Nov 01 2010 - 01:07:57 UTC