/*
 * 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.test.generic;

import EDU.oswego.cs.dl.util.concurrent.CyclicBarrier;
import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore;
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.*;

/**
 * Various tests that test isolation level semantics provided by locks
 *
 * @author Bela Ban
 * @version $Id: LockUnitTestCase.java,v 1.4.2.4 2005/12/01 19:50:26 dimitris Exp $
 */
public class LockUnitTestCase extends TestCase {
   int  value=10;
   Throwable t1_ex, t2_ex;
   long start=0;
   final long TIMEOUT=5000;
   final long SLEEP=500;


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


   public void setUp() {
   }

   public void tearDown() {
      t1_ex=t2_ex=null;
   }


   class MyFIFOSemaphore extends FIFOSemaphore {
      public MyFIFOSemaphore(long l) {
         super(l);
      }

      public void acquire() throws InterruptedException {
         // log("acquiring sem (permits=" + permits() + ")");
         super.acquire();
         // log("acquired sem (permits=" + permits() + ")");
      }

      public void release() {
         // log("releasing sem (permits=" + permits() + ")");
         super.release();
         // log("released sem (permits=" + permits() + ")");
      }
   }


   /**
    * Thread1 reads data, thread2 changes and - before thread2 commits - t1 should see t2's changes.
    * Timeline:
    * <ol>
    * T1 reads data - 10
    * T2 writes data - 20
    * T1 reads data - 20 (see's T2's uncommitted modfication)
    * T2 commits (releases its lock)
    * T1 reads data - 20
    * </ol>
    */
   public void testReadUncommitted() throws Throwable {
      final LockStrategy s=new LockStrategyReadUncommitted();
      final FIFOSemaphore sem=new MyFIFOSemaphore(1);
      final CyclicBarrier barrier=new CyclicBarrier(2);

      Thread t1=new Thread("t1") {
         Sync lock=null;

         public void run() {
            try {
               sem.acquire(); // we're first to the semaphore

               // log("waiting on barrier");
               barrier.barrier(); // wait until t2 joins us
               // log("passed barrier");
               lock=s.readLock();
               lock.attempt(TIMEOUT);
               log("1st read: value is " + value);
               assertEquals(10, value);
               sem.release(); // give t2 time to make the modification
               _sleep(100);

               sem.acquire(); // to read the uncommitted modification by t2
               log("2nd read: value is " + value + "; we should see t2's uncommitted change (20)");
               assertEquals(20, value); // we're seeing the modification by t2 before t2 committed (a.k.a. released the lock)
               sem.release();
               _sleep(100);

               sem.acquire(); // to read the committed change by t2
               log("3rd read: value is still " + value + "; we should see t2's committed change");
               assertEquals(20, value);
            }
            catch(Throwable ex) {
               t1_ex=ex;
            }
            finally {
               if(lock != null)
                  lock.release();
               sem.release();
            }
         }
      };


      Thread t2=new Thread("t2") {
         Sync lock=null;

         public void run() {
            try {
               _sleep(100);
               // log("waiting on barrier");
               barrier.barrier();
               // log("passed barrier");
               sem.acquire();
               lock=s.writeLock();
               lock.attempt(TIMEOUT);
               log("changing value from " + value + " to 20");
               value=20;
               sem.release(); // now t1 can read the uncommitted modification
               _sleep(100);

               sem.acquire(); // to release the lock
               log("committing the TX");
               lock.release();
            }
            catch(Throwable ex) {
               t2_ex=ex;
            }
            finally {
               if(lock != null)
                  lock.release();
               sem.release();
            }
         }
      };

      t1.start();
      t2.start();
      t1.join();
      t2.join();
      if(t1_ex != null)
         throw t1_ex;
      if(t2_ex != null)
         throw t2_ex;
   }



   /**
    * Thread1 reads data, thread2 changes and - before thread2 commits - t1 should *not* see t2's changes.
    * Timeline:
    * <ol>
    * T1 reads data - 10
    * T2 writes data - 20 (*not* visible to T1)
    * T1 reads data - 10 (should *not* see T2's uncommitted modfication)
    * T2 commits (releases its lock)
    * T1 sees T2's committed modification - 20
    * </ol>
    * <em>Commented for now, until we get the right semantics</em>
    */
//   public void testReadCommitted() throws Throwable {
//      final LockStrategy s=new LockStrategyReadCommitted();
//
//      Thread t1=new Thread("t1") {
//         Sync lock=null;
//
//         public void run() {
//            try {
//               lock=s.readLock();
//               lock.attempt(TIMEOUT);
//               log("1st read: value is " + value);
//               assertEquals(10, value);
//               _sleep(SLEEP);
//
//               log("2nd read: value is " + value + "; we should *not* see t2's uncommitted change (20)");
//               assertEquals(10, value); // we're seeing the modification by t2 before t2 committed (a.k.a. released the lock)
//               _sleep(SLEEP);
//
//               log("3rd read: value is still " + value + "; we should see t2's committed change");
//               assertEquals(20, value);
//            }
//            catch(Throwable ex) {
//               t1_ex=ex;
//            }
//            finally {
//               lock.release();
//            }
//         }
//      };
//
//
//      Thread t2=new Thread("t2") {
//         Sync lock=null;
//
//         public void run() {
//            try {
//               _sleep(100);
//               lock=s.writeLock();
//               lock.attempt(TIMEOUT);
//               log("changing value from " + value + " to 20");
//               value=20;
//               _sleep(SLEEP);
//
//               log("committing the TX");
//               lock.release();
//            }
//            catch(Throwable ex) {
//               t2_ex=ex;
//            }
//            finally {
//               lock.release();
//            }
//         }
//      };
//
//      t1.start();
//      t2.start();
//      t1.join();
//      t2.join();
//      if(t1_ex != null)
//         throw t1_ex;
//      if(t2_ex != null)
//         throw t2_ex;
//   }



   public void testWriteThanRead() throws Throwable {
       final LockStrategy s=new LockStrategyReadCommitted();

       Thread t1=new Thread("t1") {
          Sync lock=null;

          public void run() {
             try {
                _sleep(100);
                lock=s.readLock();
                lock.attempt(TIMEOUT);
                log("1st read: value is " + value);
                assertEquals(20, value);
                _sleep(SLEEP);

                log("2nd read: value is " + value + "; we should see t2's uncommitted change (20)");
                assertEquals(20, value); // we're seeing the modification by t2 before t2 committed (a.k.a. released the lock)
                _sleep(SLEEP);
             }
             catch(Throwable ex) {
                t1_ex=ex;
             }
             finally {
                lock.release();
             }
          }
       };


       Thread t2=new Thread("t2") {
          Sync lock=null;

          public void run() {
             try {
                lock=s.writeLock();
                lock.attempt(TIMEOUT);
                log("changing value from " + value + " to 20");
                value=20;
                _sleep(SLEEP);

                log("committing the TX");
                lock.release();
             }
             catch(Throwable ex) {
                t2_ex=ex;
             }
             finally {
                lock.release();
             }
          }
       };

       t2.start();
       t1.start();
       t2.join();
       t1.join();
       if(t1_ex != null)
          throw t1_ex;
       if(t2_ex != null)
          throw t2_ex;
    }




   /**
    * Thread1 reads data, thread2 changes and - before thread2 commits - t1 should *not* see t2's changes.
    * In addition, Thread1 should *not* see thread2's changes even after thread2 commits, until thread1 commits.
    * Timeline:
    * <ol>
    * T1 reads data - 10
    * T2 writes data - 20 (*not* visible to T1)
    * T1 reads data - 10 (should *not* see T2's uncommitted modfication)
    * T2 commits (releases its lock)
    * T1 reads data, should *not* see T2's committed modification - 10
    * T1 commits
    * T1 starts a new TX - should see 20
    * </ol>
    * Note: because we use pessimistic locking, the above sequence will effectively be serialized into sequential
    * execution: thread1 will acquire the read lock on the data and hold on to it until TX commit, only then will
    * thread2 be able to access the data with a write lock.
    */
   public void testRepeatableRead() throws Throwable {
      final LockStrategy s=new LockStrategyRepeatableRead();

      Thread t1=new Thread("t1") {
         Sync lock=null;

         public void run() {
            try {
               lock=s.readLock();
               lock.attempt(TIMEOUT);
               log("1st read: value is " + value);
               assertEquals(10, value);
               _sleep(SLEEP);

               log("2nd read: value is " + value + "; we should *not* see t2's uncommitted change (20)");
               assertEquals(10, value);
               _sleep(SLEEP);

               log("3rd read: value is still " + value + "; we should not see t2's committed change");
               assertEquals(10, value);
               lock.release();

               _sleep(SLEEP);
               lock.attempt(TIMEOUT);
               log("4th read: value is now " + value + "; we should see t2's committed change in our new TX");
               assertEquals(20, value);
            }
            catch(Throwable ex) {
               t1_ex=ex;
            }
            finally {
               lock.release();
            }
         }
      };


      Thread t2=new Thread("t2") {
         Sync lock=null;

         public void run() {
            try {
               _sleep(100);
               lock=s.writeLock();
               lock.attempt(TIMEOUT);
               log("changing value from " + value + " to 20");
               value=20;
               _sleep(SLEEP);

               log("committing the TX");
               lock.release();
            }
            catch(Throwable ex) {
               t2_ex=ex;
            }
            finally {
               lock.release();
            }
         }
      };

      t1.start();
      t2.start();
      t1.join();
      t2.join();
      if(t1_ex != null)
         throw t1_ex;
      if(t2_ex != null)
         throw t2_ex;
   }


   /**
    * Because only 1 reader or writer can hold the lock at any given time, since thread1 is the first to get the lock,
    * it will hold on to it until it commits. The the writer thread (thread2) will have a chance to change the value.
    * Timeline:
    * <ol>
    * T1 reads data - 10
    * T1 commits
    * T2 writes data - 20
    * T2 commits
    * T1 starts a new TX and reads data - 20
    * T2 commits (releases its lock)
    * </ol>
    */
   public void testSerializable() throws Throwable {
      final LockStrategy s=new LockStrategySerializable();

      Thread t1=new Thread("t1") {
         Sync lock=null;

         public void run() {
            try {
               lock=s.readLock();
               lock.attempt(TIMEOUT);
               log("1st read: value is " + value);
               assertEquals(10, value);
               lock.release();
               _sleep(SLEEP);

               lock.attempt(TIMEOUT);
               log("2nd read: value is " + value + "; we should see t2's committed change (20)");
               assertEquals(20, value);
            }
            catch(Throwable ex) {
               t1_ex=ex;
            }
            finally {
               lock.release();
            }
         }
      };


      Thread t2=new Thread("t2") {
         Sync lock=null;

         public void run() {
            try {
               _sleep(100);
               lock=s.writeLock();
               lock.attempt(TIMEOUT);
               log("changing value from " + value + " to 20");
               value=20;
               log("committing the TX");
               lock.release();
            }
            catch(Throwable ex) {
               t2_ex=ex;
            }
            finally {
               lock.release();
            }
         }
      };

      t1.start();
      t2.start();
      t1.join();
      t2.join();
      if(t1_ex != null)
         throw t1_ex;
      if(t2_ex != null)
         throw t2_ex;
   }



   void _sleep(long time) {
      Thread.yield();
      try {Thread.sleep(time);} catch(InterruptedException e) {}
   }


   void log(String s) {
      long now;
      if(start == 0)
         start=System.currentTimeMillis();
      now=System.currentTimeMillis();

      System.out.println("[" + Thread.currentThread().getName() + "] [" + (now - start) + "] " + s);
    }


   public static void main(String[] args) throws Exception {
      junit.textui.TestRunner.run(suite());
   }

   // Needed for JUnit.
   public static Test suite() {
      TestSuite suite=new TestSuite();
      suite.addTestSuite(LockUnitTestCase.class);
      return suite;
   }




}

