All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.fs.FsLockController Maven / Gradle / Ivy

/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.fs;

import de.schlichtherle.truezip.entry.Entry;
import de.schlichtherle.truezip.entry.Entry.Access;
import de.schlichtherle.truezip.entry.Entry.Type;
import de.schlichtherle.truezip.io.DecoratingInputStream;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.io.DecoratingSeekableByteChannel;
import de.schlichtherle.truezip.rof.DecoratingReadOnlyFile;
import de.schlichtherle.truezip.rof.ReadOnlyFile;
import de.schlichtherle.truezip.socket.DecoratingInputSocket;
import de.schlichtherle.truezip.socket.DecoratingOutputSocket;
import de.schlichtherle.truezip.socket.InputSocket;
import de.schlichtherle.truezip.socket.OutputSocket;
import de.schlichtherle.truezip.util.BitField;
import de.schlichtherle.truezip.util.JSE7;
import de.schlichtherle.truezip.util.Threads;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Provides read/write locking for multi-threaded access by its clients.
 *
 * @see    FsLockModel
 * @see    FsNeedsWriteLockException
 * @since  TrueZIP 7.5
 * @author Christian Schlichtherle
 */
@Immutable
final class FsLockController
extends FsLockModelDecoratingController> {

    private static final SocketFactory SOCKET_FACTORY = JSE7.AVAILABLE
            ? SocketFactory.NIO2
            : SocketFactory.OIO;

    private static final ThreadLocal accounts = (JSE7.AVAILABLE
            ? ThreadLocalAccountFactory.NEW
            : ThreadLocalAccountFactory.OLD
                ).newThreadLocalAccount();

    private final ReadLock readLock;
    private final WriteLock writeLock;

    /**
     * Constructs a new file system lock controller.
     *
     * @param controller the decorated file system controller.
     */
    FsLockController(FsController controller) {
        super(controller);
        this.readLock = getModel().readLock();
        this.writeLock = getModel().writeLock();
    }

    @Override
    protected ReadLock readLock() {
        return this.readLock;
    }

    @Override
    protected WriteLock writeLock() {
        return this.writeLock;
    }

    @Override
    public boolean isReadOnly() throws IOException {
        final class IsReadOnly implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.isReadOnly();
            }
        } // IsReadOnly
        return readOrWriteLocked(new IsReadOnly());
    }

    @Override
    public FsEntry getEntry(final FsEntryName name) throws IOException {
        final class GetEntry implements Operation {
            @Override
            public FsEntry call() throws IOException {
                return delegate.getEntry(name);
            }
        } // GetEntry
        return readOrWriteLocked(new GetEntry());
    }

    @Override
    public boolean isReadable(final FsEntryName name) throws IOException {
        final class IsReadable implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.isReadable(name);
            }
        } // IsReadable
        return readOrWriteLocked(new IsReadable());
    }

    @Override
    public boolean isWritable(final FsEntryName name) throws IOException {
        final class IsWritable implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.isWritable(name);
            }
        } // IsWritable
        return readOrWriteLocked(new IsWritable());
    }

    @Override
    public boolean isExecutable(final FsEntryName name) throws IOException {
        final class IsExecutable implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.isExecutable(name);
            }
        } // IsExecutable
        return readOrWriteLocked(new IsExecutable());
    }

    @Override
    public void setReadOnly(final FsEntryName name) throws IOException {
        final class SetReadOnly implements Operation {
            @Override
            public Void call() throws IOException {
                delegate.setReadOnly(name);
                return null;
            }
        } // SetReadOnly
        writeLocked(new SetReadOnly());
    }

    @Override
    public boolean setTime(
            final FsEntryName name,
            final Map times,
            final BitField options)
    throws IOException {
        final class SetTime implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.setTime(name, times, options);
            }
        } // SetTime
        return writeLocked(new SetTime());
    }

    @Override
    public boolean setTime(
            final FsEntryName name,
            final BitField types,
            final long value,
            final BitField options)
    throws IOException {
        final class SetTime implements Operation {
            @Override
            public Boolean call() throws IOException {
                return delegate.setTime(name, types, value, options);
            }
        } // SetTime
        return writeLocked(new SetTime());
    }

    @Override
    public InputSocket getInputSocket(   FsEntryName name,
                                            BitField options) {
        return SOCKET_FACTORY.newInputSocket(this, name, options);
    }

    @Override
    public OutputSocket getOutputSocket( FsEntryName name,
                                            BitField options,
                                            @CheckForNull Entry template) {
        return SOCKET_FACTORY.newOutputSocket(this, name, options, template);
    }

    @Override
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
    public void
    mknod(  final FsEntryName name,
            final Type type,
            final BitField options,
            final Entry template)
    throws IOException {
        final class Mknod implements Operation {
            @Override
            public Void call() throws IOException {
                delegate.mknod(name, type, options, template);
                return null;
            }
        } // Mknod
        writeLocked(new Mknod());
    }

    @Override
    public void unlink(
            final FsEntryName name,
            final BitField options)
    throws IOException {
        final class Unlink implements Operation {
            @Override
            public Void call() throws IOException {
                delegate.unlink(name, options);
                return null;
            }
        } // Unlink
        writeLocked(new Unlink());
    }

    @Override
    public void sync(final BitField options)
    throws FsSyncException {
        final class Sync implements Operation {
            @Override
            public Void call() throws IOException {
                delegate.sync(options);
                return null;
            }
        } // Sync
        try {
            writeLocked(new Sync());
        } catch (final FsSyncException ex) {
            throw ex;
        } catch (IOException ex) {
            throw new AssertionError(ex);
        }
    }

    private interface Operation {
        @Nullable T call() throws IOException;
    } // Operation

     T readOrWriteLocked(Operation operation)
    throws IOException {
        try {
            return readLocked(operation);
        } catch (FsNeedsWriteLockException ex) {
            return writeLocked(operation);
        }
    }

     T readLocked(Operation operation) throws IOException {
        return locked(operation, readLock());
    }

     T writeLocked(Operation operation) throws IOException {
        assert !getModel().isReadLockedByCurrentThread()
                : "Trying to upgrade a read lock to a write lock would only result in a dead lock - see Javadoc for ReentrantReadWriteLock!";
        return locked(operation, writeLock());
    }

    /**
     * Tries to call the given consistent operation while holding the given
     * lock.
     * 

* If this is the first execution of this method on the call stack of the * current thread, then the lock gets acquired using {@link Lock#lock()}. * Once the lock has been acquired the operation gets called. * If this fails for some reason and the thrown exception chain contains a * {@link FsNeedsLockRetryException}, then the lock gets temporarily * released and the current thread gets paused for a small random time * interval before this procedure starts over again. * Otherwise, the exception chain gets just passed on to the caller. *

* If this is not the first execution of this method on the call * stack of the current thread, then the lock gets acquired using * {@link Lock#tryLock()} instead. * If this fails, an {@code FsNeedsLockRetryException} gets created and * passed to the given exception handler for mapping before finally * throwing the resulting exception by executing * {@code throw handler.fail(new FsNeedsLockRetryException())}. * Once the lock has been acquired the operation gets called. * If this fails for some reason then the exception chain gets just passed * on to the caller. *

* This algorithm prevents dead locks effectively by temporarily unwinding * the stack and releasing all locks for a small random time interval. * Note that this requires some minimal cooperation by the operation: * Whenever it throws an exception, it MUST leave its resources in a * consistent state so that it can get retried again! * Mind that this is standard requirement for any {@link FsController}. * * @param The return type of the operation. * @param operation The atomic operation. * @param lock The lock to hold while calling the operation. * @return The result of the operation. * @throws IOException As thrown by the operation. * @throws FsNeedsLockRetryException See above. */ private T locked(final Operation operation, final Lock lock) throws IOException { final Account account = accounts.get(); if (0 < account.lockCount) { if (!lock.tryLock()) throw FsNeedsLockRetryException.get(); account.lockCount++; try { return operation.call(); } finally { account.lockCount--; lock.unlock(); } } else { try { while (true) { try { lock.lock(); account.lockCount++; try { return operation.call(); } finally { account.lockCount--; lock.unlock(); } } catch (FsNeedsLockRetryException ex) { account.pause(); } } } finally { accounts.remove(); } } } static int getLockCount() { return accounts.get().lockCount; } @Immutable private enum SocketFactory { NIO2() { @Override InputSocket newInputSocket( FsLockController controller, FsEntryName name, BitField options) { return controller.new Nio2Input(name, options); } @Override OutputSocket newOutputSocket( FsLockController controller, FsEntryName name, BitField options, @CheckForNull Entry template) { return controller.new Nio2Output(name, options, template); } }, OIO() { @Override InputSocket newInputSocket( FsLockController controller, FsEntryName name, BitField options) { return controller.new Input(name, options); } @Override OutputSocket newOutputSocket( FsLockController controller, FsEntryName name, BitField options, @CheckForNull Entry template) { return controller.new Output(name, options, template); } }; abstract InputSocket newInputSocket( FsLockController controller, FsEntryName name, BitField options); abstract OutputSocket newOutputSocket( FsLockController controller, FsEntryName name, BitField options, @CheckForNull Entry template); } // SocketFactory @Immutable private final class Nio2Input extends Input { Nio2Input( final FsEntryName name, final BitField options) { super(name, options); } @Override public SeekableByteChannel newSeekableByteChannel() throws IOException { final class NewSeekableByteChannel implements Operation { @Override public SeekableByteChannel call() throws IOException { return new LockSeekableByteChannel( getBoundSocket().newSeekableByteChannel()); } } // NewSeekableByteChannel return writeLocked(new NewSeekableByteChannel()); } } // Nio2Input @Immutable private class Input extends DecoratingInputSocket { Input( final FsEntryName name, final BitField options) { super(FsLockController.this.delegate .getInputSocket(name, options)); } @Override public Entry getLocalTarget() throws IOException { final class GetLocalTarget implements Operation { @Override public Entry call() throws IOException { return getBoundSocket().getLocalTarget(); } } // GetLocalTarget return writeLocked(new GetLocalTarget()); } @Override public ReadOnlyFile newReadOnlyFile() throws IOException { final class NewReadOnlyFile implements Operation { @Override public ReadOnlyFile call() throws IOException { return new LockReadOnlyFile( getBoundSocket().newReadOnlyFile()); } } // NewReadOnlyFile return writeLocked(new NewReadOnlyFile()); } @Override public InputStream newInputStream() throws IOException { final class NewInputStream implements Operation { @Override public InputStream call() throws IOException { return new LockInputStream( getBoundSocket().newInputStream()); } } // NewInputStream return writeLocked(new NewInputStream()); } } // Input @Immutable private final class Nio2Output extends Output { Nio2Output( final FsEntryName name, final BitField options, final @CheckForNull Entry template) { super(name, options, template); } @Override public SeekableByteChannel newSeekableByteChannel() throws IOException { final class NewSeekableByteChannel implements Operation { @Override public SeekableByteChannel call() throws IOException { return new LockSeekableByteChannel( getBoundSocket().newSeekableByteChannel()); } } // NewSeekableByteChannel return writeLocked(new NewSeekableByteChannel()); } } // Nio2Output @Immutable private class Output extends DecoratingOutputSocket { Output( final FsEntryName name, final BitField options, final @CheckForNull Entry template) { super(FsLockController.this.delegate .getOutputSocket(name, options, template)); } @Override public Entry getLocalTarget() throws IOException { final class GetLocalTarget implements Operation { @Override public Entry call() throws IOException { return getBoundSocket().getLocalTarget(); } } // GetLocalTarget return writeLocked(new GetLocalTarget()); } @Override public OutputStream newOutputStream() throws IOException { final class NewOutputStream implements Operation { @Override public OutputStream call() throws IOException { return new LockOutputStream( getBoundSocket().newOutputStream()); } } // NewOutputStream return writeLocked(new NewOutputStream()); } } // Output private final class LockReadOnlyFile extends DecoratingReadOnlyFile { @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") LockReadOnlyFile(@WillCloseWhenClosed ReadOnlyFile rof) { super(rof); } @Override public void close() throws IOException { FsLockController.this.close(delegate); } } // LockReadOnlyFile private final class LockSeekableByteChannel extends DecoratingSeekableByteChannel { @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") LockSeekableByteChannel(@WillCloseWhenClosed SeekableByteChannel sbc) { super(sbc); } @Override public void close() throws IOException { FsLockController.this.close(delegate); } } // LockSeekableByteChannel private final class LockInputStream extends DecoratingInputStream { @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") LockInputStream(@WillCloseWhenClosed InputStream in) { super(in); } @Override public void close() throws IOException { FsLockController.this.close(delegate); } } // LockInputStream private final class LockOutputStream extends DecoratingOutputStream { @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") LockOutputStream(@WillCloseWhenClosed OutputStream out) { super(out); } @Override public void close() throws IOException { FsLockController.this.close(delegate); } } // LockOutputStream void close(final Closeable closeable) throws IOException { final class Close implements Operation { @Override public Void call() throws IOException { closeable.close(); return null; } } // Close writeLocked(new Close()); } @NotThreadSafe private static final class Account { int lockCount; final Random rnd; Account(Random rnd) { this.rnd = rnd; } /** * Delays the current thread for a random time interval between one and * {@link #WAIT_TIMEOUT_MILLIS} milliseconds inclusively. * Interrupting the current thread has no effect on this method. */ void pause() { Threads.pause(1 + rnd.nextInt(WAIT_TIMEOUT_MILLIS)); } } // ThreadUtil @Immutable private enum ThreadLocalAccountFactory { NEW { @Override ThreadLocal newThreadLocalAccount() { return new ThreadLocal() { @Override public Account initialValue() { return new Account(ThreadLocalRandom.current()); } }; } }, OLD { @Override ThreadLocal newThreadLocalAccount() { return new ThreadLocal() { @Override public Account initialValue() { return new Account(new Random()); } }; } }; abstract ThreadLocal newThreadLocalAccount(); } // ThreadLocalAccountFactory }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy