View Javadoc

1   package net.sf.ehcache.transaction;
2   
3   import bitronix.tm.BitronixTransactionManager;
4   import bitronix.tm.TransactionManagerServices;
5   import bitronix.tm.internal.TransactionStatusChangeListener;
6   import net.sf.ehcache.Cache;
7   import net.sf.ehcache.CacheManager;
8   import net.sf.ehcache.Ehcache;
9   import net.sf.ehcache.Element;
10  import net.sf.ehcache.TransactionController;
11  import net.sf.ehcache.config.CacheConfiguration;
12  import net.sf.ehcache.config.Configuration;
13  import net.sf.ehcache.config.DiskStoreConfiguration;
14  import net.sf.ehcache.transaction.xa.EhcacheXAResourceImpl;
15  import net.sf.ehcache.transaction.xa.XATransactionStore;
16  import net.sf.ehcache.transaction.xa.XidTransactionID;
17  import net.sf.ehcache.util.RetryAssert;
18  
19  import org.hamcrest.core.Is;
20  import org.junit.After;
21  import org.junit.Before;
22  import org.junit.Test;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.ObjectInputStream;
27  import java.io.ObjectOutputStream;
28  import java.lang.reflect.Field;
29  import java.util.concurrent.Callable;
30  import java.util.concurrent.TimeUnit;
31  
32  import javax.transaction.Status;
33  import javax.transaction.SystemException;
34  import javax.transaction.xa.Xid;
35  
36  import static junit.framework.Assert.assertNull;
37  import static junit.framework.Assert.assertTrue;
38  import static org.junit.Assert.assertEquals;
39  
40  /***
41   * @author lorban
42   */
43  public class SoftLockPinningTest {
44  
45      private CacheManager cacheManager;
46      private Ehcache cache1;
47      private Ehcache cache2;
48      private Ehcache xaCache1;
49      private Ehcache xaCache2;
50      private TransactionController transactionController;
51      private BitronixTransactionManager transactionManager;
52  
53      @Before
54      public void setUp() throws Exception {
55          TransactionManagerServices.getConfiguration().setJournal("null").setServerId("SoftLockPinningTest");
56          transactionManager = TransactionManagerServices.getTransactionManager();
57  
58          cacheManager = new CacheManager(new Configuration().diskStore(new DiskStoreConfiguration().path(System.getProperty("java.io.tmpdir")))
59              .cache(new CacheConfiguration().name("localTxCache1")
60                  .maxEntriesLocalHeap(1)
61                  .overflowToDisk(true)
62                  .transactionalMode(CacheConfiguration.TransactionalMode.LOCAL)
63              )
64              .cache(new CacheConfiguration().name("localTxCache2")
65                  .maxEntriesLocalHeap(1)
66                  .overflowToDisk(true)
67                  .transactionalMode(CacheConfiguration.TransactionalMode.LOCAL)
68              )
69              .cache(new CacheConfiguration().name("xaCache1")
70                  .maxEntriesLocalHeap(1)
71                  .overflowToDisk(true)
72                  .transactionalMode(CacheConfiguration.TransactionalMode.XA_STRICT)
73              )
74              .cache(new CacheConfiguration().name("xaCache2")
75                  .maxEntriesLocalHeap(1)
76                  .overflowToDisk(true)
77                  .transactionalMode(CacheConfiguration.TransactionalMode.XA_STRICT)
78              )
79          );
80  
81          transactionController = cacheManager.getTransactionController();
82          transactionController.begin();
83          cache1 = cacheManager.getEhcache("localTxCache1");
84          cache1.removeAll();
85          cache2 = cacheManager.getEhcache("localTxCache2");
86          cache2.removeAll();
87          transactionController.commit();
88  
89          transactionManager.begin();
90          xaCache1 = cacheManager.getEhcache("xaCache1");
91          xaCache1.removeAll();
92          xaCache2 = cacheManager.getEhcache("xaCache2");
93          xaCache2.removeAll();
94          transactionManager.commit();
95      }
96  
97      @After
98      public void tearDown() throws Exception {
99          if (transactionController.getCurrentTransactionContext() != null) {
100             transactionController.rollback();
101         }
102         if (transactionManager.getStatus() != Status.STATUS_NO_TRANSACTION) {
103             transactionManager.rollback();
104         }
105         transactionManager.shutdown();
106         cacheManager.shutdown();
107     }
108 
109     @Test
110     public void testDiskBackedCacheLocalTx() throws Exception {
111         transactionController.begin();
112 
113         for (int i = 0; i < 100; i++) {
114             Element element1 = new Element(i, i);
115             element1.setTimeToIdle(1);
116             element1.setTimeToLive(1);
117             cache1.put(element1);
118 
119             Element element2 = new Element(i, i);
120             element2.setTimeToIdle(1);
121             element2.setTimeToLive(1);
122             cache2.put(element2);
123         }
124 
125         assertEquals(100, cache1.getMemoryStoreSize());
126         assertEquals(100, cache2.getMemoryStoreSize());
127         RetryAssert.assertBy(5, TimeUnit.SECONDS, new Callable<Integer>() {
128             public Integer call() throws Exception {
129                 return cache1.getDiskStoreSize();
130             }
131         }, Is.is(100));
132         RetryAssert.assertBy(5, TimeUnit.SECONDS, new Callable<Integer>() {
133             public Integer call() throws Exception {
134                 return cache2.getDiskStoreSize();
135             }
136         }, Is.is(100));
137 
138         // wait more than TTI/TTL, soft locked elements are both pinned and eternal, they must not be evicted
139         Thread.sleep(1999);
140 
141         transactionController.commit();
142 
143         transactionController.begin();
144         assertEquals(100, cache1.getSize());
145         assertEquals(100, cache2.getSize());
146         transactionController.commit();
147 
148         // wait more than TTI/TTL, elements must have been evicted
149         Thread.sleep(1999);
150 
151         transactionController.begin();
152         for (int i = 0; i < 100; i++) {
153             assertNull("cache1 key " + i, cache1.get(i));
154             assertNull("cache2 key " + i, cache2.get(i));
155         }
156         transactionController.commit();
157     }
158 
159     @Test
160     public void testDiskBackedCacheXaStrictTx() throws Exception {
161         transactionManager.begin();
162 
163         for (int i = 0; i < 100; i++) {
164             Element element1 = new Element(i, i);
165             element1.setTimeToIdle(1);
166             element1.setTimeToLive(1);
167             xaCache1.put(element1);
168 
169             Element element2 = new Element(i, i);
170             element2.setTimeToIdle(1);
171             element2.setTimeToLive(1);
172             xaCache2.put(element2);
173         }
174 
175         // The soft locked elements are put into the underlying store during prepare -> assertions have to occur between phase 1 and 2 of 2PC
176         // Fortunately, there are some good JTA implementations out there (*wink*, *wink*) that do have useful non-standard extensions which
177         // make this check easy to implement. We must also make sure to use at least 2 XA resources to prevent the 1PC optimization to kick in.
178         transactionManager.getCurrentTransaction().addTransactionStatusChangeListener(new TransactionStatusChangeListener() {
179             public void statusChanged(final int oldStatus, final int newStatus) {
180                 if (oldStatus == Status.STATUS_PREPARED) {
181 
182                     assertEquals(100, xaCache1.getMemoryStoreSize());
183                     assertEquals(100, xaCache2.getMemoryStoreSize());
184                     RetryAssert.assertBy(5, TimeUnit.SECONDS, new Callable<Integer>() {
185                         public Integer call() throws Exception {
186                             return xaCache1.getDiskStoreSize();
187                         }
188                     }, Is.is(100));
189                     RetryAssert.assertBy(5, TimeUnit.SECONDS, new Callable<Integer>() {
190                         public Integer call() throws Exception {
191                             return xaCache2.getDiskStoreSize();
192                         }
193                     }, Is.is(100));
194 
195                 }
196             }
197         });
198 
199         // wait more than TTI/TTL, soft locked elements are both pinned and eternal, they must not be evicted
200         Thread.sleep(1999);
201 
202         transactionManager.commit();
203 
204         transactionManager.begin();
205         assertEquals(100, xaCache1.getSize());
206         assertEquals(100, xaCache2.getSize());
207         transactionManager.commit();
208 
209         // wait more than TTI/TTL, elements must have been evicted
210         Thread.sleep(1999);
211 
212         transactionManager.begin();
213         for (int i = 0; i < 100; i++) {
214             assertNull("xaCache1 key " + i, xaCache1.get(i));
215             assertNull("xaCache2 key " + i, xaCache2.get(i));
216         }
217         transactionManager.commit();
218     }
219 
220     @Test
221     public void testSoftLockSerialization() throws Exception {
222         transactionController.begin();
223 
224         TransactionID transactionId = transactionController.getCurrentTransactionContext().getTransactionId();
225         SoftLock originalSoftLock = cacheManager.getSoftLockFactory(cache1.getName()).createSoftLock(transactionId, -1, null, null);
226 
227         // serialize a soft lock
228         ByteArrayOutputStream baos = new ByteArrayOutputStream();
229         ObjectOutputStream oos = new ObjectOutputStream(baos);
230         oos.writeObject(originalSoftLock);
231         oos.close();
232 
233         // deserialize the soft lock
234         ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
235         SoftLock deserializedSoftLock = (SoftLock)ois.readObject();
236         ois.close();
237 
238         // check that we got the original soft lock back
239         assertTrue(originalSoftLock == deserializedSoftLock);
240 
241         transactionController.commit();
242     }
243 
244     @Test
245     public void testXaSoftLockSerialization() throws Exception {
246         transactionManager.begin();
247         XidTransactionID xidTransactionID = findXidTransactionId(xaCache1);
248 
249         SoftLock originalSoftLock = cacheManager.getSoftLockFactory(xaCache1.getName()).createSoftLock(xidTransactionID, -1, null, null);
250 
251         // serialize a soft lock
252         ByteArrayOutputStream baos = new ByteArrayOutputStream();
253         ObjectOutputStream oos = new ObjectOutputStream(baos);
254         oos.writeObject(originalSoftLock);
255         oos.close();
256 
257         // deserialize the soft lock
258         ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
259         SoftLock deserializedSoftLock = (SoftLock)ois.readObject();
260         ois.close();
261 
262         // check that we got the original soft lock back
263         assertTrue(originalSoftLock == deserializedSoftLock);
264 
265         transactionManager.commit();
266     }
267 
268     private XidTransactionID findXidTransactionId(Ehcache xaCache) throws NoSuchFieldException, IllegalAccessException, SystemException {
269         // an element must be added to the cache so that it gets enlisted and we can get its XAResource's currentXid
270         xaCache.put(new Element(1, 1));
271 
272         Field compoundStoreField = Cache.class.getDeclaredField("compoundStore");
273         compoundStoreField.setAccessible(true);
274         XATransactionStore xaTransactionStore = (XATransactionStore)compoundStoreField.get(xaCache);
275         EhcacheXAResourceImpl ehcacheXAResource = xaTransactionStore.getOrCreateXAResource();
276 
277         Field currentXidField = EhcacheXAResourceImpl.class.getDeclaredField("currentXid");
278         currentXidField.setAccessible(true);
279         Xid currentXid = (Xid)currentXidField.get(ehcacheXAResource);
280 
281         Field transactionIdFactoryField = XATransactionStore.class.getDeclaredField("transactionIdFactory");
282         transactionIdFactoryField.setAccessible(true);
283         TransactionIDFactory transactionIdFactory = (TransactionIDFactory)transactionIdFactoryField.get(xaTransactionStore);
284 
285         return transactionIdFactory.createXidTransactionID(currentXid);
286     }
287 
288 }