Package com.facebook.concurrency

Source Code of com.facebook.concurrency.TestExecutorServiceFront

/*
* Copyright (C) 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.concurrency;

import com.facebook.testing.AnnotatedRunnable;
import com.facebook.testing.LoopThread;
import com.facebook.testing.MockExecutor;
import com.facebook.testing.TestUtils;
import com.facebook.testing.ThreadHelper;
import org.apache.log4j.Logger;
import org.joda.time.DateTimeUtils;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class TestExecutorServiceFront {
  private static final Logger LOG =
    Logger.getLogger(TestExecutorServiceFront.class);

  private MockExecutor mockExecutor;
  private ExecutorServiceFront executorFront;
  private ExecutorServiceFront executorFront2;
  private AtomicLong count;
  private AtomicLong offsetTime;
  private Runnable countTask;
  private LatchTask latchTask;
  private Runnable slowTask;
  private static final int NUM_THREADS = 10;

  @BeforeMethod(alwaysRun = true)
  public void setUp() throws Exception {
    count = new AtomicLong(0);
    offsetTime = new AtomicLong(0);
    countTask = new Runnable() {
      @Override
      public void run() {
        count.incrementAndGet();
      }
    };
    slowTask = new Runnable() {
      @Override
      public void run() {
        try {
          DateTimeUtils.setCurrentMillisOffset(
            offsetTime.addAndGet(20000)
          );
        } catch (SecurityException e) {
          LOG.warn("security exception on incrementing the system time!", e);

          throw new RuntimeException(e);
        }
        count.incrementAndGet();
      }
    };
    latchTask = LatchTask.createPaused();
    mockExecutor = new MockExecutor();
    executorFront = new ExecutorServiceFront(
      mockExecutor, 10000, TimeUnit.MILLISECONDS
    );
    executorFront2 = new ExecutorServiceFront(
      new LinkedBlockingQueue<Runnable>(),
      mockExecutor,
      "fuu",
      2,
      10000,
      TimeUnit.MILLISECONDS
    );
  }

  /**
   * tests
   * 1. multiple submits result in only 1 task being submitted to underlying
   * executor
   * 2. running that one task drains our own queue
   */
  @Test(groups = "fast")
  public void testMaxDrainer() throws Exception {
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 0);
    executorFront.execute(countTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);
    executorFront.execute(countTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);
    mockExecutor.drain();
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 0);
    Assert.assertEquals(count.get(), 2);

  }

  /**
   * This test will submit several tasks at least one of which will
   * be the latch-task.  Another thread will try to drain the backing
   * executor.  This will hang that thread running a task
   */
  @Test(groups = "fast")
  public void testConcurrentDrainerAndSubmit() throws Exception {
    // thread will drain the executor backing us
    Thread drainingThread = new Thread(
      new Runnable() {
        @Override
        public void run() {
          mockExecutor.drain();
        }
      }
    );

    // submit a task that will hang, and a count task => 1 drainer task
    executorFront.execute(latchTask);
    executorFront.execute(countTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);

    // hang at the latch task
    drainingThread.start();
    Assert.assertEquals(count.get(), 0);

    // submit a new task that will no result in a new drainer task
    executorFront.execute(countTask);

    // let the drainer proceed, and wait for it to complete
    latchTask.proceed();
    drainingThread.join();

    // should have 2 count tasks
    Assert.assertEquals(count.get(), 2);

    // 0 drainer tasks, add a count task => 1 drainer task
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 0);
    executorFront.execute(countTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);
  }

  /**
   * This test:
   * 1. submit several slow tasks all of which will expire. Check these tasks
   * are executed one by one by several drainers.
   * 2. submit several fast tasks all of which will not expire. Check all these
   * tasks are executed in batch by one drainer.
   */
  @Test(groups = "fast")
  public void testExpiringSingleDrainer() throws Exception {
    int numTask = 3;

    // submit several slow tasks
    for (int i = 0; i < numTask; i++) {
      executorFront.execute(slowTask);
    }
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);

    for (int i = 0; i < numTask; i++) {
      // a drainer should run one task each time, then expire
      mockExecutor.removeHead().run();
      Assert.assertEquals(count.get(), i + 1);
    }

    //reset the counter
    count.set(0);

    // submit several fast task
    for (int i = 0; i < numTask; i++) {
      executorFront.execute(countTask);
    }
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);

    // a drainer should run all tasks
    mockExecutor.removeHead().run();
    Assert.assertEquals(count.get(), numTask);

    // must clear the timer offset
    DateTimeUtils.setCurrentMillisOffset(0);
  }

  /**
   * This test will submit several tasks two of which will
   * expire. We check the drainer(s) in the pending list
   * before and after the expiration.
   */
  @Test(groups = "fast")
  public void testExpiringDualDrainer() throws Exception {
    // submit three tasks
    executorFront2.execute(slowTask);
    executorFront2.execute(countTask);
    executorFront2.execute(slowTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 2);
    AnnotatedRunnable drainer2 = mockExecutor.getRunnableList().get(1);

    mockExecutor.removeHead().run();

    // Drainer1 expires after the 1st task completes,
    // and is rescheduled to the end of the pending list.
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 2);

    // Drainer2 should be on the head of the pending list   
    Assert.assertSame(drainer2, mockExecutor.getRunnableList().get(0));

    mockExecutor.removeHead().run();

    // Drainer2 expires after the 2nd and 3rd task complete.    
    AnnotatedRunnable drainer1 = mockExecutor.getRunnableList().get(0);

    // Drainer1 should be on the head of the pending list
    Assert.assertNotSame(drainer1, drainer2);

    // should have 2 slow tasks and 1 count tasks
    Assert.assertEquals(count.get(), 3);

    // must clear the timer offset
    DateTimeUtils.setCurrentMillisOffset(0);
  }

  @DataProvider(name = "getTestExecutors")
  public Object[][] getTestExecutors() {
    return new Object[][]{
      {Executors.newFixedThreadPool(NUM_THREADS)},
      {new ExecutorServiceFront(
        new LinkedBlockingQueue<Runnable>(),
        Executors.newFixedThreadPool(NUM_THREADS),
        "fuu",
        NUM_THREADS, 1, TimeUnit.MILLISECONDS
      )}
    };
  }

  /**
   * Tests that tasks that die with RuntimeException don't cause the
   * Executor to lose threads.
   *
   * @param executor The test executor
   */
  @Test(groups = "fast", dataProvider = "getTestExecutors")
  public void testDyingThreads(Executor executor) throws Exception {
    final int numTasks = NUM_THREADS * 2;
    final CountDownLatch latch = new CountDownLatch(numTasks);
    // kill all the threads
    for (int i = 0; i < numTasks; i++) {
      executor.execute(
        new Runnable() {
          @Override
          public void run() {
            latch.countDown();
            throw new RuntimeException("Expected Failure");
          }
        }
      );
    }
    Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
    // now add more tasks to run
    final CountDownLatch latch2 = new CountDownLatch(NUM_THREADS);
    // Run tasks to see if they can still run
    for (int i = 0; i < NUM_THREADS; i++) {
      executor.execute(
        new Runnable() {
          @Override
          public void run() {
            latch2.countDown();
          }
        }
      );
    }
    Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
  }

  @Test(groups = "fast")
  public void testTimeExpirationWithEmptyQueue() throws Exception {
    try {
      LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
      final ExecutorServiceFront executorServiceFront = new ExecutorServiceFront(
        workQueue, mockExecutor, "fuu", 1, 1, TimeUnit.SECONDS
      );
      DateTimeUtils.setCurrentMillisFixed(0);
      executorServiceFront.execute(latchTask);
      Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);

      Thread t = TestUtils.runInThread(
        new Runnable() {
          @Override
          public void run() {
            AnnotatedRunnable drainer = mockExecutor.removeHead();

            drainer.run();
          }
        },
        "drainer"
      );
      // once the queue is empty, we know the drainer has taken the task out and is blocked on the
      // latch
      while (!workQueue.isEmpty()) {
        Thread.sleep(50);
      }
      // move time forward, which will cause the Drainer to terminate based on time-slice (not
      // empty queue)
      DateTimeUtils.setCurrentMillisFixed(1001);
      latchTask.proceed();
      // wait for drainer to terminate
      t.join();
      // our time has expired, and there are no tasks in the queue, no tasks should be in the queue 
      Assert.assertEquals(mockExecutor.getNumPendingTasks(), 0);
    } finally {
      DateTimeUtils.setCurrentMillisSystem();
    }
  }

  @Test(groups = "fast")
  public void testRenameThread() throws Exception {
    LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
    final ExecutorServiceFront executorServiceFront = new ExecutorServiceFront(
      workQueue, mockExecutor, "custom-name", 1
    );
    executorServiceFront.execute(latchTask);
    Assert.assertEquals(mockExecutor.getNumPendingTasks(), 1);

    Thread t = createDrainerThread();
    // once the queue is empty, we know the drainer has taken the task out and is blocked on the
    // latch
    while (!workQueue.isEmpty()) {
      Thread.sleep(50);
    }
   
    Assert.assertEquals(t.getName(), "custom-name-000");
    latchTask.proceed();
    t.join();
    Assert.assertEquals(t.getName(), "original");
  }

  @Test(groups = "fast")
  public void testCreateManyThreads() throws Exception {
    LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
    ExecutorServiceFront executorServiceFront = new ExecutorServiceFront(
      workQueue, mockExecutor, "custom-name", 2
    );
    LatchTask task1 = LatchTask.createPaused();
    LatchTask task2 = LatchTask.createPaused();
    // put 2 tasks  in the queue, both will hang, causing 2 drainers to be created
    executorServiceFront.execute(task1);
    executorServiceFront.execute(task2);

    ThreadHelper threadHelper = new ThreadHelper();
    // ie, we'll want two physical threads
    LoopThread drainerThread1 = createDrainerThread(threadHelper);
    LoopThread drainerThread2 = createDrainerThread(threadHelper);
    // this waits for both tasks to get into a Drainer. Two tasks that pause immediately and
    // two drainers => we get 1 task per drainer
    while (!workQueue.isEmpty()) {
      Thread.sleep(50);
    }

    // check that the names change according to our ESF
    Assert.assertEquals(drainerThread1.getName(), "custom-name-000");
    task1.proceed();
    task1.await();
    Assert.assertEquals(drainerThread2.getName(), "custom-name-001");
    task2.proceed();
    task2.await();
    drainerThread1.join();
    drainerThread2.join();
    // and that the 'base name' of the borrowed  threads is restored
    Assert.assertEquals(drainerThread1.getName(), "drainer");
    Assert.assertEquals(drainerThread2.getName(), "drainer");
  }

  private LoopThread createDrainerThread(ThreadHelper threadHelper) {
    return threadHelper.repeatInThread(
      new Runnable() {
        @Override
        public void run() {
          AnnotatedRunnable drainer = mockExecutor.removeHead();

          if (drainer != null) {
            drainer.run();
          } else {
            try {
              Thread.sleep(10);
            } catch (InterruptedException e) {
              throw new RuntimeException(e);
            }
          }
        }
      },
      "drainer"
    );
  }

  private Thread createDrainerThread() {
    return TestUtils.runInThread(
      new Runnable() {
        @Override
        public void run() {
          AnnotatedRunnable drainer = mockExecutor.removeHead();

          drainer.run();
        }
      },
      "original"
    );
  }
}
TOP

Related Classes of com.facebook.concurrency.TestExecutorServiceFront

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.