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

net.java.truevfs.kernel.impl.ResourceController Maven / Gradle / Ivy

/*
 * Copyright © 2005 - 2021 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.truevfs.kernel.impl;

import lombok.val;
import net.java.truecommons.cio.*;
import net.java.truecommons.io.DecoratingInputStream;
import net.java.truecommons.io.DecoratingOutputStream;
import net.java.truecommons.io.DecoratingSeekableChannel;
import net.java.truecommons.shed.BitField;
import net.java.truecommons.shed.ControlFlowException;
import net.java.truecommons.shed.ExceptionHandler;
import net.java.truevfs.kernel.spec.*;

import javax.annotation.concurrent.NotThreadSafe;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Optional;

import static net.java.truevfs.kernel.spec.FsSyncOption.FORCE_CLOSE_IO;
import static net.java.truevfs.kernel.spec.FsSyncOption.WAIT_CLOSE_IO;

/**
 * Accounts input and output resources returned by its decorated controller.
 *
 * @author Christian Schlichtherle
 * @see ResourceAccountant
 */
@NotThreadSafe
abstract class ResourceController implements DelegatingArchiveController {

    private static final int waitTimeoutMillis = LockingStrategy.acquireTimeoutMillis;

    private final ResourceAccountant accountant = new ResourceAccountant(writeLock());

    @Override
    public InputSocket input(BitField options, FsNodeName name) {
        return new DelegatingInputSocket() {

            final InputSocket socket = getController().input(options, name);

            @Override
            protected InputSocket socket() throws IOException {
                return socket;
            }

            @Override
            public InputStream stream(OutputSocket peer) throws IOException {
                return new ResourceInputStream(socket.stream(peer));
            }

            @Override
            public SeekableByteChannel channel(OutputSocket peer) throws IOException {
                return new ResourceSeekableChannel(socket.channel(peer));
            }
        };
    }

    @Override
    public OutputSocket output(BitField options, FsNodeName name, Optional template) {
        return new DelegatingOutputSocket() {

            final OutputSocket socket = getController().output(options, name, template);

            @Override
            protected OutputSocket socket() throws IOException {
                return socket;
            }

            @Override
            public OutputStream stream(InputSocket peer) throws IOException {
                return new ResourceOutputStream(socket.stream(peer));
            }

            @Override
            public SeekableByteChannel channel(InputSocket peer) throws IOException {
                return new ResourceSeekableChannel(socket.channel(peer));
            }
        };
    }

    @Override
    public void sync(final BitField options) throws FsSyncException {
        assert writeLockedByCurrentThread();
        assert !readLockedByCurrentThread();

        // HC SVNT DRACONES!
        val beforeWait = accountant.resources();
        if (0 == beforeWait.getTotal()) {
            getController().sync(options);
            return;
        }

        val builder = new FsSyncExceptionBuilder();
        try {
            if (0 != beforeWait.getLocal() && !options.get(FORCE_CLOSE_IO)) {
                throw new FsOpenResourceException(beforeWait.getLocal(), beforeWait.getTotal());
            }
            accountant.awaitClosingOfOtherThreadsResources(options.get(WAIT_CLOSE_IO) ? 0 : waitTimeoutMillis);
            val afterWait = accountant.resources();
            if (0 != afterWait.getTotal()) {
                throw new FsOpenResourceException(afterWait.getLocal(), afterWait.getTotal());
            }
        } catch (final FsOpenResourceException e) {
            if (!options.get(FORCE_CLOSE_IO)) {
                throw builder.fail(new FsSyncException(getMountPoint(), e));
            }
            builder.warn(new FsSyncWarningException(getMountPoint(), e));
        }
        closeResources(builder);
        if (beforeWait.isNeedsWaiting()) {
            // awaitClosingOfOtherThreadsResources(*) has temporarily released the write lock, so the state of the
            // virtual file system may have completely changed and thus we need to restart the sync operation unless an
            // exception occured.
            builder.check();
            throw NeedsSyncException.apply();
        }
        try {
            getController().sync(options);
        } catch (FsSyncException e) {
            throw builder.fail(e);
        }
        builder.check();
    }

    /**
     * Closes and disconnects all entry streams of the output and input archive.
     *
     * @param builder the exception handling strategy.
     */
    private void closeResources(final FsSyncExceptionBuilder builder) {
        accountant.closeAllResources(new ExceptionHandler() {

            @Override
            public RuntimeException fail(IOException input) {
                throw new AssertionError();
            }

            @Override
            public void warn(IOException input) throws RuntimeException {
                builder.warn(new FsSyncWarningException(getMountPoint(), input));
            }
        });
    }

    private final class ResourceInputStream extends DecoratingInputStream {

        final ResourceCloseable closeable = new ResourceCloseable(in);

        ResourceInputStream(InputStream in) {
            super(in);
        }

        @Override
        public void close() throws IOException {
            closeable.close();
        }
    }

    private final class ResourceOutputStream extends DecoratingOutputStream {

        final ResourceCloseable closeable = new ResourceCloseable(out);

        ResourceOutputStream(OutputStream out) {
            super(out);
        }

        @Override
        public void close() throws IOException {
            closeable.close();
        }
    }

    private final class ResourceSeekableChannel extends DecoratingSeekableChannel {

        final ResourceCloseable closeable = new ResourceCloseable(channel);

        ResourceSeekableChannel(SeekableByteChannel channel) {
            super(channel);
        }

        @Override
        public void close() throws IOException {
            closeable.close();
        }
    }

    private final class ResourceCloseable implements Closeable {

        final Closeable closeable;

        ResourceCloseable(final Closeable closeable) {
            this.closeable = closeable;
            accountant.startAccountingFor(this);
        }

        /**
         * Close()s this resource and finally stops accounting for it unless a {@link ControlFlowException} is thrown.
         *
         * @see Issue TRUEZIP-279
         */
        @Override
        public void close() throws IOException {
            boolean cfe = false;
            try {
                closeable.close();
            } catch (final ControlFlowException e) {
                cfe = true;
                throw e;
            } finally {
                if (!cfe) {
                    accountant.stopAccountingFor(this);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy