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

Java tutorial

Introduction

Here is the source code for org.commonjava.util.partyline.JoinableFileManagerTest.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.commonjava.cdi.util.weft.ThreadContext;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 java.util.concurrent.atomic.AtomicInteger;

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

public class JoinableFileManagerTest extends AbstractJointedIOTest {

    private static final long SHORT_TIMEOUT = 10;

    @Rule
    public TestRule timeout = timeoutRule(30, TimeUnit.SECONDS);

    private final JoinableFileManager mgr = new JoinableFileManager();

    @Test
    public void lockAndUnlockTwiceInSequenceFromOneThread() throws IOException, InterruptedException {
        File dir = temp.newFolder();
        mgr.lock(dir, Long.MAX_VALUE, LockLevel.write);
        assertThat(mgr.isWriteLocked(dir), equalTo(true));
        assertThat(mgr.getContextLockCount(dir), equalTo(1));
        mgr.lock(dir, Long.MAX_VALUE, LockLevel.write);
        assertThat(mgr.isWriteLocked(dir), equalTo(true));
        assertThat(mgr.getContextLockCount(dir), equalTo(2));
        mgr.unlock(dir);
        assertThat(mgr.isWriteLocked(dir), equalTo(true));
        assertThat(mgr.getContextLockCount(dir), equalTo(1));
        mgr.unlock(dir);
        assertThat(mgr.isWriteLocked(dir), equalTo(false));
        assertThat(mgr.getContextLockCount(dir), equalTo(0));
    }

    @Test
    public void lockDirThenLockFile() throws IOException, InterruptedException {
        File dir = temp.newFolder();
        File child = dir.toPath().resolve("child.txt").toFile();

        boolean dirLocked = mgr.lock(dir, 2000, LockLevel.write);
        assertThat(dirLocked, equalTo(true));
        assertThat(mgr.isWriteLocked(dir), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(dir), equalTo(true));
        assertThat(mgr.getContextLockCount(dir), equalTo(1));

        try (OutputStream out = mgr.openOutputStream(child, 2000)) {
            assertThat(mgr.isWriteLocked(child), equalTo(true));
            assertThat(mgr.getContextLockCount(dir), equalTo(2));
            IOUtils.write("This is a test", out);
        }
        assertThat(mgr.getContextLockCount(dir), equalTo(1));

        boolean unlocked = mgr.unlock(dir);
        assertThat(unlocked, equalTo(true));
        assertThat(mgr.isWriteLocked(dir), equalTo(false));
        assertThat(mgr.getContextLockCount(dir), equalTo(0));

        assertThat(mgr.isWriteLocked(child), equalTo(false));
    }

    @Test
    public void lockDirThenLockTwoFilesWithShortTimeout() throws IOException, InterruptedException {
        lockDirThenLockNFilesWithTimeout(2, 2000);
    }

    @Test
    public void lockDirThenLockFourFilesWithShortTimeout() throws IOException, InterruptedException {
        lockDirThenLockNFilesWithTimeout(4, 2000);
    }

    @Test
    public void lockDirThenLockTwoFilesWithLongTimeout() throws IOException, InterruptedException {
        lockDirThenLockNFilesWithTimeout(2, Long.MAX_VALUE);
    }

    @Test
    public void lockDirThenLockFourFilesWithLongTimeout() throws IOException, InterruptedException {
        lockDirThenLockNFilesWithTimeout(4, Long.MAX_VALUE);
    }

    private void lockDirThenLockNFilesWithTimeout(final int filesNum, final long timeout)
            throws IOException, InterruptedException {
        File dir = temp.newFolder();

        final List<File> files = new ArrayList<>(filesNum);

        for (int i = 1; i <= filesNum; i++) {
            final String file = "child" + i + ".txt";
            files.add(dir.toPath().resolve(file).toFile());
        }

        final boolean dirLocked = mgr.lock(dir, timeout, LockLevel.write);
        assertThat(dirLocked, equalTo(true));
        assertThat(mgr.isWriteLocked(dir), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(dir), equalTo(true));
        assertThat(mgr.getContextLockCount(dir), equalTo(1));

        final List<OutputStream> fileOuts = new ArrayList<>(filesNum);

        int count = 1;
        for (File f : files) {
            fileOuts.add(mgr.openOutputStream(f, timeout));
            assertThat(mgr.getContextLockCount(dir), equalTo(++count));
        }

        for (File f : files) {
            assertThat(mgr.isWriteLocked(f), equalTo(true));
        }

        for (int i = filesNum - 1; i > 0; i--) {
            final OutputStream out = fileOuts.get(i);
            IOUtils.write("This is a test", out);
            out.close();
            assertThat(mgr.getContextLockCount(dir), equalTo(i + 1));
        }

        assertThat(mgr.isWriteLocked(dir), equalTo(true));

        final OutputStream out1 = fileOuts.get(0);
        IOUtils.write("This is a test", out1);
        out1.close();
        assertThat(mgr.getContextLockCount(dir), equalTo(1));

        boolean unlocked = mgr.unlock(dir);
        assertThat(unlocked, equalTo(true));
        assertThat(mgr.isWriteLocked(dir), equalTo(false));
        assertThat(mgr.getContextLockCount(dir), equalTo(0));

        for (int i = filesNum - 1; i >= 0; i--) {
            final File f = files.get(i);
            assertThat(mgr.isWriteLocked(f), equalTo(false));
        }

    }

    @Test
    public void lockTwoNestedDirsThenLockFile() throws Exception {
        File parent = temp.newFolder();
        File child = mkChildDir("child", parent);
        final String file = "childFile.txt";
        File childFile = child.toPath().resolve(file).toFile();

        // First lock parent dir
        final boolean parentLocked = mgr.lock(parent, Long.MAX_VALUE, LockLevel.write);
        assertThat(parentLocked, equalTo(true));
        assertThat(mgr.isWriteLocked(parent), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(parent), equalTo(true));
        assertThat(mgr.getContextLockCount(parent), equalTo(1));

        // Then lock child dir
        final boolean childLocked = mgr.lock(child, Long.MAX_VALUE, LockLevel.write);
        assertThat(childLocked, equalTo(true));
        assertThat(mgr.isWriteLocked(child), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(child), equalTo(true));
        assertThat(mgr.getContextLockCount(child), equalTo(1));
        assertThat(mgr.getContextLockCount(parent), equalTo(2));

        // Then lock file in child dir ans IO
        try (OutputStream childFileOut = mgr.openOutputStream(childFile)) {
            IOUtils.write("This is a test", childFileOut);
            assertThat(mgr.isWriteLocked(childFile), equalTo(true));
            assertThat(mgr.isLockedByCurrentThread(childFile), equalTo(true));
            assertThat(mgr.getContextLockCount(childFile), equalTo(1));
            assertThat(mgr.getContextLockCount(child), equalTo(2));
            //FIXME: should this count be 2 or 3? Seems it should be 3, but 3 can not pass the test now.
            assertThat(mgr.getContextLockCount(parent), equalTo(3));
        }

        // IO for file closed, should start unlock LIFO
        // NOTE: The child dir (parent of childFile) is still write-locked from another operation.
        assertThat(mgr.isWriteLocked(childFile), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(childFile), equalTo(false));
        assertThat(mgr.getContextLockCount(childFile), equalTo(0));
        assertThat(mgr.getContextLockCount(child), equalTo(1));
        //FIXME: This is also impacted by the upper fixme
        assertThat(mgr.getContextLockCount(parent), equalTo(2));

        // unlock child dir and calculate lock count
        mgr.unlock(child);
        // Child directory is still write-locked because its parent directory was previous locked by another operation
        assertThat(mgr.isWriteLocked(child), equalTo(true));
        assertThat(mgr.isLockedByCurrentThread(child), equalTo(false));
        assertThat(mgr.getContextLockCount(child), equalTo(0));
        //FIXME: This is also impacted by the upper fixme
        assertThat(mgr.getContextLockCount(parent), equalTo(1));

        // unlock parent dir and calculate lock count
        mgr.unlock(parent);
        //FIXME: Not sure if it is reasonable that the parent dir is still locked after the sequence unlock
        assertThat(mgr.isWriteLocked(parent), equalTo(false));
        assertThat(mgr.isLockedByCurrentThread(parent), equalTo(false));
        assertThat(mgr.getContextLockCount(parent), equalTo(0));

        // Releasing the lock on the parent dir should release the last remaining lock on these children too.
        assertThat(mgr.isWriteLocked(child), equalTo(false));
        assertThat(mgr.isWriteLocked(childFile), equalTo(false));

    }

    private File mkChildDir(String childName, File parent) {
        File child = parent.toPath().resolve(childName).toFile();
        return child.mkdir() ? child : null;
    }

    @Test
    public void twoFileReaders_CleanupFileEntryOnLastClose() throws Exception {
        String src = "This is a test";

        File f = temp.newFile();
        FileUtils.write(f, src);

        int count = 2;
        CountDownLatch start = new CountDownLatch(count);
        CountDownLatch end = new CountDownLatch(count);

        Logger logger = LoggerFactory.getLogger(getClass());
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < count; i++) {
            logger.info("Starting: {}", i);
            executor.execute(() -> {
                logger.info("Signaling thread: {} has started", Thread.currentThread().getName());
                start.countDown();
                try {
                    logger.info("Waiting for other thread(s) to start...");
                    start.await(3, TimeUnit.SECONDS);

                    assertThat("Threads did not start correctly!", start.getCount(), equalTo(0L));

                    logger.info("Opening: {}", f);

                    try (InputStream in = mgr.openInputStream(f)) {
                        assertThat(IOUtils.toString(in), equalTo(src));
                    } catch (IOException e) {
                        e.printStackTrace();
                        fail("Cannot open: " + f);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    fail("Interrupted");
                } finally {
                    logger.info("Signaling thread: {} has ended", Thread.currentThread().getName());
                    end.countDown();
                }
            });
        }

        logger.info("Waiting for end of threads");
        end.await(5, TimeUnit.SECONDS);

        assertThat("Threads did not end correctly!", end.getCount(), equalTo(0L));

        AtomicInteger counter = new AtomicInteger(0);
        mgr.getFileTree().forAll(entry -> true, entry -> counter.incrementAndGet());

        assertThat("FileEntry instance was not removed after closing!", counter.get(), equalTo(0));
    }

    @Test
    public void concurrentWriteAndRead_CleanupFileEntryOnLastClose() throws Exception {
        String src = "This is a test";

        File f = temp.newFile();
        FileUtils.write(f, src);

        int count = 2;
        CountDownLatch writing = new CountDownLatch(count);
        CountDownLatch reading = new CountDownLatch(count);
        CountDownLatch end = new CountDownLatch(count);

        Logger logger = LoggerFactory.getLogger(getClass());
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(() -> {
            logger.info("Starting write: {}", f);
            try (OutputStream out = mgr.openOutputStream(f)) {
                logger.info("Signaling write starting: {}", f);
                writing.countDown();

                IOUtils.write(src, out);

                logger.info("Waiting for read to start...");
                reading.await(1, TimeUnit.SECONDS);
            } catch (IOException e) {
                e.printStackTrace();
                fail("Failed to write: " + f);
            } catch (InterruptedException e) {
                e.printStackTrace();
                fail("Write Interrupted!");
            } finally {
                end.countDown();
            }
        });

        executor.execute(() -> {
            logger.info("Signaling thread: {} has started", Thread.currentThread().getName());
            writing.countDown();
            try {
                logger.info("Waiting for other thread(s) to written...");
                writing.await(1, TimeUnit.SECONDS);

                assertThat("Threads did not written correctly!", writing.getCount(), equalTo(0L));

                logger.info("Opening: {}", f);

                try (InputStream in = mgr.openInputStream(f)) {
                    logger.info("Signaling that reading has begun: {}", f);
                    reading.countDown();
                    assertThat(IOUtils.toString(in), equalTo(src));
                } catch (IOException e) {
                    e.printStackTrace();
                    fail("Cannot open: " + f);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                fail("Interrupted");
            } finally {
                logger.info("Signaling thread: {} has ended", Thread.currentThread().getName());
                end.countDown();
            }
        });

        logger.info("Waiting for end of threads");
        end.await(5, TimeUnit.SECONDS);

        assertThat("Threads did not end correctly!", end.getCount(), equalTo(0L));

        AtomicInteger counter = new AtomicInteger(0);
        mgr.getFileTree().forAll(entry -> true, entry -> counter.incrementAndGet());

        assertThat("FileEntry instance was not removed after closing!", counter.get(), equalTo(0));
    }

    @Test
    public void lockWriteDoesntPreventOpenInputStream() throws Exception {
        String src = "This is a test";

        File f = temp.newFile();
        FileUtils.write(f, src);

        assertThat("Write lock failed.", mgr.lock(f, -1, LockLevel.write, "test"), equalTo(true));

        InputStream stream = mgr.openInputStream(f);
        assertThat("InputStream cannot be null", stream, notNullValue());

        String result = IOUtils.toString(stream);
        assertThat(result, equalTo(src));
    }

    @Test
    public void waitForLockThenOpenOutputStream() throws Exception {
        final File f = temp.newFile();
        assertThat(mgr.waitForWriteUnlock(f), equalTo(true));

        try (OutputStream stream = mgr.openOutputStream(f)) {
            //nop
        }

        assertThat(mgr.isWriteLocked(f), equalTo(false));
    }

    @Test
    public void openOutputStream_VerifyWriteLocked_NotReadLocked() throws Exception {
        final File f = temp.newFile();
        final OutputStream stream = mgr.openOutputStream(f);

        assertThat(mgr.isWriteLocked(f), equalTo(true));
        assertThat(mgr.isReadLocked(f), equalTo(false));

        stream.close();

        assertThat(mgr.isWriteLocked(f), equalTo(false));
        assertThat(mgr.isReadLocked(f), equalTo(false));
    }

    @Test(expected = IOException.class)
    public void openOutputStream_TimeBoxedSecondCallThrowsException() throws Exception {
        ThreadContext ctx = ThreadContext.getContext(true);
        final String key = "real owner";

        final File f = temp.newFile();

        Thread.currentThread().setName(key);
        final OutputStream stream = mgr.openOutputStream(f);

        assertThat(((String) ctx.get(PARTYLINE_LOCK_OWNER)).contains(key), equalTo(true));

        ctx.put(PARTYLINE_LOCK_OWNER, "output 2");
        OutputStream s2 = mgr.openOutputStream(f, SHORT_TIMEOUT);

        assertThat(s2, nullValue());

        ctx.put(PARTYLINE_LOCK_OWNER, key);
        stream.close();

        ctx.put(PARTYLINE_LOCK_OWNER, "output 3");
        s2 = mgr.openOutputStream(f, SHORT_TIMEOUT);

        assertThat(s2, notNullValue());
    }

    @Test
    public void openInputStream_VerifyWriteLocked_ReadLocked() throws Exception {
        final File f = temp.newFile();
        final InputStream stream = mgr.openInputStream(f);

        assertThat(mgr.isWriteLocked(f), equalTo(true));

        // after change to always use JoinableFile (even for read-first), I don't think partyline will ever read lock.
        assertThat(mgr.isReadLocked(f), equalTo(false));

        stream.close();

        assertThat(mgr.isWriteLocked(f), equalTo(false));
        assertThat(mgr.isReadLocked(f), equalTo(false));
    }

    @Test
    @Ignore("With change to JoinableFile for all reads, partyline should never read-lock")
    public void openInputStream_TimeBoxedSecondCallReturnsNull() throws Exception {
        final File f = temp.newFile();
        final InputStream stream = mgr.openInputStream(f);

        InputStream s2 = mgr.openInputStream(f, SHORT_TIMEOUT);

        assertThat(s2, nullValue());

        stream.close();

        s2 = mgr.openInputStream(f, SHORT_TIMEOUT);

        assertThat(s2, notNullValue());
    }

    @Test
    @Ignore
    public void openInputStream_clearThreadContext_openOutputStream() throws Exception {
        ThreadContext.getContext(true);

        final File f = temp.newFile("test.txt");
        FileUtils.write(f, "This is first pass");
        mgr.openInputStream(f);
        //        mgr.cleanupCurrentThread();
        ThreadContext.clearContext();
        ThreadContext.getContext(true);
        OutputStream outputStream = mgr.openOutputStream(f);

        outputStream.close();
        ThreadContext.clearContext();
    }

}