KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hibernate > cache > ReadWriteCache


1 //$Id: ReadWriteCache.java,v 1.8 2005/03/16 06:01:17 oneovthafew Exp $
2
package org.hibernate.cache;
3
4 import java.io.Serializable JavaDoc;
5 import java.util.Comparator JavaDoc;
6
7 import org.apache.commons.logging.Log;
8 import org.apache.commons.logging.LogFactory;
9
10 /**
11  * Caches data that is sometimes updated while maintaining the semantics of
12  * "read committed" isolation level. If the database is set to "repeatable
13  * read", this concurrency strategy <em>almost</em> maintains the semantics.
14  * Repeatable read isolation is compromised in the case of concurrent writes.
15  * This is an "asynchronous" concurrency strategy.<br>
16  * <br>
17  * If this strategy is used in a cluster, the underlying cache implementation
18  * must support distributed hard locks (which are held only momentarily). This
19  * strategy also assumes that the underlying cache implementation does not do
20  * asynchronous replication and that state has been fully replicated as soon
21  * as the lock is released.
22  *
23  * @see NonstrictReadWriteCache for a faster algorithm
24  * @see CacheConcurrencyStrategy
25  */

26 public class ReadWriteCache implements CacheConcurrencyStrategy {
27
28     private static final Log log = LogFactory.getLog(ReadWriteCache.class);
29
30     private Cache cache;
31     private int nextLockId;
32
33     public ReadWriteCache() {}
34
35     public void setCache(Cache cache) {
36         this.cache=cache;
37     }
38
39     public Cache getCache() {
40         return cache;
41     }
42
43     public String JavaDoc getRegionName() {
44         return cache.getRegionName();
45     }
46     
47     /**
48      * Generate an id for a new lock. Uniqueness per cache instance is very
49      * desirable but not absolutely critical. Must be called from one of the
50      * synchronized methods of this class.
51      */

52     private int nextLockId() {
53         if (nextLockId==Integer.MAX_VALUE) nextLockId = Integer.MIN_VALUE;
54         return nextLockId++;
55     }
56
57     /**
58      * Do not return an item whose timestamp is later than the current
59      * transaction timestamp. (Otherwise we might compromise repeatable
60      * read unnecessarily.) Do not return an item which is soft-locked.
61      * Always go straight to the database instead.<br>
62      * <br>
63      * Note that since reading an item from that cache does not actually
64      * go to the database, it is possible to see a kind of phantom read
65      * due to the underlying row being updated after we have read it
66      * from the cache. This would not be possible in a lock-based
67      * implementation of repeatable read isolation. It is also possible
68      * to overwrite changes made and committed by another transaction
69      * after the current transaction read the item from the cache. This
70      * problem would be caught by the update-time version-checking, if
71      * the data is versioned or timestamped.
72      */

73     public synchronized Object JavaDoc get(Object JavaDoc key, long txTimestamp) throws CacheException {
74
75         if ( log.isTraceEnabled() ) log.trace("Cache lookup: " + key);
76
77         try {
78             cache.lock(key);
79
80             Lockable lockable = (Lockable) cache.get(key);
81
82             boolean gettable = lockable!=null && lockable.isGettable(txTimestamp);
83
84             if (gettable) {
85                 if ( log.isTraceEnabled() ) log.trace("Cache hit: " + key);
86                 return ( (Item) lockable ).getValue();
87             }
88             else {
89                 if ( log.isTraceEnabled() ) {
90                     if (lockable==null) {
91                         log.trace("Cache miss: " + key);
92                     }
93                     else {
94                         log.trace("Cached item was locked: " + key);
95                     }
96                 }
97                 return null;
98             }
99         }
100         finally {
101             cache.unlock(key);
102         }
103     }
104
105     /**
106      * Stop any other transactions reading or writing this item to/from
107      * the cache. Send them straight to the database instead. (The lock
108      * does time out eventually.) This implementation tracks concurrent
109      * locks of transactions which simultaneously attempt to write to an
110      * item.
111      */

112     public synchronized SoftLock lock(Object JavaDoc key, Object JavaDoc version) throws CacheException {
113         if ( log.isTraceEnabled() ) log.trace("Invalidating: " + key);
114
115         try {
116             cache.lock(key);
117
118             Lockable lockable = (Lockable) cache.get(key);
119             long timeout = cache.nextTimestamp() + cache.getTimeout();
120             final Lock lock = (lockable==null) ?
121                 new Lock( timeout, nextLockId(), version ) :
122                 lockable.lock( timeout, nextLockId() );
123             cache.update(key, lock);
124             return lock;
125         }
126         finally {
127             cache.unlock(key);
128         }
129
130     }
131
132     /**
133      * Do not add an item to the cache unless the current transaction
134      * timestamp is later than the timestamp at which the item was
135      * invalidated. (Otherwise, a stale item might be re-added if the
136      * database is operating in repeatable read isolation mode.)
137      * For versioned data, don't add the item unless it is the later
138      * version.
139      */

140     public synchronized boolean put(
141             Object JavaDoc key,
142             Object JavaDoc value,
143             long txTimestamp,
144             Object JavaDoc version,
145             Comparator JavaDoc versionComparator,
146             boolean minimalPut)
147     throws CacheException {
148         if ( log.isTraceEnabled() ) log.trace("Caching: " + key);
149
150         try {
151             cache.lock(key);
152
153             Lockable lockable = (Lockable) cache.get(key);
154
155             boolean puttable = lockable==null ||
156                 lockable.isPuttable(txTimestamp, version, versionComparator);
157
158             if (puttable) {
159                 cache.put( key, new Item( value, version, cache.nextTimestamp() ) );
160                 if ( log.isTraceEnabled() ) log.trace("Cached: " + key);
161                 return true;
162             }
163             else {
164                 if ( log.isTraceEnabled() ) {
165                     if ( lockable.isLock() ) {
166                         log.trace("Item was locked: " + key);
167                     }
168                     else {
169                         log.trace("Item was already cached: " + key);
170                     }
171                 }
172                 return false;
173             }
174         }
175         finally {
176             cache.unlock(key);
177         }
178     }
179
180     /**
181      * decrement a lock and put it back in the cache
182      */

183     private void decrementLock(Object JavaDoc key, Lock lock) throws CacheException {
184         //decrement the lock
185
lock.unlock( cache.nextTimestamp() );
186         cache.update(key, lock);
187     }
188
189     /**
190      * Release the soft lock on the item. Other transactions may now
191      * re-cache the item (assuming that no other transaction holds a
192      * simultaneous lock).
193      */

194     public synchronized void release(Object JavaDoc key, SoftLock clientLock) throws CacheException {
195         if ( log.isTraceEnabled() ) log.trace("Releasing: " + key);
196
197         try {
198             cache.lock(key);
199
200             Lockable lockable = (Lockable) cache.get(key);
201             if ( isUnlockable(clientLock, lockable) ) {
202                 decrementLock(key, (Lock) lockable);
203             }
204             else {
205                 handleLockExpiry(key);
206             }
207         }
208         finally {
209             cache.unlock(key);
210         }
211     }
212
213     void handleLockExpiry(Object JavaDoc key) throws CacheException {
214         log.warn("An item was expired by the cache while it was locked (increase your cache timeout): " + key);
215         long ts = cache.nextTimestamp() + cache.getTimeout();
216         // create new lock that times out immediately
217
Lock lock = new Lock( ts, nextLockId(), null );
218         lock.unlock(ts);
219         cache.update(key, lock);
220     }
221
222     public void clear() throws CacheException {
223         cache.clear();
224     }
225
226     public void remove(Object JavaDoc key) throws CacheException {
227         cache.remove(key);
228     }
229
230     public void destroy() {
231         try {
232             cache.destroy();
233         }
234         catch (Exception JavaDoc e) {
235             log.warn("could not destroy cache", e);
236         }
237     }
238
239     /**
240      * Re-cache the updated state, if and only if there there are
241      * no other concurrent soft locks. Release our lock.
242      */

243     public synchronized boolean afterUpdate(Object JavaDoc key, Object JavaDoc value, Object JavaDoc version, SoftLock clientLock)
244     throws CacheException {
245         
246         if ( log.isTraceEnabled() ) log.trace("Updating: " + key);
247
248         try {
249             cache.lock(key);
250
251             Lockable lockable = (Lockable) cache.get(key);
252             if ( isUnlockable(clientLock, lockable) ) {
253                 Lock lock = (Lock) lockable;
254                 if ( lock.wasLockedConcurrently() ) {
255                     // just decrement the lock, don't recache
256
// (we don't know which transaction won)
257
decrementLock(key, lock);
258                     return false;
259                 }
260                 else {
261                     //recache the updated state
262
cache.update( key, new Item( value, version, cache.nextTimestamp() ) );
263                     if ( log.isTraceEnabled() ) log.trace("Updated: " + key);
264                     return true;
265                 }
266             }
267             else {
268                 handleLockExpiry(key);
269                 return false;
270             }
271
272         }
273         finally {
274             cache.unlock(key);
275         }
276     }
277
278     /**
279      * Add the new item to the cache, checking that no other transaction has
280      * accessed the item.
281      */

282     public synchronized boolean afterInsert(Object JavaDoc key, Object JavaDoc value, Object JavaDoc version)
283     throws CacheException {
284     
285         if ( log.isTraceEnabled() ) log.trace("Inserting: " + key);
286         try {
287             cache.lock(key);
288
289             Lockable lockable = (Lockable) cache.get(key);
290             if (lockable==null) {
291                 cache.update( key, new Item( value, version, cache.nextTimestamp() ) );
292                 if ( log.isTraceEnabled() ) log.trace("Inserted: " + key);
293                 return true;
294             }
295             else {
296                 return false;
297             }
298         }
299         finally {
300             cache.unlock(key);
301         }
302     }
303
304     /**
305      * Do nothing.
306      */

307     public void evict(Object JavaDoc key) throws CacheException {
308         // noop
309
}
310
311     /**
312      * Do nothing.
313      */

314     public boolean insert(Object JavaDoc key, Object JavaDoc value) throws CacheException {
315         return false;
316     }
317
318     /**
319      * Do nothing.
320      */

321     public boolean update(Object JavaDoc key, Object JavaDoc value) throws CacheException {
322         return false;
323     }
324
325     /**
326      * Is the client's lock commensurate with the item in the cache?
327      * If it is not, we know that the cache expired the original
328      * lock.
329      */

330     private boolean isUnlockable(SoftLock clientLock, Lockable myLock)
331     throws CacheException {
332         //null clientLock is remotely possible but will never happen in practice
333
return myLock!=null &&
334             myLock.isLock() &&
335             clientLock!=null &&
336             ( (Lock) clientLock ).getId()==( (Lock) myLock ).getId();
337     }
338
339     public static interface Lockable {
340         public Lock lock(long timeout, int id);
341         public boolean isLock();
342         public boolean isGettable(long txTimestamp);
343         public boolean isPuttable(long txTimestamp, Object JavaDoc newVersion, Comparator JavaDoc comparator);
344     }
345
346     /**
347      * An item of cached data, timestamped with the time it was cached,.
348      * @see ReadWriteCache
349      */

350     public static final class Item implements Serializable JavaDoc, Lockable {
351
352         private final long freshTimestamp;
353         private final Object JavaDoc value;
354         private final Object JavaDoc version;
355
356         public Item(Object JavaDoc value, Object JavaDoc version, long currentTimestamp) {
357             this.value = value;
358             this.version = version;
359             freshTimestamp = currentTimestamp;
360         }
361         /**
362          * The timestamp on the cached data
363          */

364         public long getFreshTimestamp() {
365             return freshTimestamp;
366         }
367         /**
368          * The actual cached data
369          */

370         public Object JavaDoc getValue() {
371             return value;
372         }
373
374         /**
375          * Lock the item
376          */

377         public Lock lock(long timeout, int id) {
378             return new Lock(timeout, id, version);
379         }
380         /**
381          * Not a lock!
382          */

383         public boolean isLock() {
384             return false;
385         }
386         /**
387          * Is this item visible to the timestamped
388          * transaction?
389          */

390         public boolean isGettable(long txTimestamp) {
391             return freshTimestamp < txTimestamp;
392         }
393
394         /**
395          * Don't overwite already cached items
396          */

397         public boolean isPuttable(long txTimestamp, Object JavaDoc newVersion, Comparator JavaDoc comparator) {
398             // we really could refresh the item if it
399
// is not a lock, but it might be slower
400
//return freshTimestamp < txTimestamp
401
return version!=null && comparator.compare(version, newVersion) < 0;
402         }
403
404         public String JavaDoc toString() {
405             return "Item{version=" + version +
406                 ",freshTimestamp=" + freshTimestamp;
407         }
408     }
409
410     /**
411      * A soft lock which supports concurrent locking,
412      * timestamped with the time it was released
413      * @author Gavin King
414      */

415     public static final class Lock implements Serializable JavaDoc, Lockable, SoftLock {
416         private long unlockTimestamp = -1;
417         private int multiplicity = 1;
418         private boolean concurrentLock = false;
419         private long timeout;
420         private final int id;
421         private final Object JavaDoc version;
422
423         public Lock(long timeout, int id, Object JavaDoc version) {
424             this.timeout = timeout;
425             this.id = id;
426             this.version = version;
427         }
428
429         public long getUnlockTimestamp() {
430             return unlockTimestamp;
431         }
432         /**
433          * Increment the lock, setting the
434          * new lock timeout
435          */

436         public Lock lock(long timeout, int id) {
437             concurrentLock = true;
438             multiplicity++;
439             this.timeout = timeout;
440             return this;
441         }
442         /**
443          * Decrement the lock, setting the unlock
444          * timestamp if now unlocked
445          * @param currentTimestamp
446          */

447         public void unlock(long currentTimestamp) {
448             if ( --multiplicity == 0 ) {
449                 unlockTimestamp = currentTimestamp;
450             }
451         }
452
453         /**
454          * Can the timestamped transaction re-cache this
455          * locked item now?
456          */

457         public boolean isPuttable(long txTimestamp, Object JavaDoc newVersion, Comparator JavaDoc comparator) {
458             if (timeout < txTimestamp) return true;
459             if (multiplicity>0) return false;
460             return version==null ?
461                 unlockTimestamp < txTimestamp :
462                 comparator.compare(version, newVersion) < 0; //by requiring <, we rely on lock timeout in the case of an unsuccessful update!
463
}
464
465         /**
466          * Was this lock held concurrently by multiple
467          * transactions?
468          */

469         public boolean wasLockedConcurrently() {
470             return concurrentLock;
471         }
472         /**
473          * Yes, this is a lock
474          */

475         public boolean isLock() {
476             return true;
477         }
478         /**
479          * locks are not returned to the client!
480          */

481         public boolean isGettable(long txTimestamp) {
482             return false;
483         }
484
485         public int getId() { return id; }
486
487         public String JavaDoc toString() {
488             return "Lock{id=" + id +
489                 ",version=" + version +
490                 ",multiplicity=" + multiplicity +
491                 ",unlockTimestamp=" + unlockTimestamp;
492         }
493
494     }
495
496     public String JavaDoc toString() {
497         return cache + "(read-write)";
498     }
499
500 }
501
502
503
504
505
506
507
Popular Tags