package org.infinispan.lucene.readlocks;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.lucene.CacheTestSupport;
import org.infinispan.lucene.FileCacheKey;
import org.infinispan.lucene.FileMetadata;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;

/**
 * Stress test for {@link org.infinispan.lucene.readlocks.LocalLockMergingSegmentReadLocker}. See also ISPN-4497 for an
 * example of race conditions it protects against.
 *
 * @author Sanne Grinovero
 * @since 7.0
 */
@Test(groups = "profiling", testName = "lucene.readlocks.LocalLockStressTest")
public class LocalLockStressTest extends SingleCacheManagerTest {

   static final int NUM_THREADS = 10;
   static final int TEST_MINUTES_MAX = 10;

   @Override
   protected EmbeddedCacheManager createCacheManager() throws Exception {
      ConfigurationBuilder configurationBuilder = CacheTestSupport.createLocalCacheConfiguration();
      return TestCacheManagerFactory.createCacheManager(configurationBuilder);
   }

   @Test
   public void testMultiThreaded() {
      final Cache<Object, Object> metadata = cacheManager.getCache("metadata");
      final Cache<Object, Object> chunks = cacheManager.getCache("chunks");
      final Cache<Object, Integer> locks = cacheManager.getCache("locks");

      FileMetadata fileMetadata = new FileMetadata(10);
      fileMetadata.setSize(11); // Make it chunked otherwise no read lock will involved
      metadata.put(new FileCacheKey("indexName", "fileName", -1), fileMetadata);
      final LocalLockMergingSegmentReadLocker locker = new LocalLockMergingSegmentReadLocker(locks, chunks, metadata, "indexName", -1);
      final AtomicBoolean testFailed = new AtomicBoolean(false);
      final ExecutorService exec = Executors.newFixedThreadPool(NUM_THREADS);

      Runnable stressor = new Runnable() {
         @Override
         public void run() {
            try {
               int counter = 0;
               while (testFailed.get() == false) {

                  locker.acquireReadLock("fileName");
                  Thread.sleep(2);
                  locker.deleteOrReleaseReadLock("fileName");

                  // Take a break every now and a again to try and avoid the same LocalReadLock being used constantly
                  if (counter++ % 900 == 0) {
                     System.out.print(".");
                     Thread.sleep(7);
                  }
                  if (metadata.get(new FileCacheKey("indexName", "fileName", -1)) == null) {
                     //Shouldn't have been deleted!
                     testFailed.set(true);
                     System.out.print("X");
                  }
               }
            } catch (InterruptedException e) {
               Thread.currentThread().interrupt();
               return;
            }
         }
      };
      for (int i = 0; i < NUM_THREADS; i++) {
         exec.execute(stressor);
      }
      System.out.println("Stressor threads started...");
      exec.shutdown();
      try {
         exec.awaitTermination(TEST_MINUTES_MAX, TimeUnit.MINUTES);
      } catch (InterruptedException e) {
         exec.shutdownNow();
      }
      AssertJUnit.assertFalse(testFailed.get());
   }

}
