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.

Reply via email to