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

org.jboss.remoting3.spi.AbstractHandleableCloseable Maven / Gradle / Ivy

There is a newer version: 3.3.12.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.remoting3.spi;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import org.jboss.remoting3.CloseHandler;
import org.jboss.remoting3.HandleableCloseable;
import org.jboss.remoting3.NotOpenException;
import org.jboss.remoting3.RemotingException;
import org.xnio.IoUtils;
import org.jboss.logging.Logger;

/**
 * A basic implementation of a closeable resource.  Use as a convenient base class for your closeable resources.
 * Ensures that the {@code close()} method is idempotent; implements the registry of close handlers.
 *
 * @param  the type of the closeable resource
 */
public abstract class AbstractHandleableCloseable> implements HandleableCloseable {

    private static final Logger log = Logger.getLogger("org.jboss.remoting.resource");
    private static final boolean LEAK_DEBUGGING;

    private final Executor executor;
    private final StackTraceElement[] backtrace;
    private final boolean autoClose;

    private final Object closeLock = new Object();
    private State state = State.OPEN;
    private IOException failure = null;
    private Map> closeHandlers = null;

    enum State {
        OPEN,
        CLOSING,
        CLOSED,
    }

    static {
        boolean b;
        try {
            b = Boolean.parseBoolean(AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getProperty("jboss.remoting.leakdebugging", "false");
                }
            }));
        } catch (SecurityException se) {
            b = false;
        }
        LEAK_DEBUGGING = b;
    }

    /**
     * Basic constructor.
     *
     * @param executor the executor used to execute the close notification handlers
     */
    protected AbstractHandleableCloseable(final Executor executor) {
        this(executor, true);
    }

    /**
     * Basic constructor.
     *
     * @param executor the executor used to execute the close notification handlers
     * @param autoClose {@code true} if this instance should automatically close on finalize
     */
    protected AbstractHandleableCloseable(final Executor executor, final boolean autoClose) {
        if (executor == null) {
            throw new NullPointerException("executor is null");
        }
        this.executor = executor;
        backtrace = LEAK_DEBUGGING ? Thread.currentThread().getStackTrace() : null;
        this.autoClose = autoClose;
    }

    /**
     * Read the status of this resource.  This is just a snapshot in time; there is no guarantee that the resource
     * will remain open for any amount of time, even if this method returns {@code true}.
     *
     * @return {@code true} if the resource is still open
     */
    protected boolean isOpen() {
        synchronized (closeLock) {
            return state == State.OPEN;
        }
    }

    /**
     * Called exactly once when the {@code close()} method is invoked; the actual close operation should take place here.
     * This method must call {@link #closeComplete()}, directly or indirectly, for the close operation to finish
     * (it may happen in another thread but it must happen).
     *
     * This method should not expect the {@link #closeComplete()} call to be made from another thread from the same thread pool
     * that may cause {@link #close()}. As close will block, this can result in situations where all threads in the pool are
     * blocked on {@link #close()} method calls, which means the {@link #closeComplete()} will never be run.
     *
     * @throws RemotingException if the close failed
     */
    protected void closeAction() throws IOException {
        closeComplete();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public void close() throws IOException {
        log.tracef("Closing %s synchronously", this);
        boolean first = false;
        synchronized (closeLock) {
            switch (state) {
                case OPEN: {
                    first = true;
                    state = State.CLOSING;
                    break;
                }
                case CLOSING: {
                    break;
                }
                case CLOSED: return;
                default: throw new IllegalStateException();
            }
        }
        if (first) try {
            closeAction();
        } catch (IOException e) {
            log.tracef(e, "Close of %s failed", this);
            final Map> closeHandlers;
            synchronized (closeLock) {
                state = State.CLOSED;
                closeHandlers = this.closeHandlers;
                this.closeHandlers = null;
                closeLock.notifyAll();
            }
            if (closeHandlers != null) {
                for (final CloseHandler handler : closeHandlers.values()) {
                    SpiUtils.safeHandleClose(handler, (T) AbstractHandleableCloseable.this, null);
                }
            }
            throw e;
        } catch (Throwable t) {
            log.errorf(t, "Close action for %s failed to execute (resource may be left in an indeterminate state)", this);
            final Map> closeHandlers;
            synchronized (closeLock) {
                state = State.CLOSED;
                closeHandlers = this.closeHandlers;
                this.closeHandlers = null;
                closeLock.notifyAll();
            }
            if (closeHandlers != null) {
                for (final CloseHandler handler : closeHandlers.values()) {
                    SpiUtils.safeHandleClose(handler, (T) AbstractHandleableCloseable.this, null);
                }
            }
            throw new IllegalStateException(t);
        }

        final IOException failure;
        synchronized (closeLock) {
            while (state != State.CLOSED) try {
                closeLock.wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new InterruptedIOException("Interrupted while waiting for close to complete");
            }
            failure = this.failure;
            this.failure = null;
        }
        if (failure != null) {
            final IOException clone = clone(failure);
            if (failure != clone) {
                SpiUtils.glueStackTraces(failure, Thread.currentThread().getStackTrace(), 1, "asynchronous close");
            }
            throw clone;
        }
    }

    private static  T clone(T original) {
        final Throwable cause = original.getCause();
        @SuppressWarnings("unchecked")
        final Class originalClass = (Class) original.getClass();
        // try a few constructors
        Constructor constructor;
        try {
            constructor = originalClass.getConstructor(String.class, Throwable.class);
            final T clone = constructor.newInstance(original.getMessage(), cause);
            clone.setStackTrace(original.getStackTrace());
            return clone;
        } catch (NoSuchMethodException e) {
            // nope
        } catch (InvocationTargetException e) {
            // nope
        } catch (InstantiationException e) {
            // nope
        } catch (IllegalAccessException e) {
            // nope
        }
        try {
            constructor = originalClass.getConstructor(String.class);
            final T clone = constructor.newInstance(original.getMessage());
            clone.initCause(cause);
            clone.setStackTrace(original.getStackTrace());
            return clone;
        } catch (NoSuchMethodException e) {
            // nope
        } catch (InvocationTargetException e) {
            // nope
        } catch (InstantiationException e) {
            // nope
        } catch (IllegalAccessException e) {
            // nope
        }
        try {
            constructor = originalClass.getConstructor();
            final T clone = constructor.newInstance();
            clone.initCause(cause);
            clone.setStackTrace(original.getStackTrace());
            return clone;
        } catch (NoSuchMethodException e) {
            // nope
        } catch (InvocationTargetException e) {
            // nope
        } catch (InstantiationException e) {
            // nope
        } catch (IllegalAccessException e) {
            // nope
        }
        // we tried!
        return original;
    }

    /**
     * Call when close is complete.
     */
    protected void closeComplete() {
        final Map> closeHandlers;
        synchronized (closeLock) {
            switch (state) {
                case OPEN: {
                    log.tracef("Closing %s asynchronously", this);
                    // fall thru
                }
                case CLOSING: {
                    log.tracef("Completed close of %s", this);
                    state = State.CLOSED;
                    closeHandlers = this.closeHandlers;
                    this.closeHandlers = null;
                    break;
                }
                case CLOSED: {
                    // idempotent
                    return;
                }
                default:
                    throw new IllegalStateException();
            }
            closeLock.notifyAll();
        }
        if (closeHandlers != null) {
            for (final CloseHandler handler : closeHandlers.values()) {
                runCloseTask(new CloseHandlerTask(handler, null));
            }
        }
    }

    /**
     * Call if an async close has failed.
     *
     * @param cause the failure cause
     */
    protected void closeFailed(IOException cause) {
        final Map> closeHandlers;
        synchronized (closeLock) {
            switch (state) {
                case CLOSING: {
                    log.tracef(cause, "Completed close of %s with failure", this);
                    state = State.CLOSED;
                    failure = cause;
                    closeHandlers = this.closeHandlers;
                    this.closeHandlers = null;
                    break;
                }
                case CLOSED: {
                    // idempotent
                    return;
                }
                default:
                    throw new IllegalStateException();
            }
            closeLock.notifyAll();
        }
        if (closeHandlers != null) {
            for (final CloseHandler handler : closeHandlers.values()) {
                runCloseTask(new CloseHandlerTask(handler, cause));
            }
        }
    }

    /** {@inheritDoc} */
    public void awaitClosed() throws InterruptedException {
        synchronized (closeLock) {
            while (state != State.CLOSED) {
                closeLock.wait();
            }
        }
    }

    /** {@inheritDoc} */
    public void awaitClosedUninterruptibly() {
        boolean intr = false;
        try {
            synchronized (closeLock) {
                while (state != State.CLOSED) {
                    try {
                        closeLock.wait();
                    } catch (InterruptedException e) {
                        intr = true;
                    }
                }
            }
        } finally {
            if (intr) Thread.currentThread().interrupt();
        }
    }

    /** {@inheritDoc} */
    public void closeAsync() {
        log.tracef("Closing %s asynchronously", this);
        boolean first;
        synchronized (closeLock) {
            switch (state) {
                case OPEN: {
                    first = true;
                    state = State.CLOSING;
                    break;
                }
                case CLOSING:
                case CLOSED: return;
                default: throw new IllegalStateException();
            }
        }
        if (first) try {
            closeAction();
        } catch (IOException e) {
            log.tracef(e, "Close of %s failed", this);
            final Map> closeHandlers;
            synchronized (closeLock) {
                state = State.CLOSED;
                closeHandlers = this.closeHandlers;
                this.closeHandlers = null;
                closeLock.notifyAll();
            }
            if (closeHandlers != null) {
                for (final CloseHandler handler : closeHandlers.values()) {
                    runCloseTask(new CloseHandlerTask(handler, e));
                }
            }
        } catch (Throwable t) {
            log.errorf(t, "Close action for %s failed to execute (resource may be left in an indeterminate state)", this);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Key addCloseHandler(final CloseHandler handler) {
        if (handler == null) {
            throw new NullPointerException("handler is null");
        }
        synchronized (closeLock) {
            if (state == State.OPEN || state == State.CLOSING) {
                final Key key = new KeyImpl(this);
                final Map> closeHandlers = this.closeHandlers;
                if (closeHandlers == null) {
                    final IdentityHashMap> newMap = new IdentityHashMap>();
                    this.closeHandlers = newMap;
                    newMap.put(key, handler);
                } else {
                    closeHandlers.put(key, handler);
                }
                return key;
            }
        }
        runCloseTask(new CloseHandlerTask(handler, null));
        return new NullKey();
    }

    private static void runCloseTask(final Runnable task) {
        try {
            task.run();
        } catch (Throwable t) {
            log.tracef(t, "Got exception running close task %s", task);
        }
    }

    private static final class NullKey implements Key {
        public void remove() {
        }
    }

    private static final class KeyImpl> implements Key {

        private final AbstractHandleableCloseable instance;

        private KeyImpl(final AbstractHandleableCloseable instance) {
            this.instance = instance;
        }

        public void remove() {
            synchronized (instance.closeLock) {
                final Map> closeHandlers = instance.closeHandlers;

                if (closeHandlers != null) {
                    closeHandlers.remove(this);
                }
            }
        }
    }

    /**
     * Get the executor to use for handler invocation.
     *
     * @return the executor
     */
    protected Executor getExecutor() {
        return executor;
    }

    /**
     * Finalize this closeable instance.  If the instance hasn't been closed, it is closed and a warning is logged.
     */
    protected void finalize() throws Throwable {
        try {
            super.finalize();
        } finally {
            if (autoClose && isOpen()) {
                if (LEAK_DEBUGGING) {
                    final Throwable t = new LeakThrowable();
                    t.setStackTrace(backtrace);
                    log.warnf(t, "Leaked a %s instance: %s", getClass().getName(), this);
                } else {
                    log.tracef("Leaked a %s instance: %s", getClass().getName(), this);
                }
                closeAsync();
            }
        }
    }

    /**
     * Check if open, throwing an exception if it is not.
     *
     * @throws NotOpenException if not open
     */
    protected void checkOpen() throws NotOpenException {
        synchronized (closeLock) {
            if (state != State.OPEN) {
                throw new NotOpenException(toString() + " is not open");
            }
        }
    }

    @SuppressWarnings({ "serial" })
    static final class LeakThrowable extends Throwable {

        LeakThrowable() {
        }

        public String toString() {
            return "a leaked reference";
        }
    }

    final class CloseHandlerTask implements Runnable {

        private final CloseHandler handler;
        private final IOException exception;

        CloseHandlerTask(final CloseHandler handler, final IOException exception) {
            this.handler = handler;
            this.exception = exception;
        }

        @SuppressWarnings("unchecked")
        public void run() {
            SpiUtils.safeHandleClose(handler, (T) AbstractHandleableCloseable.this, exception);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy