001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.transaction.memory; 018 019import java.util.HashMap; 020import java.util.Map; 021 022import junit.framework.Test; 023import junit.framework.TestSuite; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028import org.apache.commons.transaction.locking.LockException; 029import org.apache.commons.transaction.util.CommonsLoggingLogger; 030import org.apache.commons.transaction.util.LoggerFacade; 031import org.apache.commons.transaction.util.RendezvousBarrier; 032 033/** 034 * Tests for map wrapper. 035 * 036 * @version $Id: PessimisticMapWrapperTest.java 493628 2007-01-07 01:42:48Z joerg $ 037 */ 038public class PessimisticMapWrapperTest extends MapWrapperTest { 039 040 private static final Log log = LogFactory.getLog(PessimisticMapWrapperTest.class.getName()); 041 private static final LoggerFacade sLogger = new CommonsLoggingLogger(log); 042 043 protected static final long TIMEOUT = Long.MAX_VALUE; 044 045 private static int deadlockCnt = 0; 046 047 public static Test suite() { 048 TestSuite suite = new TestSuite(PessimisticMapWrapperTest.class); 049 return suite; 050 } 051 052 public static void main(java.lang.String[] args) { 053 junit.textui.TestRunner.run(suite()); 054 } 055 056 public PessimisticMapWrapperTest(String testName) { 057 super(testName); 058 } 059 060 protected TransactionalMapWrapper getNewWrapper(Map map) { 061 return new PessimisticMapWrapper(map, sLogger); 062 } 063 064 // XXX no need for this code, just to make clear those tests are run as well 065 public void testBasic() throws Throwable { 066 super.testBasic(); 067 } 068 069 public void testComplex() throws Throwable { 070 super.testComplex(); 071 } 072 073 public void testSets() throws Throwable { 074 super.testSets(); 075 } 076 077 public void testMulti() throws Throwable { 078 sLogger.logInfo("Checking concurrent transaction features"); 079 080 final Map map1 = new HashMap(); 081 082 final PessimisticMapWrapper txMap1 = (PessimisticMapWrapper) getNewWrapper(map1); 083 084 Thread thread1 = new Thread(new Runnable() { 085 public void run() { 086 txMap1.startTransaction(); 087 txMap1.put("key1", "value2"); 088 synchronized (txMap1) { 089 txMap1.commitTransaction(); 090 report("value2", (String) txMap1.get("key1")); 091 } 092 } 093 }, "Thread1"); 094 095 txMap1.put("key1", "value1"); 096 097 txMap1.startTransaction(); 098 099 report("value1", (String) txMap1.get("key1")); 100 101 thread1.start(); 102 103 // we have serializable as isolation level, that's why I will still see the old value 104 report("value1", (String) txMap1.get("key1")); 105 106 txMap1.put("key1", "value3"); 107 108 // after commit it must be our value 109 synchronized (txMap1) { 110 txMap1.commitTransaction(); 111 report("value3", (String) txMap1.get("key1")); 112 } 113 } 114 115 public void testConflict() throws Throwable { 116 sLogger.logInfo("Checking concurrent transaction features"); 117 118 final Map map1 = new HashMap(); 119 120 final PessimisticMapWrapper txMap1 = (PessimisticMapWrapper) getNewWrapper(map1); 121 122 final RendezvousBarrier restart = new RendezvousBarrier("restart", 123 TIMEOUT, sLogger); 124 125 for (int i = 0; i < 25; i++) { 126 127 final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i, 128 TIMEOUT, sLogger); 129 130 Thread thread1 = new Thread(new Runnable() { 131 public void run() { 132 txMap1.startTransaction(); 133 try { 134 // first both threads get a lock, this one on res2 135 txMap1.put("key2", "value2"); 136 synchronized (deadlockBarrier1) { 137 deadlockBarrier1.meet(); 138 deadlockBarrier1.reset(); 139 } 140 // if I am first, the other thread will be dead, i.e. 141 // exactly one 142 txMap1.put("key1", "value2"); 143 txMap1.commitTransaction(); 144 } catch (LockException le) { 145 assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM); 146 deadlockCnt++; 147 txMap1.rollbackTransaction(); 148 } catch (InterruptedException ie) { 149 } finally { 150 try { 151 synchronized (restart) { 152 restart.meet(); 153 restart.reset(); 154 } 155 } catch (InterruptedException ie) {} 156 157 } 158 } 159 }, "Thread1"); 160 161 thread1.start(); 162 163 txMap1.startTransaction(); 164 try { 165 // first both threads get a lock, this one on res2 166 txMap1.get("key1"); 167 synchronized (deadlockBarrier1) { 168 deadlockBarrier1.meet(); 169 deadlockBarrier1.reset(); 170 } 171 // if I am first, the other thread will be dead, i.e. exactly 172 // one 173 txMap1.get("key2"); 174 txMap1.commitTransaction(); 175 } catch (LockException le) { 176 assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM); 177 deadlockCnt++; 178 txMap1.rollbackTransaction(); 179 } finally { 180 try { 181 synchronized (restart) { 182 restart.meet(); 183 restart.reset(); 184 } 185 } catch (InterruptedException ie) {} 186 187 } 188 189 // XXX in special scenarios the current implementation might cause both 190 // owners to be deadlock victims 191 if (deadlockCnt != 1) { 192 sLogger.logWarning("More than one thread was deadlock victim!"); 193 } 194 assertTrue(deadlockCnt >= 1); 195 deadlockCnt = 0; 196 } 197 } 198 199 public void testTxControl() throws Throwable { 200 super.testTxControl(); 201 } 202 203}