1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.transaction.memory;
18  
19  import java.util.Map;
20  import java.util.concurrent.TimeUnit;
21  
22  import junit.framework.JUnit4TestAdapter;
23  import static junit.framework.Assert.*;
24  import org.junit.Test;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.apache.commons.transaction.locking.LockException;
30  import org.apache.commons.transaction.locking.RWLockManager;
31  import org.apache.commons.transaction.util.RendezvousBarrier;
32  
33  /**
34   * Tests for map wrapper.
35   */
36  public class PessimisticTxMapTest extends BasicTxMapTest {
37  
38      private static final Log log = LogFactory.getLog(PessimisticTxMapTest.class.getName());
39  
40      protected static final long TIMEOUT = Long.MAX_VALUE;
41  
42      private static int deadlockCnt = 0;
43  
44      public static junit.framework.Test suite() {
45          return new JUnit4TestAdapter(PessimisticTxMapTest.class);
46      }
47  
48      public static void main(java.lang.String[] args) {
49          junit.textui.TestRunner.run(suite());
50      }
51  
52      @Test
53      public void testMulti() {
54          log.info("Checking concurrent transaction features");
55  
56          final PessimisticTxMap<String, String> txMap1 = new PessimisticTxMap<String, String>(
57                  "txMap1");
58          final Map map1 = txMap1.getWrappedMap();
59  
60          Thread thread1 = new Thread(new Runnable() {
61              public void run() {
62                  txMap1.startTransaction(5, TimeUnit.MINUTES);
63                  txMap1.put("key1", "value2");
64                  synchronized (txMap1) {
65                      txMap1.commitTransaction();
66                      report("value2", (String) txMap1.get("key1"));
67                  }
68              }
69          }, "Thread1");
70  
71          txMap1.put("key1", "value1");
72  
73          txMap1.startTransaction(5, TimeUnit.MINUTES);
74  
75          report("value1", (String) txMap1.get("key1"));
76  
77          thread1.start();
78  
79          // we have serializable as isolation level, that's why I will still see
80          // the old value
81          report("value1", (String) txMap1.get("key1"));
82  
83          txMap1.put("key1", "value3");
84  
85          // after commit it must be our value
86          synchronized (txMap1) {
87              txMap1.commitTransaction();
88              report("value3", (String) txMap1.get("key1"));
89          }
90          try {
91              thread1.join();
92          } catch (InterruptedException e) {
93              // TODO Auto-generated catch block
94              e.printStackTrace();
95          }
96      }
97  
98      @Test
99      public void testConflict() {
100         log.info("Checking concurrent conflict resolvation features");
101 
102         final RWLockManager<Object, Object> lm = new RWLockManager<Object, Object>();
103         // adds more concurrency and makes test run faster
104         lm.setAbsolutePrewaitTime(0);
105         final PessimisticTxMap<String, String> txMap1 = new PessimisticTxMap<String, String>(
106                 "txMap1", lm);
107         final Map map1 = txMap1.getWrappedMap();
108 
109         final RendezvousBarrier restart = new RendezvousBarrier("restart", TIMEOUT);
110 
111         int conflictingRuns = 0;
112         int runs = 25;
113 
114         for (int i = 0; i < runs; i++) {
115             System.out.println(i);
116 
117             final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i,
118                     TIMEOUT);
119 
120             Thread thread1 = new Thread(new Runnable() {
121                 public void run() {
122                     txMap1.startTransaction(5, TimeUnit.MINUTES);
123                     try {
124                         // first both threads get a lock, this one on key2
125                         txMap1.put("key2", "value2");
126                         synchronized (deadlockBarrier1) {
127                             deadlockBarrier1.meet();
128                             deadlockBarrier1.reset();
129                         }
130                         // if I am first, the other thread will be dead, i.e.
131                         // exactly one
132                         txMap1.put("key1", "value2");
133                         txMap1.commitTransaction();
134                     } catch (LockException le) {
135                         assertEquals(le.getCode(), LockException.Code.WOULD_DEADLOCK);
136                         deadlockCnt++;
137                         txMap1.rollbackTransaction();
138                     } catch (InterruptedException ie) {
139                     } finally {
140                         try {
141                             synchronized (restart) {
142                                 restart.meet();
143                                 restart.reset();
144                             }
145                         } catch (InterruptedException ie) {
146                         }
147 
148                     }
149                 }
150             }, "Thread1");
151 
152             thread1.start();
153 
154             txMap1.startTransaction(5, TimeUnit.MINUTES);
155             try {
156                 // first both threads get a lock, this one on key1
157                 txMap1.get("key1");
158                 synchronized (deadlockBarrier1) {
159                     try {
160                         deadlockBarrier1.meet();
161                     } catch (InterruptedException e) {
162                         // TODO Auto-generated catch block
163                         e.printStackTrace();
164                     }
165                     deadlockBarrier1.reset();
166                 }
167                 // if I am first, the other thread will be dead, i.e. exactly
168                 // one
169                 txMap1.get("key2");
170                 txMap1.commitTransaction();
171             } catch (LockException le) {
172                 assertEquals(le.getCode(), LockException.Code.WOULD_DEADLOCK);
173                 deadlockCnt++;
174                 txMap1.rollbackTransaction();
175             } finally {
176                 try {
177                     synchronized (restart) {
178                         restart.meet();
179                         restart.reset();
180                     }
181                 } catch (InterruptedException ie) {
182                 }
183 
184             }
185 
186             // XXX in special scenarios the current implementation might cause
187             // both owners to be deadlock victims
188             if (deadlockCnt != 1) {
189                 // log.warn("More than one thread was deadlock victim!");
190                 conflictingRuns++;
191             }
192             assertTrue(deadlockCnt >= 1);
193             deadlockCnt = 0;
194 
195             try {
196                 thread1.join();
197             } catch (InterruptedException e) {
198                 // TODO Auto-generated catch block
199                 e.printStackTrace();
200             }
201 
202         }
203         System.out.println();
204         System.out.println("Of the " + runs + " there were " + conflictingRuns
205                 + " runs that rolled back both transactions!");
206     }
207 
208 }