org.commonjava.util.partyline.ClearCurrentThreadWithAnotherThreadReadingTest.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.util.partyline.ClearCurrentThreadWithAnotherThreadReadingTest.java

Source

/**
 * Copyright (C) 2015 Red Hat, Inc. (jdcasey@commonjava.org)
 *
 * 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 org.commonjava.util.partyline;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jboss.byteman.contrib.bmunit.BMRule;
import org.jboss.byteman.contrib.bmunit.BMRules;
import org.jboss.byteman.contrib.bmunit.BMUnitConfig;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.commonjava.util.partyline.fixture.ThreadDumper.timeoutRule;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
 * Created by jdcasey on 1/3/17.
 *
 * Test that checks resource management under the following conditions:
 * <ul>
 *     <li>Two threads open {@link InputStream}s to the same file concurrently and start reading</li>
 *     <li>Thread 1 finishes reading first, and calls {@link JoinableFileManager#cleanupCurrentThread()} before Thread 2 is done reading</li>
 * </ul>
 * <br/>
 * <b>EXPECTED RESULT:</b> Thread 2 should be able to complete its read operation and close the {@link JoinableFile}
 * when it completes. This process should be <b>unaffected</b> by Thread 1's
 * {@link JoinableFileManager#cleanupCurrentThread()} call.
 */
public class ClearCurrentThreadWithAnotherThreadReadingTest {
    @ClassRule
    public static TestRule timeout = timeoutRule(2, TimeUnit.SECONDS);

    @Rule
    public TemporaryFolder temp = new TemporaryFolder();

    private Logger logger = LoggerFactory.getLogger(getClass());

    private CountDownLatch completionLatch = new CountDownLatch(2);

    /**
     * Given:
     * <ul>
     *     <li>Two threads, T1 and T2</li>
     *     <li>One file containing some content, targeted for reading by both threads</li>
     * </ul>
     * <br/>
     * Sequence:
     * <br/>
     * <ol>
     *   <li>T1 opens input stream</li>
     *   <li>T1 reads from input stream</li>
     *   <li>One of the following in unspecified order:
     *      <ol type="A">
     *          <li>T1 waits for T2 to open input stream</li>
     *          <li>T2 opens input stream</li>
     *      </ol>
     *   </li>
     *   <li>T1 calls {@link JoinableFileManager#cleanupCurrentThread()}</li>
     *   <li>T2 reads from input stream</li>
     * </ol>
     * <br/>
     * Expected Result: Both threads successfully read correct content from the file.
     */
    @Test
    @BMUnitConfig(debug = true)
    public void run() throws Exception {
        final ExecutorService execs = Executors.newFixedThreadPool(2);

        CountDownLatch t1StartLatch = new CountDownLatch(1);
        CountDownLatch t2StartLatch = new CountDownLatch(1);
        CountDownLatch t1CleanupLatch = new CountDownLatch(1);

        final JoinableFileManager manager = new JoinableFileManager();

        final String content = "This is a test";
        final File file = temp.newFile();

        FileUtils.write(file, content);

        List<String> returning = new ArrayList<String>();

        execs.execute(() -> {
            Thread.currentThread().setName("T1");

            readFile(null, t1StartLatch, null, manager, file, returning);
            try {
                logger.info("Waiting for T2 to get an input stream");
                t2StartLatch.await();
            } catch (InterruptedException e) {
                logger.warn(Thread.currentThread().getName()
                        + " interrupted while waiting for second reader to open input stream.");
            }

            logger.info("Cleaning up T1 resources");
            manager.cleanupCurrentThread();

            logger.info("Signaling T1 cleanup is complete.");
            t1CleanupLatch.countDown();
        });

        execs.execute(() -> {
            Thread.currentThread().setName("T2");

            readFile(t1StartLatch, t2StartLatch, t1CleanupLatch, manager, file, returning);
        });

        completionLatch.await();

        assertThat("Both threads should return content!", returning.size(), equalTo(2));
        assertThat(returning.get(0), equalTo(content));
        assertThat(returning.get(1), equalTo(content));
    }

    private void readFile(CountDownLatch start, CountDownLatch preReadLatch, CountDownLatch cleanupLatch,
            JoinableFileManager manager, File file, List<String> returning) {
        logger.info("Starting file read...", Thread.currentThread().getName());

        try {
            // if we have a start latch, wait for it to clear before proceeding. This helps guarantee thread order.
            if (start != null) {
                logger.info("Waiting for pre-start latch to clear.");
                start.await();
            }

            logger.info("Opening input stream");
            try (InputStream s = manager.openInputStream(file)) {
                // if we have a pre-read latch, count it down to signal that we have an input stream.
                if (preReadLatch != null) {
                    logger.info("Clearing pre-read latch");
                    preReadLatch.countDown();
                }

                // if we have a cleanup latch, wait for it to ensure cleanupCurrentThread is called from the other thread before we try to read.
                if (cleanupLatch != null) {
                    logger.info("Waiting for other thread's cleanup latch to clear");
                    cleanupLatch.await();
                }

                logger.info("Reading file contents");

                // read the file and store it to the returning list...
                returning.add(IOUtils.toString(s));
            } catch (Exception e) {
                e.printStackTrace();
                fail("Failed to open inputStream: " + e.getMessage());
            }
        } catch (InterruptedException e) {
            logger.warn(Thread.currentThread().getName() + " interrupted during open-read operation.");
        } finally {
            completionLatch.countDown();
            logger.info("File read done");
        }
    }
}