[OJB-developers] implicit locking and ODMG compliance (and JDO)
Brought to you by:
thma
From: Doug C. <de...@fl...> - 2002-02-01 07:41:20
|
Based on my experience with object databases, it surprises me that ojb can be considered "fully ODMG 3.0 compliant." I must admit that I do not have a copy of the ODMG 3.0 spec at hand (is there a freely downloadable copy on the web?). I do have the JDO spec, though, and this same issue will certainly need to be addressed before a JDO compliant implementation can be completed, so I am reporting it here. The problem is: implicit locking doesn't work. BTW, I am not advocating changing ojb to correct this problem, but implicit locking should not be communicated to users as a feature, and claims about compliance should be made with caveats. Consider the following comments from interface org.odmg.Transaction: * Read locks are implicitly obtained on objects as they are accessed. * Write locks are implicitly obtained as objects are modified. Here is a sequence of events to demonstrate that the implicit locking is not taking place (also see the junit test below where the number comments from this sequence are duplicated): /*1*/ transaction 1 obtains an object A1 using OQLQuery /*2*/ transaction 1 accesses A1 by reading a field with an accessor NOTE: A1 is not locked at this point as reported by LockMap.hasReadLock This is the first bug; I worked around it by calling lock explicitly. /*3*/ transaction 2 obtains an object A2 using OQLQuery (A1 == A2) /*4*/ transaction 2 modifies A2 by writing a field with a setter NOTE: A2 is not write-locked at this point, otherwise the transaction would be stopped and the write prevented This is the second bug. /*5*/ transaction 2 aborts. This is irrelevant. /*6*/ transaction 1 accesses A1 by reading the same field as in /*2*/ NOTE: the value has changed despite the read-lock. This is a consequence of the second bug. If transaction 1 were allowed to proceed, (a) the object A1 will look dirty despite the fact that transaction 1 didn't write it, and (b) transaction 1 could read bad data from an aborted transaction. Data are corrupted, and transaction isolation is violated. The moral of the story is that ojb does not support implicit locks correctly. A user must use explicit locking for reads and writes before accessing or modifying an object, respectively. Most ODMG Java implementations do implicit locking by either: - using a custom JVM and requiring persistent classes to be registered and/or.. - modifying persistent classes' source code or .class files to intercept accesses and making all persistent fields private In order to support "transparent persistence" AND implicit locking it would be necessary to modify any class which accesses a persistent class's fields. JDO does require implicit locking, and uses a class "enhancer" to make it work. The reference implementation produces replacement java source code for peristent classes, and so does not completely implement the specification. A better approach would be to implement a custom class loader to "enhance" the persistent classes at load time. Regards, e /* -=- junit test for test/odmg/OdmgExamples.java -=- */ /* e added read/write thread for testThreadedRWR */ class OtherThread extends Thread { Implementation odmg; OtherThread(Implementation odmg) { this.odmg = odmg; } public void run() { Transaction tx2 = odmg.newTransaction(); try { tx2.begin(); OQLQuery query = odmg.newOQLQuery(); query.create("select anArticle from " + Article.class.getName() + " where articleId = 60"); /*3*/ List results = (List) query.execute(); Article a2 = (Article) results.get(0); /*4*/ a2.addToStock(23); // --- the "implicit" write lock /*5*/ tx2.abort(); } catch (Exception ex) { tx2.abort(); fail("ODMGException: " + ex.getMessage()); } } } public void testThreadedRWR() { // get facade instance Implementation odmg = OJB.getInstance(); Database db = odmg.newDatabase(); //open database try { db.open(databaseName, Database.OPEN_READ_WRITE); } catch (ODMGException ex) { fail("ODMGException: " + ex.getMessage()); } Transaction tx1 = odmg.newTransaction(); OtherThread o = new OtherThread( odmg ); try { tx1.begin(); OQLQuery query = odmg.newOQLQuery(); query.create("select anArticle from " + Article.class.getName() + " where articleId = 60"); /*1*/ List results = (List) query.execute(); Article a1 = (Article) results.get(0); /*2*/ int a1s1 = a1.getStock(); // --- the implicit read lock if( ! ojb.odmg.locking.LockMapFactory.getLockMap().hasReadLock( (ojb.odmg.TransactionImpl )tx1, a1) ) { OJB.getLogger().error( "!!! not read locked !!!" ); //fail( "OQLQuery.execute didn't real-lock the result" ); tx1.lock( a1, Transaction.READ ); } o.start(); // run the other RW transaction o.join(); // wait for it to finish /*6*/ int a1s2 = a1.getStock(); if( a1s1 != a1s2 ) { OJB.getLogger().error( "!!! dirty cache !!!" ); fail( "touching didn't write-lock" ); } tx1.abort(); } catch (Exception ex) { tx1.abort(); fail("ODMGException: " + ex.getMessage()); } // close database try { db.close(); } catch (ODMGException ex) { fail("ODMGException: " + ex.getMessage()); } } /* */ |