package org.jboss.cache;

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

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

   public ReadWriteLockWithUpgradeStressTest(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(ReadWriteLockWithUpgradeStressTest.class);
      return suite;
   }

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

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


   public void testTimedWriteLocks() throws InterruptedException {
      ReadWriteLockWithUpgrade l = new ReadWriteLockWithUpgrade();
      final int NUM=1000000;

      for(int i=0; i < NUM; i++) {
         l.writeLock().attempt(2000);
         l.writeLock().release();
         if(i % 10000 == 0)
            System.out.println(i);
      }
   }


   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);
   }
}

