com.yahoo.jdisc.AbstractResource Maven / Gradle / Ivy
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc;
import com.yahoo.jdisc.handler.RequestHandler;
import com.yahoo.jdisc.service.ClientProvider;
import com.yahoo.jdisc.service.ServerProvider;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for
* all subclasses of {@link RequestHandler}, {@link ClientProvider} and {@link ServerProvider}. Once the reference count
* of this resource reaches zero, the {@link #destroy()} method is called.
*
* @author Simon Thoresen
*/
public abstract class AbstractResource implements SharedResource {
private static final Logger log = Logger.getLogger(AbstractResource.class.getName());
private final boolean debug = SharedResource.DEBUG;
private final AtomicInteger refCount;
private final Object monitor;
private final Set activeReferences;
private final ResourceReference initialCreationReference;
protected AbstractResource() {
if (!debug) {
this.refCount = new AtomicInteger(1);
this.monitor = null;
this.activeReferences = null;
this.initialCreationReference = new NoDebugResourceReference(this);
} else {
this.refCount = null;
this.monitor = new Object();
this.activeReferences = new HashSet<>();
final Throwable referenceStack = new Throwable();
this.activeReferences.add(referenceStack);
this.initialCreationReference = new DebugResourceReference(this, referenceStack);
}
}
@Override
public final ResourceReference refer() {
if (!debug) {
addRef(1);
return new NoDebugResourceReference(this);
}
final Throwable referenceStack = new Throwable();
final String state;
synchronized (monitor) {
if (activeReferences.isEmpty()) {
throw new IllegalStateException("Object is already destroyed, no more new references may be created."
+ " State={ " + currentStateDebugWithLock() + " }");
}
activeReferences.add(referenceStack);
state = currentStateDebugWithLock();
}
log.log(Level.WARNING,
getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }",
referenceStack);
return new DebugResourceReference(this, referenceStack);
}
@Override
public void release() {
initialCreationReference.close();
}
private void removeReferenceStack(final Throwable referenceStack, final Throwable releaseStack) {
final boolean doDestroy;
final String state;
synchronized (monitor) {
final boolean wasThere = activeReferences.remove(referenceStack);
state = currentStateDebugWithLock();
if (!wasThere) {
throw new IllegalStateException("Reference is already released and can only be released once."
+ " reference=" + Arrays.toString(referenceStack.getStackTrace())
+ ". State={ " + state + "}");
}
doDestroy = activeReferences.isEmpty();
}
log.log(Level.WARNING,
getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }",
releaseStack);
if (doDestroy) {
destroy();
}
}
/**
* Returns the reference count of this resource. This typically has no value for other than single-threaded unit-
* tests, as it is merely a snapshot of the counter.
*
* @return The current value of the reference counter.
*/
public final int retainCount() {
if (!debug) {
return refCount.get();
}
synchronized (monitor) {
return activeReferences.size();
}
}
/**
* This method signals that this AbstractResource can dispose of any internal resources, and commence with shut
* down of any internal threads. This will be called once the reference count of this resource reaches zero.
*/
protected void destroy() {
}
private int addRef(int value) {
while (true) {
int prev = refCount.get();
if (prev == 0) {
throw new IllegalStateException(getClass().getName() + ".addRef(" + value + "):"
+ " Object is already destroyed."
+ " Consider toggling the " + SharedResource.SYSTEM_PROPERTY_NAME_DEBUG
+ " system property to get debugging assistance with reference tracking.");
}
int next = prev + value;
if (refCount.compareAndSet(prev, next)) {
return next;
}
}
}
/**
* Returns a string describing the current state of references in human-friendly terms. May be used for debugging.
*/
public String currentState() {
if (!debug) {
return "Active references: " + refCount.get() + "."
+ " Resource reference debugging is turned off. Consider toggling the "
+ SharedResource.SYSTEM_PROPERTY_NAME_DEBUG
+ " system property to get debugging assistance with reference tracking.";
}
synchronized (monitor) {
return currentStateDebugWithLock();
}
}
private String currentStateDebugWithLock() {
return "Active references: " + makeListOfActiveReferences();
}
private String makeListOfActiveReferences() {
final StringBuilder builder = new StringBuilder();
builder.append("[");
for (final Throwable activeReference : activeReferences) {
builder.append(" ");
builder.append(Arrays.toString(activeReference.getStackTrace()));
}
builder.append(" ]");
return builder.toString();
}
private static class NoDebugResourceReference implements ResourceReference {
private final AbstractResource resource;
private final AtomicBoolean isReleased = new AtomicBoolean(false);
public NoDebugResourceReference(final AbstractResource resource) {
this.resource = resource;
}
@Override
public final void close() {
final boolean wasReleasedBefore = isReleased.getAndSet(true);
if (wasReleasedBefore) {
final String message = "Reference is already released and can only be released once."
+ " State={ " + resource.currentState() + " }";
throw new IllegalStateException(message);
}
int refCount = resource.addRef(-1);
if (refCount == 0) {
resource.destroy();
}
}
}
private static class DebugResourceReference implements ResourceReference {
private final AbstractResource resource;
private final Throwable referenceStack;
public DebugResourceReference(final AbstractResource resource, final Throwable referenceStack) {
this.resource = resource;
this.referenceStack = referenceStack;
}
@Override
public final void close() {
final Throwable releaseStack = new Throwable();
resource.removeReferenceStack(referenceStack, releaseStack);
}
}
}