/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.cache.stress;

import EDU.oswego.cs.dl.util.concurrent.Sync;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.lock.ReadWriteLockWithUpgrade;
import org.jboss.logging.Logger;

/**
 * Stress test for RWLock with upgrade.
 */
public class ReadWriteLockWithUpgradeStressTestCase extends TestCase
{
   static final ReadWriteLockWithUpgrade lock_ = new ReadWriteLockWithUpgrade();
   static final long SLEEP_MSECS = 50;
   static final int LOOPS = 200;
   static int SEED = 1;
   static Logger log_ = Logger.getLogger("ReadWriteLockWithUpgrade");
   int counter;

   public ReadWriteLockWithUpgradeStressTestCase(String name)
   {
      super(name);
   }


   public static void main(String[] args) throws Exception
   {
      log("\nBeginning ReadWriteLockWithUpgrade automated testing ...\n");

      junit.textui.TestRunner.run(suite());
   }

   // Needed for JUnit.
   public static Test suite()
   {
      TestSuite suite = new TestSuite();
      // Adding test cases here ...
      suite.addTestSuite(ReadWriteLockWithUpgradeStressTestCase.class);
      return suite;
   }

   public void setUp()
   {
      log("Setting up stress test case ...");
      counter = 0;
   }

   public void tearDown()
   {
      log("Tearing down stress test case ...");
   }

   protected Thread readAttemptThread(String name, final long msecs)
   {
      return new Thread(name)
      {
         public void run()
         {
            java.util.Random rand = new java.util.Random(++SEED);
            for (int i = 0; i < LOOPS; i++) {
               int duration = rand.nextInt((int) SLEEP_MSECS);
               _sleep(SLEEP_MSECS);
               Sync rlock = null;
               try {
                  rlock = lock_.readLock();
                  if (!rlock.attempt(msecs)) {
                     log("Read lock attempt failed.");
//                     fail("Read lock attempt failed.");
                     counter++;
                     continue;
                  }
                  log("acquired read lock");
                  _sleep(duration);
                  log("released read lock");
               } catch (Exception ex) {
               }
               finally {
                  rlock.release();
               }
            }
         }
      };
   }

   protected Thread writeAttemptThread(String name, final long msecs)
   {
      return new Thread(name)
      {
         public void run()
         {
            java.util.Random rand = new java.util.Random(++SEED);
            for (int i = 0; i < LOOPS; i++) {
               int duration = rand.nextInt((int) SLEEP_MSECS);
               _sleep(SLEEP_MSECS + duration);
               Sync wlock = null;
               try {
                  wlock = lock_.writeLock();
                  if (!wlock.attempt(msecs)) {
                     log("Write lock attempt failed.");
//                     fail("Write lock attempt failed.");
                     counter++;
                     continue;
                  }
                  log("acquired write lock");
                  _sleep(duration);
                  log("released write lock");
               } catch (Exception ex) {
               }
               finally {
                  wlock.release();
               }
            }
         }
      };
   }

   protected Thread upgradeAttemptThread(String name, final long msecs)
   {
      return new Thread(name)
      {
         public void run()
         {
            java.util.Random rand = new java.util.Random(++SEED);
            for (int i = 0; i < LOOPS; i++) {
               int duration = rand.nextInt((int) SLEEP_MSECS);
               _sleep(SLEEP_MSECS);
               Sync rlock = null;
               Sync wlock = null;
               try {
                  rlock = lock_.readLock();
                  if (!wlock.attempt(msecs)) {
                     log("Read lock attempt failed.");
//                   fail("Read lock attempt failed.");
                     counter++;
                     continue;
                  }
                  log("Acquired read lock for upgrade later");
                  _sleep(duration / 2);
                  // upgrade lock. Note that read lock will be released
                  // and write lock be acquired automatically.
                  wlock = lock_.upgradeLockAttempt(msecs);
                  if (wlock == null) {
                     log("upgrade lock attempt failed");
//                     fail("upgrade lock attempt failed");
                     rlock.release();
                     counter++;
                  }

                  log("Upgraded write lock");
                  _sleep(duration);
                  log("released write lock");
               } catch (Exception ex) {
               }
               finally {
                  if(wlock != null)
                     wlock.release();
               }
            }
         }
      };
   }

   private void _sleep(long l) {
      try {
         Thread.sleep(l);
      }
      catch(InterruptedException e) {
         e.printStackTrace();
      }
   }

   public void testWriteWriteAttempt() throws Exception
   {
      log("testWriteWriteAttempt() ...");
      final long msecs = 1000;
      Thread t1 = writeAttemptThread("t1-write", msecs);
      Thread t2 = writeAttemptThread("t2-write", msecs);
      Thread t3 = writeAttemptThread("t3-write", msecs);
      Thread t4 = writeAttemptThread("t4-write", msecs);

      t1.start();
      t2.start();
      t3.start();
      t4.start();
      long timeout = 0;
      t1.join(timeout);
      t2.join(timeout);
      t3.join(timeout);
      t4.join(timeout);
      checkCounter();
   }

   public void testReadWriteAttempt1() throws Exception
   {
      log("testReadWriteAttemp1() ...");
      final long msecs = 2000;
      Thread t1 = readAttemptThread("t1-read", msecs);
      Thread t2 = readAttemptThread("t2-read", msecs);
      Thread t3 = writeAttemptThread("t3-write", msecs);
      Thread t4 = writeAttemptThread("t4-write", msecs);
      Thread t5 = upgradeAttemptThread("t5-upgrade", msecs);
      Thread t6 = upgradeAttemptThread("t6-upgrade", msecs);

      t1.start();
      t2.start();
      t3.start();
      t4.start();
      t5.start();
      t6.start();
      long timeout = 0;
      t1.join(timeout);
      t2.join(timeout);
      t3.join(timeout);
      t4.join(timeout);
      t5.join(timeout);
      t6.join(timeout);
      checkCounter();
   }

   public void testReadWriteAttempt2() throws Exception
   {
      log("**************");
      log("testReadWriteAttemp2() ...");
      log("**************");
      SEED++;
      final long msecs = 1000;
      Thread t1 = readAttemptThread("t1-read", msecs);
//       Thread t2 = readAttemptThread("t2-read", msecs);
      Thread t3 = writeAttemptThread("t3-write", msecs);
//       Thread t4 = writeAttemptThread("t4-write", msecs);
      Thread t5 = upgradeAttemptThread("t5-upgrade", msecs);
      Thread t6 = upgradeAttemptThread("t6-upgrade", msecs);

      t1.start();
//       t2.start();
      t3.start();
//       t4.start();
      t5.start();
      t6.start();
      long timeout = 0;
      t1.join(timeout);
//       t2.join(timeout);
      t3.join(timeout);
//       t4.join(timeout);
      t5.join(timeout);
      t6.join(timeout);
      checkCounter();
   }

   public void testReadWriteAttempt3() throws Exception
   {
      log("**************");
      log("testReadWriteAttemp3() ...");
      log("**************");
      SEED++;
      final long msecs = 1000;
      Thread t1 = readAttemptThread("t1-read", msecs);
      Thread t2 = readAttemptThread("t2-read", msecs);
      Thread t3 = writeAttemptThread("t3-write", msecs);
      Thread t4 = writeAttemptThread("t4-write", msecs);
//       Thread t5 = upgradeAttemptThread("t5-upgrade", msecs);
      Thread t6 = upgradeAttemptThread("t6-upgrade", msecs);

      t1.start();
      t2.start();
      t3.start();
      t4.start();
//       t5.start();
      t6.start();
      long timeout = 0;
      t1.join(timeout);
      t2.join(timeout);
      t3.join(timeout);
      t4.join(timeout);
//       t5.join(timeout);
      t6.join(timeout);
      checkCounter();
   }

   private void checkCounter() {
      if(counter > (LOOPS) ) 
          fail("Failed lock attempt or upgrade exceeded warning. Counter: "+counter);
   }

   public static void log(String str)
   {
//       System.out.println(Thread.currentThread() + ": " + " StressTestCase " + " : "
//       +" : "+java.util.Calendar.getInstance().getTime() + " : " +str);
      log_.debug(Thread.currentThread() + ": "
            + " : " + str);
   }
}

