Bug 290032 - Provide sticky views
Summary: Provide sticky views
Status: CLOSED FIXED
Alias: None
Product: EMF
Classification: Modeling
Component: cdo.core (show other bugs)
Version: 4.0   Edit
Hardware: All All
: P3 enhancement (vote)
Target Milestone: ---   Edit
Assignee: Caspar D. CLA
QA Contact: Eike Stepper CLA
URL:
Whiteboard: Power to the People
Keywords: noteworthy
Depends on:
Blocks:
 
Reported: 2009-09-21 12:42 EDT by Adomas CLA
Modified: 2011-06-23 03:38 EDT (History)
4 users (show)

See Also:
stepper: review+


Attachments
TestCase (1.51 KB, application/octet-stream)
2009-09-21 12:45 EDT, Adomas CLA
stepper: iplog+
Details
Patch including testcase (21.20 KB, patch)
2010-09-20 05:05 EDT, Caspar D. CLA
no flags Details | Diff
Patch v2 (23.09 KB, patch)
2010-09-23 01:55 EDT, Caspar D. CLA
no flags Details | Diff
Patch v3 (24.21 KB, patch)
2010-09-24 04:26 EDT, Caspar D. CLA
no flags Details | Diff
Patch v4 - ready to be committed (25.61 KB, patch)
2010-10-01 05:30 EDT, Eike Stepper CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Adomas CLA 2009-09-21 12:42:47 EDT
User-Agent:       Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
Build Identifier: 200906160459

CDO tries to use child newer revision, then the father’s revision. This cause object not found exception

Reproducible: Always

Steps to Reproduce:
You will need two sessions with passiveUpdate = false;
1. Open session s1 and transaction t1 load repository.
2. Add object o1 with children’s ch1...ch10 into repository.
3. Commit t1.
4. Open session s2 and transaction t2 load repository.
5. Load object o1
6. In transaction t1 delete child ch1 and commit
7. Iterate children in transaction t2
You will get object not found exception
Comment 1 Adomas CLA 2009-09-21 12:44:59 EDT
org.eclipse.emf.cdo.util.ObjectNotFoundException: Object OID13 not found (temporary = false)
	at org.eclipse.emf.internal.cdo.util.FSMUtil.validate(FSMUtil.java:250)
	at org.eclipse.emf.internal.cdo.view.CDOViewImpl.createObject(CDOViewImpl.java:824)
	at org.eclipse.emf.internal.cdo.view.CDOViewImpl.getObject(CDOViewImpl.java:722)
	at org.eclipse.emf.internal.cdo.transaction.CDOTransactionImpl.getObject(CDOTransactionImpl.java:567)
	at org.eclipse.emf.internal.cdo.view.CDOViewImpl.convertIDToObject(CDOViewImpl.java:1016)
	at org.eclipse.emf.internal.cdo.CDOStore.convertToEMF(CDOStore.java:384)
	at org.eclipse.emf.internal.cdo.CDOStore.get(CDOStore.java:177)
	at org.eclipse.emf.ecore.impl.EStoreEObjectImpl$BasicEStoreEList.delegateGet(EStoreEObjectImpl.java:225)
	at org.eclipse.emf.common.util.DelegatingEList.get(DelegatingEList.java:396)
	at org.eclipse.emf.common.util.DelegatingEList$EIterator.doNext(DelegatingEList.java:1075)
	at org.eclipse.emf.common.util.DelegatingEList$EIterator.next(DelegatingEList.java:1062)
	at com.nomagic.cdo.test.tests.Bugzilla_290032_DeleteAndGetObject.testComparisonAcrossTransactions(Bugzilla_290032_DeleteAndGetObject.java:39)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at junit.framework.TestCase.runTest(TestCase.java:164)
	at junit.framework.TestCase.runBare(TestCase.java:130)
	at org.eclipse.net4j.tests.AbstractOMTest.runBare(AbstractOMTest.java:109)
	at junit.framework.TestResult$1.protect(TestResult.java:106)
	at junit.framework.TestResult.runProtected(TestResult.java:124)
	at junit.framework.TestResult.run(TestResult.java:109)
	at junit.framework.TestCase.run(TestCase.java:120)
	at org.eclipse.net4j.tests.AbstractOMTest.run(AbstractOMTest.java:131)
	at junit.framework.TestSuite.runTest(TestSuite.java:230)
	at junit.framework.TestSuite.run(TestSuite.java:225)
	at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
	at org.eclipse.pde.internal.junit.runtime.RemotePluginTestRunner.main(RemotePluginTestRunner.java:62)
	at org.eclipse.pde.internal.junit.runtime.CoreTestApplication.run(CoreTestApplication.java:23)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at org.eclipse.equinox.internal.app.EclipseAppContainer.callMethodWithException(EclipseAppContainer.java:574)
	at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:195)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:386)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:549)
	at org.eclipse.equinox.launcher.Main.basicRun(Main.java:504)
	at org.eclipse.equinox.launcher.Main.run(Main.java:1236)
	at org.eclipse.equinox.launcher.Main.main(Main.java:1212)
Comment 2 Adomas CLA 2009-09-21 12:45:58 EDT
Created attachment 147715 [details]
TestCase
Comment 3 Simon Mc Duff CLA 2009-09-21 12:54:09 EDT
(In reply to comment #2)
> Created an attachment (id=147715) [details]
> TestCase

This is a problem that the back-end needs to support.

If it doesn't ... we cannot do much about it !
It will depends of the implementations of the back-end!

Simon
Comment 4 Saulius Tvarijonas CLA 2009-09-22 02:03:13 EDT
Simon,

1. Which back-ends supporting it? We using back-end with audit support.
2. There is API to load revisions by time (getRevisionByTime). I think in this case to not broke data integrity revision could be loaded by last session refresh timestamp. Could work like audit view when you see all objects at a give time.

Regards,
Saulius
Comment 5 Simon Mc Duff CLA 2009-09-22 06:56:22 EDT
(In reply to comment #4)
> Simon,
> 
> 1. Which back-ends supporting it? We using back-end with audit support.
> 2. There is API to load revisions by time (getRevisionByTime). I think in this
> case to not broke data integrity revision could be loaded by last session
> refresh timestamp. Could work like audit view when you see all objects at a
> give time.
> 
> Regards,
> Saulius

To support it,in passivemode == false,  back-end needs to assign a specific db session to a CDOSession on the client. I built Objectivity Store that way.

That way, when you access object the back-end it "should" always keep reference to old objects... until it commit or refresh. It should work also with relational database...

Simon
Comment 6 Saulius Tvarijonas CLA 2009-09-23 11:42:46 EDT
Simon,

Can you give more details on this solution? Looks like it is not standard CDO code. From you I understand back-end needs to keep old revisions only if there is no audit support, otherwise why we cannot just load from db?

I wonder how other people are using CDO and don't get into this problem. Seems like major integrity issue. Maybe only we using session with passiveUpdate=false? :(

Saulius
Comment 7 Simon Mc Duff CLA 2009-09-23 12:42:59 EDT
(In reply to comment #6)
> Simon,
> 
> Can you give more details on this solution? Looks like it is not standard CDO
> code. From you I understand back-end needs to keep old revisions only if there
> is no audit support, otherwise why we cannot just load from db?
> 
[SIMON] No, I didn't said it needs to keep old revisions.
What I will explain it is not related whatsoever to CDO.

Usually relational database and object database have an infrastructure to keep integrity of the table/graph.

When you fetch an object ? They can (at different level) keep internally a way to access other objects for that period.. so even if one transaction add new objects or deleted objects, these objects are still accessible if the transaction already did a read on these table...

> I wonder how other people are using CDO and don't get into this problem. Seems
> like major integrity issue. Maybe only we using session with
> passiveUpdate=false? :(

[SIMON] Again this isn't a CDO issue, this will rely on the back-end. 
You will have the same problem in many case ... even in straight EMF. where object into a list belong to different resource... and one of them was deleted.... you Will have the same kind of exceptions.

Simon

> 
> Saulius
Comment 8 Caspar D. CLA 2009-10-06 04:47:46 EDT
Simon, 

This isn't really a bug, because CDO's current behavior is perfectly reasonable. It's more of an enhancement request ;-) (And should probably have been logged as one.)

What we would like to see is a read-write view that is safer for the client to read. That is, a R/W view that minimizes (or eliminates) the risk that a *read* operation will throw an exception due to a concurrent change by another client.

It seems to us this would not be very hard to implement. For example, a TX could have an option to be "sticky", i.e. load revisions corresponding to the time that the TX was opened (or last refreshed), instead of getting the latest revisions.

We realize that with this option turned on, the server will sometimes provide stale revisions to the client; if the client makes changes on such objects and commits them, they will certainly fail -- but that's the client's problem, and a risk he chooses to take if he turns this "sticky" option on. The client can in fact mitigate this risk to a very large extent by doing a refresh when the user actually starts editing.

The usefulness of this kind of view comes from the fact that a user typically cannot say in advance whether he will make changes or not. He might just want to browse the data while retaining the option of editing. As long as he's only browsing, we'd like to avoid any read exceptions due to concurrent updates.

What do you think?

Thanks,
Caspar.
Comment 9 Eike Stepper CLA 2009-10-07 03:19:01 EDT
If the user is not sure whether he will want to modify objects or not it might be smart enough to expect the "worst" and just start over with a transaction instead of a view? In terms of cost there's not much of a difference between views and transactions as long as nothing is actually modified.
Comment 10 Caspar D. CLA 2009-10-07 06:07:52 EDT
(In reply to comment #9)
> If the user is not sure whether he will want to modify objects or not it might
> be smart enough to expect the "worst" and just start over with a transaction
> instead of a view? In terms of cost there's not much of a difference between
> views and transactions as long as nothing is actually modified.

We're not worried about the additional cost of opening a tx instead of a read-only view. As you say, we can expect the worst and just use a TX -- but this isn't the issue.

Our problem with a regular TX (using lazy loading), is that it cannot guarantee that a read operation will not fail. This is because a TX always fetches the *latest* revisions, which may have been affected by other clients since the last lazy load. So even simple things such as the following, might go wrong:

  if (list.size() == 10 ) // assume list owner was loaded earlier
  {
    Object o = list.get(9);
    // triggers a lazy load, and therefore
    // might fail if other client removed object at index #9
  }

What we're asking for, is a r/w view that eliminates this kind of problem. I.e. a view that guarantees that reads will succeed, by not getting the latest revision, but by getting all revisions for the timestamp at which the view was opened (or last refreshed).

As far as read behavior is concerned, this is of course the same as an audit view opened for timestamp = systemtime. But an audit view is not writeable. We want this new view type to be writeable, because often the client won't have any problems if he updates objects and commits -- only if indeed another client beat him to it, will his commit be rejected.
Comment 11 Eike Stepper CLA 2009-10-07 07:26:06 EDT
The code example you gave would suffer from the same problem in a purely local, but multi-threaded scenario. There you would add synchronization. In CDO remote scenarios I'd expect explicit locking being used, right?

If you just want to prevent the local CDO framework to apply remote invalidations I think CDOView.getLock() should help. Does it?
Comment 12 Saulius Tvarijonas CLA 2009-10-07 08:06:30 EDT
(In reply to comment #11)
> The code example you gave would suffer from the same problem in a purely local,
> but multi-threaded scenario. There you would add synchronization. In CDO remote
> scenarios I'd expect explicit locking being used, right?
With explicit locking it is remote call always. It costs. Plus it blocks writing.
Lazy loading is also remote call, but only when cache missing it. Even in such case, loading one revision does not block commits.
Another problematic place is generic code reading EMF code and not aware of CDO. Such code could work without additional CDO read locks.
Even if we go with CDO read locks, there might be cases when I start reading model v1(time t1) and end with v2(time t2). For example I read root object recursively. For the beginning I can lock root object only. Get children for root and lock each child. While I reading root children, one of children could be changed (v2 committed), because children are locked later. So currently we can only use audit view to read model consistently. It works, but I would prefer to read and write in the same view (one transaction).

> 
> If you just want to prevent the local CDO framework to apply remote
> invalidations I think CDOView.getLock() should help. Does it?
This particular issue we experience with passiveUpdate=false, so remote invalidation isn't a problem. BTW, does remote invalidation applies changes in transactional style also? 

Like:
1. lock view
2. apply all one commit changes
3. unlock view

Saulius
Comment 13 Eike Stepper CLA 2009-10-08 00:03:59 EDT
> BTW, does remote invalidation applies changes in
> transactional style also? 
> 
> Like:
> 1. lock view
> 2. apply all one commit changes
> 3. unlock view

Yes, see CDOViewImpl.handleInvalidation():

    lock.lock();

    try
    {
      conflicts = handleInvalidationWithoutNotification(dirtyOIDs, detachedOIDs, dirtyObjects, detachedObjects);
    }
    finally
    {
      lock.unlock();
    }

But the sole purpose of this lock is that a user can use it explicitely to suspend invalidation handling. It's not used by the framework otherwise.
Comment 14 Eike Stepper CLA 2009-10-08 00:12:00 EDT
I just noticed that your last question could imply a misunderstanding. When handling remote invalidations the framework will never apply changes other then marking CDOObjects PROXY or CONFLICT (plus setting the CDOObject's revision pointer to null in case of PROXY).

IIRC even in the case of a change subscription the modeled state of this object is not changed. The delta is only used to emit EMF style notifications.

Sijmon, do you think we could use change deltas to construct a local revision copy of the latest state? Then a subsequent LoadTransition would not cause a server round trip, but find the needed revision in the local cache...
Comment 15 Simon Mc Duff CLA 2009-10-08 06:53:40 EDT
(In reply to comment #14)
> I just noticed that your last question could imply a misunderstanding. When
> handling remote invalidations the framework will never apply changes other then
> marking CDOObjects PROXY or CONFLICT (plus setting the CDOObject's revision
> pointer to null in case of PROXY).
> 
> IIRC even in the case of a change subscription the modeled state of this object
> is not changed. The delta is only used to emit EMF style notifications.
> 
> Sijmon, do you think we could use change deltas to construct a local revision
> copy of the latest state? Then a subsequent LoadTransition would not cause a
> server round trip, but find the needed revision in the local cache...

We could if the version received == current version of the view + 1

Simon
Comment 16 Simon Mc Duff CLA 2009-10-08 06:55:24 EDT
Just to be clear.. 

By doing that.... it will not mean that this bug will be solved. You will still encounter that problem(this bug).
Comment 17 Eike Stepper CLA 2009-10-08 07:00:29 EDT
Yes, I understand. It's more about an additional internal optimization. Thx ;-)
Comment 18 Eike Stepper CLA 2010-06-29 04:54:40 EDT
Rebasing all outstanding 3.0 problem reports to version 3.0.1
Comment 19 Eike Stepper CLA 2010-07-07 07:10:34 EDT
Fixing wrong bug version.
Comment 20 Caspar D. CLA 2010-09-07 06:50:08 EDT
I'd like to put this back on the table for 4.0.

Recap: the idea is to have CDO transactions (i.e. R/W views) of a special
type, that we could call "time-pegged". That is to say, they're pegged to 
the timestamp at which they're opened, until an explicit refresh. Such a
tx would load revisions for that timestamp, not the latest revisions as
a normal tx would.

I have a working (or so it seems) implementation of this in my custom
codebase. Will try to create a 4.0 patch.
Comment 21 Caspar D. CLA 2010-09-20 05:05:50 EDT
Created attachment 179230 [details]
Patch including testcase
Comment 22 Caspar D. CLA 2010-09-23 01:55:38 EDT
Created attachment 179430 [details]
Patch v2

Uploading an enhanced version of the patch which, during
postcommit, tracks for every committed object onto which
branchPoint it was committed, so that the correct revision will
be fetched when a sticky TX must reload an object that it itself
committed earlier.

On Skype we also talked about eliminating setPassiveUpdateEnabled
in favor of setPassiveUpdateMode(NONE), as part of this patch. I
didn't go ahead with this, as it impacts the GUI, which is
unknown territory for me.
Comment 23 Caspar D. CLA 2010-09-24 04:26:25 EDT
Created attachment 179502 [details]
Patch v3

Patch amended again, in accordance with what we agreed to on Skype:
sticky behavior shall be automatic when passiveUpdates==false and
the IStore supports auditing.

Also fixed a few failures in tests that assumed non-sticky behavior.
Comment 24 Eike Stepper CLA 2010-10-01 05:30:28 EDT
Created attachment 180029 [details]
Patch v4 - ready to be committed
Comment 25 Caspar D. CLA 2010-10-01 05:37:04 EDT
Committed to HEAD
Comment 26 Eike Stepper CLA 2011-06-23 03:38:40 EDT
Available in R20110608-1407