Uploaded image for project: 'Cayenne'
  1. Cayenne
  2. CAY-863

Object property unexpectedly set to null through forceMergeWithSnapshot

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 1.2 branch, 2.0 branch, 3.0
    • 3.0M2
    • Core Library
    • 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)

      { 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.

      Attachments

        Activity

          People

            andrus Andrus Adamchik
            theli Martin Thelian
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: