Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 328681

Summary: LockObjectsRequest can cause corruption of client-side graph
Product: [Modeling] EMF Reporter: Caspar D. <caspar_d>
Component: cdo.coreAssignee: Caspar D. <caspar_d>
Status: CLOSED FIXED QA Contact: Eike Stepper <stepper>
Severity: normal    
Priority: P3 CC: saulius.tvarijonas, stepper
Version: 4.0Flags: stepper: review+
Target Milestone: ---   
Hardware: All   
OS: All   
Whiteboard:
Attachments:
Description Flags
Patch v1 (test case only)
none
Patch including testcases
none
Patch including testcases
none
Patch v3 none

Description Caspar D. CLA 2010-10-26 05:47:25 EDT
A LockObjectsRequest implicitly refreshes the objects being locked,
and since normally only a small subset of all objects viewed by the 
client get locked, this means locking an object comes down to a 
*partial* refresh. 

Partial refreshes are inherently dangerous because they make it possible 
for only one end of a containment or eOpposite relationship to be changed
in the client's graph. (That is, when the object holding one end of the
relationship is refreshed, but the object holding the other end isn't.)

Will attach testcase shortly.
Comment 1 Caspar D. CLA 2010-10-26 05:49:05 EDT
Note: this is primarily a problem for sessions with passiveUpdate=false,
but I guess in theory it could even be a problem for PU=true because there's
no guarantee that the passiveUpdate info will arrive before the refresh.
Comment 2 Caspar D. CLA 2010-10-26 05:50:50 EDT
Created attachment 181705 [details]
Patch v1 (test case only)

Test case, as a patch.
Comment 3 Caspar D. CLA 2010-10-26 23:55:29 EDT
I can think of 3 possible solutions so far:

(1) Locking an object does not require refreshing that object.

 This is a simple, clean solution, but the regrettable side-effect
 is that holding a write lock would no longer by itself guarantee
 that the commit of the locked object will succeed, as it might
 still get rejected for being an modification of a revised
 revision. This isn't breakage, but it would make the semantics of
 CDO locks a bit odd.

(2) Locking one or more objects requires refreshing ALL viewed objects.

 Seems like a straightforward solution at first, but it's at odds
 with the sticky (i.e. timestamp-pegged) behavior that we recently
 introduced for views with passiveUpdate=false (and supporting
 audit data).

 The 2nd drawback is that it turns what's supposed to be a
 light-weight op (i.e. locking one or a few objects) into a
 heavy-weight op that could possibly involve a big data transfer
 across the network.

(3) Locking a set of objects requires refreshing at least those
 objects, plus a computed additional set including all objects
 that were "touched" by changes to the primary set. 

 This would be a very complicated solution. The problem is that
 the client cannot compute the set of additional objects, since it
 has no (certain) knowledge about what changes have been made. The
 server can compute the set (I think...), but the server is in
 itself unaware of which objects the client is viewing. So in order
 to avoid the server sending refresh data for objects that aren't
 even being viewed, the client would still have to identify in its
 lock request ALL of the objects it's viewing .

 Example of this approach: say client wants to lock A. Server
 checks whether it has for A a revision newer than the requesting
 client's revision. If so, server compares that latest revision
 with the client's revision to see if there have been changes to
 any of A's containment and eOpposites relationships. If so, the
 "other end" of those relationships must, in principle, be included
 in the response, unless those other ends aren't viewed by that
 particular client.

 (But come to think of it, this solution seems even harder to reconcile 
  with sticky views.)
Comment 4 Caspar D. CLA 2010-10-27 01:46:51 EDT
Another option:

(4) Locking a set of objects requires refreshing only those
 objects *initially*, but when receiving the response the client
 checks for inconsistencies arising from the refresh, and requests
 additional fresh revisions from the server to deal with those.

 This is similar to (3) except that the computational burden is on 
 the client. Obvious is drawback is that it could lead to an
 unpredictably long sequence of LoadRevision requests.
Comment 5 Eike Stepper CLA 2010-10-27 02:22:07 EDT
What about this: 

When a lock is to be requested and passive updates are off, then we just check if the client's last update time is uptodate (i.e. equal to the servers last commit time of the viewed branch). If the client's view is based on old data (even if that client doesn't use/see this old data) we reject the lock request and throw an exception. Indicating to the user that a refresh() is needed first.
Comment 6 Caspar D. CLA 2010-10-27 02:58:00 EDT
(In reply to comment #5)
> When a lock is to be requested and passive updates are off, then we just check
> if the client's last update time is uptodate (i.e. equal to the servers last
> commit time of the viewed branch). If the client's view is based on old data
> (even if that client doesn't use/see this old data) we reject the lock request
> and throw an exception. Indicating to the user that a refresh() is needed
> first.

Hmm yeah, that's good actually. I defer :-)

You want me to implement this, or will you?
Comment 7 Eike Stepper CLA 2010-10-27 03:14:41 EDT
(In reply to comment #6)
> (In reply to comment #5)
> > When a lock is to be requested and passive updates are off, then we just check
> > if the client's last update time is uptodate (i.e. equal to the servers last
> > commit time of the viewed branch). If the client's view is based on old data
> > (even if that client doesn't use/see this old data) we reject the lock request
> > and throw an exception. Indicating to the user that a refresh() is needed
> > first.
> 
> Hmm yeah, that's good actually. I defer :-)

No sarcasm??

> You want me to implement this, or will you?

I'd be happy if you can tackle this ;-)
Comment 8 Caspar D. CLA 2010-10-27 05:00:35 EDT
(In reply to comment #7)

OK.

Saulius suggested the following optimization: instead of simply
comparing the client's latest update TS with the latest commit
time on the server, let's check whether for any of the objects the
client is trying to lock, he (the client) does not have the latest
revision.

This is better, because it doesn't unnecessarily refuse locks
because of unrelated commits by other clients (i.e. commits that 
didn't affect any of the objects the client is trying to lock).
Comment 9 Eike Stepper CLA 2010-10-27 05:04:29 EDT
> Saulius suggested the following optimization: instead of simply
> comparing the client's latest update TS with the latest commit
> time on the server, let's check whether for any of the objects the
> client is trying to lock, he (the client) does not have the latest
> revision.

Isn't that almost the same effort like a complete refresh()?
Comment 10 Caspar D. CLA 2010-10-27 22:31:58 EDT
(In reply to comment #9)
> Isn't that almost the same effort like a complete refresh()?

No, it's still much less effort, because, first, the check will only
be performed for the objects being locked, which is typically a
small subset of all the objects the client is viewing. And second, 
even if stale revisions are detected, the server will only throw
an exception; it will not send back the new revisions as it would
in case of a refresh.

Anyway, point is that the client shouldn't be prevented from locking
a set of objects because of totally unrelated commits. (E.g. commits
in other resources by people (colleagues?) he might not even know and
who are working on different projects -- a likely scenario if a CDO
based app is used in a large organization.)
Comment 11 Caspar D. CLA 2010-10-29 02:36:17 EDT
Created attachment 182013 [details]
Patch including testcases

Here's a patch and a few new tests in LockingManagerTest, which
make my earlier testcase for this Zilla obsolete.
Comment 12 Caspar D. CLA 2010-10-29 02:51:57 EDT
Created attachment 182015 [details]
Patch including testcases

> ... which make my earlier testcase for this Zilla obsolete.

But still I included it :-( 

Here's another version of the patch, without the obsoleted
testcase.
Comment 13 Caspar D. CLA 2010-10-29 03:42:45 EDT
Looks like we'll have to deprecate Bugzilla_294850_Test, since
with this patch, a LockObjectsRequest no longer brings in any
revisions, and so it can't cause any conflicts.
Comment 14 Eike Stepper CLA 2010-10-29 07:02:10 EDT
Your patch looks good. Unfortunately Bugzilla_294850_Test fails in all scenarios here. I guess it's easy to fix for you. BTW. I fear that my hotfix for bug 329005 is in conflict with your LockManagerTest changes. Can you bring this class uptodate?

I wonder if we can remove the synchronization on the view's invalidation lock, now that Objects don't change anymore...
Comment 15 Caspar D. CLA 2010-11-01 01:17:15 EDT
Created attachment 182128 [details]
Patch v3

Yeah, Bugzilla_294850_Test.java has to be removed, see my
earlier comment.

Uploading a new patch that includes the removal.
Comment 16 Caspar D. CLA 2010-11-01 01:29:38 EDT
(In reply to comment #14)
> BTW. I fear that my hotfix for bug 329005 is in conflict with
> your LockManagerTest changes. Can you bring this class uptodate?

See comments in bug 329005.
Comment 17 Eike Stepper CLA 2010-11-06 05:26:38 EDT
IIRC patch v3 in *this* bugzilla contains changes to LockManagerTest.java but they don't match HEAD anymore due to my changes to the same class (see bug 329005). I guess your changes are important, that's why I asked you to bring patch v3 in sync with the newer HEAD ;-)
Comment 18 Caspar D. CLA 2010-11-09 02:42:50 EST
Patch v3 in this bugzilla still works with current HEAD.
Comment 19 Eike Stepper CLA 2010-11-13 02:15:27 EST
Please go ahead and commit...
Comment 20 Caspar D. CLA 2010-11-14 22:35:32 EST
Committed to HEAD
Comment 21 Eike Stepper CLA 2011-06-23 03:37:44 EDT
Available in R20110608-1407