org.jboss.remoting3.spi.AbstractHandleableCloseable Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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 super T> 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 super T> 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 super T> 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 super T> 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 super T> 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 super T> handler : closeHandlers.values()) {
runCloseTask(new CloseHandlerTask(handler, new IOException(t)));
}
}
}
}
/**
* {@inheritDoc}
*/
public Key addCloseHandler(final CloseHandler super T> 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 super T> handler;
private final IOException exception;
CloseHandlerTask(final CloseHandler super T> handler, final IOException exception) {
this.handler = handler;
this.exception = exception;
}
@SuppressWarnings("unchecked")
public void run() {
SpiUtils.safeHandleClose(handler, (T) AbstractHandleableCloseable.this, exception);
}
}
}