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}