Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
1.2 branch, 2.0 branch, 3.0
-
None
-
Windows, Linux, PostgreSQL
Description
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)
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
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)
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 true;
}
public boolean shouldProcessDelete(DataObject object)
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.