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: 5.0.8.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.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.jboss.logging.Logger;
import org.wildfly.common.Assert;

/**
 * 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
     */
    public 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);
            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, new IOException(t)));
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public Key addCloseHandler(final CloseHandler handler) {
        Assert.checkNotNullParam("handler", handler);
        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 - 2025 Weber Informatics LLC | Privacy Policy