[JIRA] Created: (CAY-863) Object property unexpectedly set to null through forceMergeWithSnapshot

From: Martin Thelian (JIRA) ("Martin)
Date: Wed Sep 12 2007 - 09:48:51 EDT

  • Next message: Kevin Menard: "Re: [DISCUSSION] Java 5 and 3.0 schedule"

    Object property unexpectedly set to null through forceMergeWithSnapshot
    -----------------------------------------------------------------------

                     Key: CAY-863
                     URL: https://issues.apache.org/cayenne/browse/CAY-863
                 Project: Cayenne
              Issue Type: Bug
              Components: Cayenne Core Library
        Affects Versions: 1.2 [STABLE], 2.0 [STABLE], 3.0
             Environment: Windows, Linux, PostgreSQL
                Reporter: Martin Thelian
                Assignee: Andrus Adamchik

    Hi!
    We have problems in our application in situations where multiple threads try to update the same object in parallel. Here are some simplified parts of our code:

    01: public void updateUserPoints(String userID, int points) {
    02: DataContext dc = DataContext.createDataContext();
    03:
    04: USR usr = this.getUserByID(userID, dc);
    05: if (usr != null) {
    06: int currpoints = usr.getPoints().intValue();
    07: currpoints += points;
    08: usr.setPoints(new Integer(currpoints));
    09: dc.commitChanges();
    10: }
    11: }
    12:
    13: public void updateUser(final String userID) {
    14: DataContext dc = DataContext.createDataContext();
    15: USR usr = this.getUserByID(userID, dc);
    16: if (usr == null) return;
    17:
    18: // Change some user data ....
    19: [...]
    20:
    21: // commit changes
    22: try {
    23: dc.commitChanges();
    24: } catch (CayenneRuntimeException e) {
    25: dc.rollbackChanges();
    26: }
    27:
    28: System.out.println(usr.getMbuser().getFirstname());
    29: // ^^ throws a NullPointerException because Mbuser is
    30: // unexpectedly null here!
    31: }
    32:
    33: private USR getUserByID(String userID, DataContext dc) {
    34: SelectQuery query = new SelectQuery(USR.class, ExpressionFactory.matchExp("userID", userID));
    35: List<USR> users = dc.performQuery(query);
    36: return (users == null || users.size() == 0) ? null : users.get(0);
    37: }

    Thread-A calls updateUserPoints(..), which just increments a simple integer value.
    Thread-B calls updateUser(..), which reads and updates some data and writes it back to DB.
    The concurrency problem sometimes occurs at line 29. Sometime it happens that a call to usr.getMbuser() unexpectedly returns null, even though this property is never explicitly deleted or set to null by us. There is a one-to-one relationship between the USR and MBuser object.

    Some debugging has shown that the property is set to "null" by one of the EventDispatcher-Threads. Here is a Stacktrace:

    Thread [EventDispatchThread-2] (Suspended (breakpoint at line 175 in USR))
            USR.writeProperty(String, Object) line: 175
            DataRowUtils.forceMergeWithSnapshot(ObjEntity, DataObject, DataRow) line: 270
            ObjectStore.processUpdatedSnapshot(Object, DataRow) line: 1175
            ObjectStore.processSnapshotEvent(SnapshotEvent) line: 819
            ObjectStore.snapshotsChanged(SnapshotEvent) line: 804
            GeneratedMethodAccessor107.invoke(Object, Object[]) line: not available
            DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
            Method.invoke(Object, Object...) line: 585
            EventManager$NonBlockingInvocation(Invocation).fire(Object[]) line: 240
            EventManager$InvocationDispatch.fire() line: 452
            EventManager$DispatchThread.run() line: 499

    Deactivating cayenne.DataDomain.sharedCache or inserting the following DataContextDelegate at line 15 helps to skip the problem, but that's not a proper solution for our application.

            dc.setDelegate(new DataContextDelegate() {
                    public void finishedMergeChanges(DataObject object) { }
                    public void finishedProcessDelete(DataObject object) { }
                    public boolean shouldMergeChanges(DataObject object, DataRow snapshotInStore) {
                            if (object instanceof USR && ((USR)object).getUserID().equals(userID)) {
                                            return false;
                                    }
                            }
                            return true;
                    }
                    public boolean shouldProcessDelete(DataObject object) { return true; }
                    public Query willPerformGenericQuery(DataContext context, Query query) { return query; }
                    public Query willPerformQuery(DataContext context, Query query) { return query; }
                    public GenericSelectQuery willPerformSelect(DataContext context, GenericSelectQuery query) { return query; }
                    
            });

    The above problem seems to occur in Cayenne 1.2, 2.X and 3.0.

    -- 
    This message is automatically generated by JIRA.
    -
    You can reply to this email to add a comment to the issue online.
    



    This archive was generated by hypermail 2.0.0 : Wed Sep 12 2007 - 09:49:21 EDT