View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package net.sf.ehcache.hibernate.ccs;
17  
18  import java.io.Serializable;
19  
20  import java.util.Comparator;
21  import java.util.UUID;
22  import java.util.concurrent.atomic.AtomicLong;
23  import java.util.concurrent.locks.ReentrantReadWriteLock;
24  import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
25  import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
26  
27  import org.hibernate.cache.CacheException;
28  import org.hibernate.cache.access.SoftLock;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /***
34   * Ehcache specific read/write cache concurrency strategy.
35   * <p>
36   * This is the Ehcache specific equivalent to Hibernate's ReadWriteCache.  This implementation uses a more robust soft-lock system (less
37   * prone to accidental collisions).
38   *
39   * @author Chris Dennis
40   */
41  @Deprecated
42  public class EhcacheReadWriteCache extends AbstractEhcacheConcurrencyStrategy {
43  
44      private static final Logger LOG = LoggerFactory.getLogger(EhcacheReadWriteCache.class);
45      
46      private final UUID uuid = UUID.randomUUID();
47      private final AtomicLong nextLockId = new AtomicLong();
48      private final ReadLock coarseReadLock;
49      private final WriteLock coarseWriteLock;
50      {
51          ReentrantReadWriteLock coarseLock = new ReentrantReadWriteLock();
52          coarseReadLock = coarseLock.readLock();
53          coarseWriteLock = coarseLock.writeLock();
54      }
55      
56      /***
57       * {@inheritDoc}
58       */
59      public Object get(Object key, long txTimestamp) throws CacheException {
60          readLockIfCoarse(key);
61          try {
62              Lockable item = (Lockable) cache.get(key);
63  
64              boolean readable = item != null && item.isReadable(txTimestamp);
65              if (readable) {
66                  return item.getValue();
67              } else {
68                  return null;
69              }
70          } finally {
71              readUnlockIfCoarse(key);
72          }
73      }
74  
75      /***
76       * {@inheritDoc}
77       */
78      public boolean put(Object key, Object value, long txTimestamp, Object version, Comparator versionComparator, boolean minimalPut)
79              throws CacheException {
80  
81          writeLock(key);
82          try {
83              Lockable item = (Lockable) cache.get(key);
84              boolean writeable = item == null || item.isWriteable(txTimestamp, version, versionComparator);
85              if (writeable) {
86                  cache.put(key, new Item(value, version, cache.nextTimestamp()));
87                  return true;
88              } else {
89                  return false;
90              }
91          } finally {
92              writeUnlock(key);
93          }
94      }
95  
96      /***
97       * Soft-locks the associated mapping prior to updating/inserting a new value.
98       */
99      public SoftLock lock(Object key, Object version) throws CacheException {
100         writeLock(key);
101         try {
102             Lockable item = (Lockable) cache.get(key);
103             long timeout = cache.nextTimestamp() + cache.getTimeout();
104             final Lock lock = (item == null) ? new Lock(timeout, uuid, nextLockId(), version) : item.lock(timeout, uuid, nextLockId());
105             cache.update(key, lock);
106             return lock;
107         } finally {
108             writeUnlock(key);
109         }
110     }
111 
112     /***
113      * Soft-unlocks the associated mapping.
114      */
115     public void release(Object key, SoftLock lock) throws CacheException {
116         writeLock(key);
117         try {
118             Lockable item = (Lockable) cache.get(key);
119 
120             if ((item != null) && item.isUnlockable(lock)) {
121                 decrementLock(key, (Lock) item);
122             } else {
123                 handleLockExpiry(key);
124             }
125         } finally {
126             writeUnlock(key);
127         }
128     }
129 
130     /***
131      * {@inheritDoc}
132      */
133     public boolean afterUpdate(Object key, Object value, Object version, SoftLock softlock) throws CacheException {
134         writeLock(key);
135         try {
136             Lockable item = (Lockable) cache.get(key);
137 
138             if (item != null && item.isUnlockable(softlock)) {
139                 Lock lock = (Lock) item;
140                 if (lock.wasLockedConcurrently()) {
141                     decrementLock(key, lock);
142                     return false;
143                 } else {
144                     cache.update(key, new Item(value, version, cache.nextTimestamp()));
145                     return true;
146                 }
147             } else {
148                 handleLockExpiry(key);
149                 return false;
150             }
151         } finally {
152             writeUnlock(key);
153         }
154     }
155 
156     /***
157      * {@inheritDoc}
158      */
159     public boolean afterInsert(Object key, Object value, Object version) throws CacheException {
160         writeLock(key);
161         try {
162             Lockable item = (Lockable) cache.get(key);
163             if (item == null) {
164                 cache.update(key, new Item(value, version, cache.nextTimestamp()));
165                 return true;
166             } else {
167                 return false;
168             }
169         } finally {
170             writeUnlock(key);
171         }
172     }
173 
174     /***
175      * A No-Op, since we are an asynchronous cache concurrency strategy.
176      */
177     public void evict(Object key) throws CacheException {
178         // no-op - since we are not transactional
179     }
180 
181     /***
182      * A No-Op, since we are an asynchronous cache concurrency strategy.
183      */
184     public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException {
185         return false;
186     }
187 
188     /***
189      * A No-Op, since we are an asynchronous cache concurrency strategy.
190      */
191     public boolean insert(Object key, Object value, Object currentVersion) throws CacheException {
192         return false;
193     }
194 
195     private long nextLockId() {
196         return nextLockId.getAndIncrement();
197     }
198     
199     private void decrementLock(Object key, Lock lock) {
200         lock.unlock(cache.nextTimestamp());
201         cache.update(key, lock);
202     }
203 
204     private void handleLockExpiry(Object key) {
205         long ts = cache.nextTimestamp() + cache.getTimeout();
206         // create new lock that times out immediately
207         Lock lock = new Lock(ts, uuid, nextLockId.getAndIncrement(), null);
208         lock.unlock(ts);
209         cache.update(key, lock);
210     }
211 
212     private void writeLock(Object key) {
213         if (cache.canLockEntries()) {
214             cache.lock(key);
215         } else {
216             coarseWriteLock.lock();
217         }
218     }
219 
220     private void writeUnlock(Object key) {
221         if (cache.canLockEntries()) {
222             cache.unlock(key);
223         } else {
224             coarseWriteLock.unlock();
225         }
226     }
227 
228     private void readLockIfCoarse(Object key) {
229         if (!cache.canLockEntries()) {
230             coarseReadLock.lock();
231         }
232     }
233 
234     private void readUnlockIfCoarse(Object key) {
235         if (!cache.canLockEntries()) {
236             coarseReadLock.unlock();
237         }
238     }
239 
240     /***
241      * Interface type implemented by all wrapper objects in the cache.
242      */
243     private static interface Lockable {
244 
245         public boolean isReadable(long txTimestamp);
246 
247         public boolean isWriteable(long txTimestamp, Object version, Comparator versionComparator);
248 
249         public Object getValue();
250 
251         public boolean isUnlockable(SoftLock lock);
252 
253         public Lock lock(long timeout, UUID uuid, long lockId);
254     }
255 
256     /***
257      * Wrapper type representing unlocked items.
258      */
259     private static final class Item implements Serializable, Lockable {
260 
261         private static final long serialVersionUID = 1L;
262         private final Object value;
263         private final Object version;
264         private final long timestamp;
265 
266         private Item(Object value, Object version, long timestamp) {
267             this.value = value;
268             this.version = version;
269             this.timestamp = timestamp;
270         }
271 
272         public boolean isReadable(long txTimestamp) {
273             return txTimestamp > timestamp;
274         }
275 
276         public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
277             return version != null && versionComparator.compare(version, newVersion) < 0;
278         }
279 
280         public Object getValue() {
281             return value;
282         }
283 
284         public boolean isUnlockable(SoftLock lock) {
285             return false;
286         }
287 
288         public Lock lock(long timeout, UUID uuid, long lockId) {
289             return new Lock(timeout, uuid, lockId, version);
290         }
291     }
292 
293     /***
294      * Wrapper type representing locked items.
295      */
296     private static final class Lock implements Serializable, Lockable, SoftLock {
297 
298         private static final long serialVersionUID = 2L;
299 
300         private final UUID sourceUuid;
301         private final long lockId;
302         private final Object version;
303         
304         private long timeout;
305         private boolean concurrent;
306         private int multiplicity;
307         private long unlockTimestamp;
308 
309         Lock(long timeout, UUID sourceUuid, long lockId, Object version) {
310             this.timeout = timeout;
311             this.lockId = lockId;
312             this.version = version;
313             this.sourceUuid = sourceUuid;
314         }
315 
316         public boolean isReadable(long txTimestamp) {
317             return false;
318         }
319 
320         public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
321             if (txTimestamp > timeout) {
322                 // if timedout then allow write
323                 return true;
324             }
325             if (multiplicity > 0) {
326                 // if still locked then disallow write
327                 return false;
328             }
329             return version == null ? txTimestamp > unlockTimestamp : versionComparator.compare(version, newVersion) < 0;
330         }
331 
332         public Object getValue() {
333             return null;
334         }
335 
336         public boolean isUnlockable(SoftLock lock) {
337             return equals(lock);
338         }
339 
340         @Override
341         public boolean equals(Object o) {
342             if (o == this) {
343                 return true;
344             } else if (o instanceof Lock) {
345                 return (lockId == ((Lock) o).lockId) && sourceUuid.equals(((Lock) o).sourceUuid);
346             } else {
347                 return false;
348             }
349         }
350 
351         @Override
352         public int hashCode() {
353             int hash = (sourceUuid != null ? sourceUuid.hashCode() : 0);
354             int temp = (int) lockId;
355             for (int i = 1; i < Long.SIZE / Integer.SIZE; i++) {
356                 temp ^= (lockId >>> (i * Integer.SIZE));
357             }
358             return hash + temp;
359         }
360 
361         private boolean wasLockedConcurrently() {
362             return concurrent;
363         }
364 
365         public Lock lock(long timeout, UUID uuid, long lockId) {
366             concurrent = true;
367             multiplicity++;
368             this.timeout = timeout;
369             return this;
370         }
371 
372         private void unlock(long timestamp) {
373             if (--multiplicity == 0) {
374                 unlockTimestamp = timestamp;
375             }
376         }
377     }
378 }