Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 292137 - Prevent storing duplicate CDOID/CDO_VERSION revisions
Summary: Prevent storing duplicate CDOID/CDO_VERSION revisions
Status: CLOSED FIXED
Alias: None
Product: EMF
Classification: Modeling
Component: cdo.core (show other bugs)
Version: 2.0   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Eike Stepper CLA
QA Contact: Eike Stepper CLA
URL:
Whiteboard:
Keywords:
Depends on: 292146
Blocks:
  Show dependency tree
 
Reported: 2009-10-13 07:58 EDT by Caspar D. CLA
Modified: 2010-06-29 09:20 EDT (History)
3 users (show)

See Also:
stefan: galileo+
stepper: review+


Attachments
Patch for 2.0.0 (1.04 KB, patch)
2009-10-27 01:37 EDT, Caspar D. CLA
stepper: iplog+
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Caspar D. CLA 2009-10-13 07:58:20 EDT
User-Agent:       Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Firefox/3.0.1
Build Identifier: CDO 2.0.0.v200906160459

During multiuser testing, with around 15 users performing frequent commits and refreshs, we are seeing duplicate CDOID/CDO_VERSION entries appearing in our database. Obviously these pairs should be unique, so this is data corruption.

First, I'm a bit surprised that CDO doesn't put a uniqueness constraint on this compound key -- so "part 1" of this Bugzilla is a request to add that.

Second, I am wondering: how does CDO protect against conflicting "updates" at the persistence level? Obviously an "update" is really a UPDATE+INSERT combination with the insert adding the new revision -- so what I mean is: how does CDO prevent two INSERTs from inserting two rows with the same CDOID/CDO_VERSION ? Since CDO has not put a uniqueness constraint on the compound key, this responsibily can not be assumed to be handled by the database.

I am aware of the lockedObjects collection in TransactionCommitContextImpl, but these objects are released shortly after the DBStoreAccessor returns. So the assumption seems to be that once the DBStoreAccessor returns, the data has safely been written. But a DBMS will surely return before performing the actual write, if that's possible given constraints and isolation requirements. 

Note, the above is based on a somewhat hasty code review, so if I misunderstood something I apologize. Also, I must admit that we are still using Derby -- we'd like to switch, but for the upcoming release of our product we are too far down the development/test cycle for that. And at this stage we see no reason to assume that this is Derby-specific.

Reproducible: Sometimes

Steps to Reproduce:
During multiuser testing, with around 15 users performing frequent
commits and refreshs, we are seeing duplicate CDOID/CDO_VERSION entries
appearing in our database. Obviously these pairs should be unique, so
this is data corruption.

First, I'm a bit surprised that CDO doesn't put a uniqueness constraint
on this compound key -- so "part 1" of this Bugzilla is a request to add
that.

Second, I am wondering: how does CDO protect against conflicting
"updates" at the persistence level? Obviously an "update" is really an
UPDATE+INSERT combination with the insert adding the new revision -- so
what I mean is: how does CDO prevent two INSERTs from inserting two rows
with the same CDOID/CDO_VERSION ? Since CDO has not put a uniqueness
constraint on the compound key, this responsibily can not be assumed to
be handled by the database.

I am aware of the lockedObjects collection in
TransactionCommitContextImpl, but these objects are released shortly
after the DBStoreAccessor returns. So the assumption seems to be that
once the DBStoreAccessor returns, the data has safely been written. But
a DBMS will surely return before performing the actual write, if that's
possible given constraints and isolation requirements. So that seems to
leave room for an update from a second session to be processed in such a
way that it appears to have been serialized at the CDO level, but
actually may be parallel at the database level. 

Admittedly, this above is based on my review of fairly complex code, so
if the protection is there and I just overlooked it, I apologize. 

Also I must admit that we are still using Derby -- we'd like to switch,
but for the upcoming release of our product we are too far down the
development/test cycle for that. However, at this stage we see no reason
to assume that this is Derby-specific.
Comment 1 Caspar D. CLA 2009-10-13 08:06:14 EDT
One might wonder: why did I write most of the description here twice? Editing in a separate app, then clumsy pasting: once too often. Sorry guys. I would clean it up if I had the access rights.

Anyway, please read it starting from "Steps to Reproduce:"

Thanks
/Caspar
Comment 2 Stefan Winkler CLA 2009-10-13 08:26:31 EDT
Do I understand correctly that the version gets duplicated (not the ID - so i.e., we don't have two different objects getting the same ID but rather two different revisions of the same object getting the same version)?

Also, can you please specify which store (DB?) and which database you are using? 

There should be unique constraints on the (CDO_ID, CDO_VERSION) columns. Can you use an SQL browser and login to your database and check if there are constraints (maybe wrong ones?)
Comment 3 Saulius Tvarijonas CLA 2009-10-13 08:33:10 EDT
There is only one non-unique index on (CDO_ID, CDO_REVISED).
Comment 4 Stefan Winkler CLA 2009-10-13 09:40:53 EDT
Ok, I had a look at the sources. Indeed, we do not create UNIQUE indices. I
don't exactly know why, though. I checked in the old DBStore sources and it
this is not an omission during the DBStore-refactoring. We did not create
unique indices in the old DBStore, either. 

So this bug is really two in my eyes:

Bug 1: create UNIQUE (aka Primary Key) indexes (aka constraints)
=> handled in Bug 292146

Bug 2: even if UNIQUE constraints exist on the DB, they do not prevent the real
problem (well, they would by failing the conflicting transaction). But
shouldn't we catch conflicting concurrent version creation issues above the
persistence layer itself?
Comment 5 Caspar D. CLA 2009-10-21 07:03:43 EDT
OK, Bug 292164 has been taken care of, so that prevents database corruption. But CDO itself still doesn't adequately prevent conflicting concurrent updates.

As I mentioned in the bug description, it seems that TransactionCommitContextImpl.lockedObjects field should prevent this, but this protection is evidently not airtight. One thing that looks suspicious to me, is that the new revisions are computed before the objects are locked (see TransactionCommitContextImpl.write). And since the essential check "originObject.isCurrent()" is performed in computeDirtyObject, this allows two or more threads to pass this test concurrently while in fact they are going to have a conflict.

So, it seem to me that at least lockObjects() should be called before computeDirtyObjects(*). But perhaps there's more to it than this?
Comment 6 Caspar D. CLA 2009-10-27 01:37:34 EDT
Created attachment 150585 [details]
Patch for 2.0.0

A tiny patch. It should do the trick...
Comment 7 Eike Stepper CLA 2009-11-05 07:53:58 EST
Yes, that makes sense ;-)

Committed to R2_0_maintenance
Comment 8 Eike Stepper CLA 2009-11-05 07:54:32 EST
Comment on attachment 150585 [details]
Patch for 2.0.0

Jasper, please confirm that:

1) The number of lines that you changed is smaller than 250.
2) You are the only author of these changed lines.
3) You apply the EPL to these changed lines.
Comment 9 Caspar D. CLA 2009-11-05 22:34:24 EST
(In reply to comment #8)
> (From update of attachment 150585 [details])
> Jasper, please confirm that:
> 
> 1) The number of lines that you changed is smaller than 250.
> 2) You are the only author of these changed lines.
> 3) You apply the EPL to these changed lines.

I confirm.